write.vanoix.com

Reader

Read the latest posts from write.vanoix.com.

from alexandre

tl;dr : nous allons parler et illustrer l'intérêt de créer quelques objets PHP autour de certains composants externes, tout en restant sur l'écosystème Symfony, dans le but de réduire le travail lors des mises à jour du Framework.

Attention : cet article fait partie d'une série consacrée à l'architecture, les autres articles sont disponibles via cette recherche.

Préambule

J'ai vu passer un tweet de Grégoire hier concernant le changelog Symfony 6.3 à venir. En le parcourant, j'ai pu lire ceci :

Bundle/TwigBundle

Deprecate the Twig_Environment autowiring alias, use Twig\Environment instead

Ça m'a fait sourire parce que j'ai repensé à ce changement (le deprecate existe depuis très longtemps, Twig v2.7, juillet 2019).

Mais j'avais quand même ce sourire au coin qui dit “tiens, certaines personnes vont devoir faire avec cette dépréciation dans tout leur projet alors que moi, non”. Alors l'occasion fait le larron, petit article.

Pourquoi encapsuler ses dépendances externes ?

Si vous êtes tout neuf, ou toute neuve, sur des projets Symfony, vous ne savez peut être pas que le Framework a, et va, encore évoluer. Il va évoluer en termes de choix techniques, d'architecture, d'implémentation, de composants, etc. Il va même arriver certaines fois où l'écosystème Symfony va livrer un nouveau composant qui pourrait remplacer une dépendance tierce. C'est normal.

Les grandes questions sont donc : comment faire pour se prémunir de tout ça ? Comment éviter d'avoir du collatéral en cas de changement ? Comment passer moins de temps sur les migrations de code ? etc.

L'une des réponses : l'encapsulation.

Dans des architectures DDD, hexagonale, clean, “défensive” ou d'ailleurs, tout simplement en POO, on va vous inciter à vous protéger de vos dépendances externes et d'en restreindre l'accès.

Cette protection n'est pas une protection pour faire bien sur le papier et se gargariser. C'est une protection vis-à-vis des tiers, c'est même une méthode heuristique de gestion de la dette technique (©️ Julien Topçu).

Comment ?

Pour chaque dépendance externe, nous allons créer un dossier (et donc un espace de noms) avec à minima une interface et une implémentation de cette interface. Nous utiliserons nos interfaces dans nos injections de dépendances, l'autowiring de Symfony fera très bien son travail et notre code échangera avec nos méthodes au travers de contrats.

C'est vraiment la méthode de base à suivre et vous verrez que généralement, l'implémentation concrète est vraiment simple, voire ridicule, au point de déclencher des débats et claquer les plus belles poses de Yamcha aka “la grosse PLS” chez certaines personnes parce que tu comprends Alexandre, ça coute rien à changer, bim bam boum, rechercher/remplacer. Oui. Sauf que quand j'arrive sur des projets, le constat est simple personne, ou presque, ne le fait.

Alors je sors ma réponse simple et empirique, parce que comme on le sait tous et toutes, c'est toujours chez les autres et jamais chez nous :

Je traine depuis maintenant 9 ans certaines implémentations, je les ai fait évoluer rapidement, dans tous les projets, au travers de 5 versions majeures de Symfony. On est la pour coder. C'est un faux-débat.

Cas concrets

Quelques cas concrets pour illustrer le propos maintenant. Nous allons parler d'UUID, de Slug et Template. J'utilise pour ma part des architectures inspirées de DDD, Hexagonal et Clean Architecture, mais ce n'est pas un prérequis. Faites ce que vous voulez de vos espaces de noms, ce n'est pas ce point qui est le plus important alors n'en faites surtout pas un prérequis.

UUID

J'ai toujours utilisé le composant UUID de Ben Ramsey, mais Symfony a sorti son propre composant UID. J'étais plutôt dans la team “pourquoi ?” et puis j'ai rationalisé.

J'utilise tout le temps et par défaut des UUID v4.

<?php

declare(strict_types=1);

namespace App\Shared\Infrastructure\Generator;

interface GeneratorInterface
{
    public static function generate(): string;
}

Le diff de l'implémentation (hors use) a donc été le suivant :

- return Uuid::uuid4()->toString();
+ return Uuid::v4()->toRfc4122();

Comme vous l'aurez compris, mon code utilise quant à lui toujours la méthode UuidGenerator::generate(). Le jour où je voudrais changer de point de vue sur la version de l'UUID, ça se passera ici et uniquement ici.

Slug

Créer un slug est un besoin récurrent, ne serait-ce que pour du SEO. Là aussi et pendant longtemps, j'ai utilisé la librairie cocur\slugify et Symfony a apporté un nouveau composant String.

L'interface est la suivante

<?php

declare(strict_types=1);

namespace App\Shared\Infrastructure\Slugger;

interface SluggerInterface
{
    public static function slugify(string $string, string $separator = '-'): string;
}

Le diff :

- return (new Slugify())->slugify($string, $separator);
+ return (new AsciiSlugger())
+             ->slug($string, $separator)
+             ->toString();

Problème, contrairement à la librairie initialement utilisée, le composant ne touche pas à la casse du texte qu'on lui envoie. Il faut penser à lui dire de tout mettre en minuscule.

Comment est-ce que je l'ai vu ? Avec le test unitaire, très simple à écrire, qui va avec l'implémentation. Du coup je me suis fait avoir… Le temps de lancer le test pour vérifier le changement.

<?php

declare(strict_types=1);

namespace spec\App\Shared\Infrastructure\Slugger;

use App\Shared\Infrastructure\Slugger\Slugger;
use App\Shared\Infrastructure\Slugger\SluggerInterface;
use PhpSpec\ObjectBehavior;

class SluggerSpec extends ObjectBehavior
{
    function it_is_initializable()
    {
        $this->shouldHaveType(Slugger::class);
        $this->shouldBeAnInstanceOf(SluggerInterface::class);
    }

    function it_should_slugify_a_simple_sentence()
    {
        $this::slugify('A simple sentence')->shouldReturn('a-simple-sentence');
        $this::slugify('A Simple Sentence', '_')->shouldReturn('a_simple_sentence');
    }
}

J'avais oublié le lower, le texte retourné était A-simple-sentence/A_Simple_Sentence.

Le diff final :

return (new AsciiSlugger())
            ->slug($string, $separator)
+             ->lower()
            ->toString();

Je n'ai pas activé le support des emojis, nous verrons si cela arrive lors des projets et on verra ça au cas par cas.

Template

Dernier exemple pour la route, le fameux Twig. Oui, je fais du Symfony et encapsule du Twig dans un objet Template. Pourquoi ? Réponse dans la modification du changelog précédemment cité, mais, petite pensée pour Twig v1, v2 et enfin v3. Ca ne rajeunit pas ce genre de constats.

<?php

declare(strict_types=1);

namespace App\Shared\Infrastructure\Templating;

interface TemplatingInterface
{
    /**
     * @param array<array-key, mixed> $parameters
     */
    public function render(string $name, array $parameters = []): string;
}

Et son implémentation (qui n'a pas bougé depuis un moment maintenant) :

<?php

declare(strict_types=1);

namespace App\Shared\Infrastructure\Templating;

use Twig\Environment;

final class Twig implements TemplatingInterface
{
    public function __construct(private readonly Environment $twig)
    {
    }

    /**
     * @param array<array-key, mixed> $parameters
     */
    public function render(string $name, array $parameters = []): string
    {
        return $this->twig->render($name, $parameters);
    }
}

Oui je pourrais me payer le luxe de changer de moteur de template, d'en avoir plusieurs en parallèle et je ne le ferais probablement pas mais j'espère que vous avez compris que le point n'est pas la.

Pour terminer

Je fais la même chose pour d'autres composants et librairies tels que le Logger, le Mailer, des bus du composant Messenger, l'upload de fichiers et j'en passe. Comme vous le voyez, le cout technique est ridicule et il est en plus largement compensé lors que l'on veux changer notre façon de travailler avec du code tiers car le changement ne s'opère qu'à un seul endroit. Ce point d'architecture est donc dans ma case du “ça ne coute rien, ça couvre le cas où, c'est du quick win”.

Pour terminer, je pense également au composant Clock, composant officiellement introduit avec Symfony 6.2. Là encore, sourire au coin, un peu mesquin car je ne me souviens pas du nombre exact de discussions où l'on a pu m'expliquer que “la maitrise du temps” était une fois de plus un délire inutile associé au DDD. Ca ne m'empêchait pas d'encapsuler un DateTimeImmutable.

J'ai supprimé mon implémentation, l'ai remplacée par le composant Symfony, ai vérifié mes tests et… ça juste marche.

Cout technique de l'opération, 5 minutes.

 
En savoir plus...

from alexandre

J'ai eu à faire à beaucoup de projets Sylius ces derniers temps : développement, audit ou encore accompagnement d'équipe et architecture. À chaque prestation ou presque, une même question revient : quelle est la meilleure façon d'organiser ses templates ?

Au commencement : le SyliusThemeBundle

Ne vous laissez pas avoir par le nom, le SyliusThemeBundle est un bundle permettant d'ajouter une fonctionnalité de thème à un projet Symfony. Pour faire simple et compréhensible, un nouveau dossier “theme” est présent à la racine du projet et ajoute un niveau supplémentaire dans la hiérarchie de vos templates.

Étant donné qu'il s'agit d'un thème, vous pouvez en avoir plusieurs en parallèle. Le thème se déclare via un fichier composer.json très simple et vient surcharger le système de templates que vous connaissez déjà sur Symfony. Le thème se trouve au plus haut de la hiérarchie des templates de votre projet et dans la continuité du système de fallback : un fichier qui n'existe pas dans le dossier thème est un fichier qui sera recherché dans le dossier templates (puis dans un bundle etc).

Le thème à afficher peut se choisir de bien des manières et même très simplement. Tout est imaginable à ce niveau. Vous pouvez spécifier un thème par défaut pour votre projet et/ou déclencher l'activation d'un thème suivant vos critères : session, base de données, aléatoire (pour de l'A/B testing) ou encore suivant l'utilisateur connecté... Vous êtes libres d'implémenter vos propres règles et de les hiérarchiser.

Fonctionnalité intéressante, un thème peut en étendre un autre. Vous pouvez avoir un thème de base, puis d'autres qui ne feront que surcharger quelques fichiers : vive les usines à sites.

Qu'est-ce que l'on met dans un thème ?

Récapitulons : un thème n'est qu'un ensemble de fichiers Twig qui vont venir en surcouche des templates Symfony. On y met donc ce que l'on souhaite en respectant l'arborescence des templates du projet. Assets et translations peuvent également être ajoutés.

Passons maintenant au problème de fond, comment organiser tout ça.

Je vois, dans beaucoup de projets, des thèmes dans lesquels on met tous les templates sans se poser la question suivante : que se passe-t-il si le thème est désactivé ?

La réponse est assez simple, si vous désactivez un thème, vous ne voyez plus les éléments correspondants. C'est pourquoi il est très important de respecter la hiérarchie des templates et les fallbacks. Un thème surcharge la totalité de vos templates, interface d'administration comprise (dans le cas de Sylius), ce qui peut être un vrai problème si vous avez ajouté des champs de formulaires dans l'interface d'administration par exemple.

Et donc ?

L'arbre de décision est donc le suivant :

  • si un template doit TOUJOURS être présent dans un projet, que le thème soit activé ou non : il ne doit pas seulement exister dans le thème du projet, mais dans les templates (avant de potentiellement être surchargé dans un ou plusieurs thèmes),
  • si un template doit être visible UNIQUEMENT si le thème est activé : le template doit être dans le dossier themes correspondant,
  • si un template peut exister dans plusieurs thèmes différents, vous pouvez utiliser la hiérarchie des thèmes (parent/enfant) afin d'éviter les copier/coller.

Coté JS, il n'est pas possible détecter le thème pour ne faire un build que si le thème est actif. Ce n'est pas un problème : faites vos build en définissant la config webpack dans votre thème puis un require dans votre fichier webpack applicatif.

Une fois compilé, le coût technique est nul.

// webpack.config.js
const Encore = require('@symfony/webpack-encore');

// Votre config existante

// stuff
const front = Encore.getWebpackConfig();

// Fin de la config existante

Encore.reset();

const bootstrapTheme = require('./themes/BootstrapTheme/webpack.config');

module.exports = [front, bootstrapTheme];

Conclusion, respectez ces règles : vous serez joie et amour.

 
En savoir plus...

from alexandre

tl;dr : Dans cet article, qui change un peu du PHP, nous allons voir comment l'on pourrait adresser la problématique du vote électronique avec ce qu'on appelle vulgairement “la blockchain”.

Disclaimer

Cet article n'est pas une usine à lien vers des projets trop cool pour jouer à l'apprenti trader, c'est même assez compliqué puisque les sources sont souvent rattachées à des projets. Je ne citerais donc explicitement que Bitcoin et Ethereum.

Cet article n'est également pas une réponse absolue et dogmatique pour le vote électronique.

Le débat “vote électronique blockchain” est en cours comme à chaque élection, profitons-en pour discuter un peu entre personnes doux rêveurs technophiles.

Le contexte

En 2018 j'ai essayé de mettre en place un projet d'entreprise baptisé “Le Petit Bloc”. Cette société avait comme baseline, dans la langue de Shakespeare de “build a decentralised, trustable and reliable world”. Oui, votre serviteur avait de l'ambition.

L'objectif était de se servir des technologies de décentralisation pour adresser certains problèmes. On allait donc piocher dans le panel de technologies pour essayer, pour faire simple, de faire mieux.

Dans ce contexte, nous nous sommes nécessairement intéressés à tout ce qu'on met dans “la blockchain” et avons creusé un certain nombre de sujets. Parmi ces sujets, celui du vote électronique et tout le monde pourra convenir que du point de vue technique, c'est quand même un sujet intéressant à creuser.

État des lieux

Ce que l'on appelle vulgairement, de mon point de vue, “la blockchain” est un savant mélange de différentes technologies :

  • Un réseau pair à pair,
  • Une preuve,
  • De la cryptographie asymétrique,
  • Une base de données décentralisée, notre blockchain.

Chacun des éléments est indépendant et on retrouve ces éléments technologiques dans différentes situations. Ce n'est que l'assemblage de tout ces éléments qui donne un protocole tel que Bitcoin.

La chronologie des événements clés de ce que l'on connait actuellement et sur lequel on aime tant débattre, à savoir l'unité de compte bitcoin et le protocole Bitcoin, est à peu près la suivante :

  • 1971 : Richard Nixon met fin à la convertibilité du dollar. Un dollar n'est officiellement plus indexé sur son équivalent en or,
  • 1982 : David Chaum commence à réfléchir à un système de paiements intraçable avec le premier dollar électronique. Il est à mon sens l'élément déclencheur de la suite de la chronologie,
  • 1990 : David Chaum travaille sur l'e-cash et le système est testé dans certaines banques,
  • 1990 : Sholom Rosen travaille sur un système de monnaie électronique qui sera breveté,
  • 1992 : Tim May publie le Manifeste d'un crypto anarchiste issue du mouvement Cypherpunk dont l'un des pères spirituel est David Chaum,
  • 1993 : Eric Hugues publie le Manifeste d'un cypherpunk,
  • 1997 : Adam Back travail sur le HashCash au travers de la preuve de travail dans le but de lutter contre le spam,
  • 1998 : Wei Dai publie “b-money, an anonymous, distributed electronic cash system” qui servira de base au protocole Bitcoin,
  • 2000 : Freenet devient le premier réseau web distribué,
  • 2005 : Nick Szabo propose Bit-Gold, prémice de Bitcoin, qui n'obtient pas le soutien nécessaire. La résolution du problème des généraux byzantins est mise en avant,
  • 2008 : Satoshi Nakamoto publie “A peer to peer electronic cash system” qui servira de base au protocole Bitcoin,
  • 2018 : Alexandre s'intéresse à “la blockchain” et propose d'adresser le problème du vote électronique. (oh c'est bon, on rigole 😝)

Depuis 2008, des dizaines d'évolutions, de remises en question, mais toujours ces mêmes fondamentaux.

Qu'est-ce que c'est “un vote” ?

Ma compréhension du vote est la suivante : je dois parfois donner mon avis sur l'évolution de la société. Pour donner mon avis, on me propose de me déplacer dans un bureau de vote, de choisir une réponse dans un champ de possibilités et de donner ma réponse. Je signe alors une feuille permettant de déclarer mon vote en présentant ma carte d’électeur et une pièce d'identité. Je peux ne pas avoir ma carte d’électeur, mais je dois être inscrit sur la liste du bureau du vote. Les réponses sont comptabilisées par plusieurs personnes afin d'éviter la fraude et la somme de toutes ces actions permet d'acter une décision collective.

Il y a quatre étapes importantes :

  • Je suis un électeur et dois prouver mon droit de vote,
  • Je signe une feuille d’émargement permettant de ne voter qu'une fois pour un même scrutin,
  • Mon vote est anonyme,
  • Un système antifraude humain est présent.

Ce système de vote est à priori fiable dans un état qui fonctionne correctement, mais ne l'est pas nécessairement dans un pays qui n'est pas stable.

Une machine de vote électronique

Le vote électronique est déjà en place dans notre pays, en 2014 64 communes ont permis le vote électronique. Le vote électronique se fait au travers d'une machine, programmée par une société, des composants eux-mêmes programmable, possédant du code informatique, sans aucune possibilité de vérification pour les votants et c'est la somme de tous ces éléments qui amène à dire à bon nombre d'informaticiens que ce n'est certainement pas la bonne solution.

Dès lors, est-ce que l'on peut faire mieux ?


Du côté de la blockchain

Il n'existe aucun protocole de vote électronique basé sur une blockchain .

Par extension, les initiatives dApp ne remplissent pas nos critères, car elles sont pseudonymes et traçables.

Faisons tout de même le parallèle avec nos quatre étapes.

Comment est-on identifié ?

Pour échanger une unité de compte sur un protocole, vous devez posséder une adresse unique reliée à un portefeuille. Dans certains cas, une adresse peut être dérivée, un peu comme les différents espaces pour ranger vos cartes bancaires, pièces et billets.

L'adresse est numérique, mais le portefeuille peut être physique (un bout de papier) ou numérique, hardware ou software.

Comment les transactions sont-elles authentifiées ?

Une transaction correspond à une adresse, on n’accède pas à une adresse sans connaitre la clé privée permettant de la déverrouiller.

Comment gère t'on l'anonymat ?

Le protocole Bitcoin n'est pas anonyme, il est pseudonyme. Une adresse sur le réseau ne permet pas de savoir quel humain effectue une transaction, mais la totalité des échanges est vérifiable. Dès lors, lorsque l'on arrive dans un système faisant un lien entre adresse Bitcoin et une personne physique (via un KYC – Know your customer), on est capable d'identifier toutes les transactions. C'est d'ailleurs comme cela qu'on retrace certains piratages/rançons.

Il existe des protocoles anonymes, mais très peu le sont réellement. Différents exemples simple de faux anonymat :

  • une transaction dont on ne connait pas le montant, mais pour laquelle on connait l'émetteur et le récepteur est-elle anonyme ?
  • une transaction dont on ne connait ni l’émetteur ni le récepteur, mais dont le montant est connu via le registre est-elle anonyme ? (pour faire simple, si une adresse fait -153 et qu'une autre fait +153 c'est qu'on a probablement identifié l'échange)
  • une transaction anonyme sur un réseau comptant très peu de nœuds de vérification et donc facilement contrôlable est-elle réellement anonyme ?

Lors d'un échange entre deux adresses de manière anonyme, on fait intervenir un algorithme qui a pour but d'offusquer une partie ou la totalité de la transaction afin de la rendre intracable. On appelle ça un mixing. Cela sous-entend donc que l'on est technologiquement capable de rendre anonyme le contenu d'une transaction, sur un registre public. Si c'est un problème dans le cas d'un échange monétaire, ça ne l'est pas nécessairement dans le cas du vote.

Comment analyse-t-on les transactions ?

La blockchain est un registre distribué : un livre de comptes. Imaginez un livre dont chacune des pages correspond à un bloc. Vous êtes libres de mettre un nombre de transactions compris entre 0 et le nombre de lignes disponible sur chacune des pages. L'ordre des pages vous permet de remonter l'historique. Si vous n'êtes pas disponible, vous pouvez donner votre livre à quelqu'un d'autre. Si vous voulez vérifier qu'aucune bêtise n'a été faite, vous pouvez repartir de la première page, etc.

Dans la version la plus simple, on retrouve un système de blocs puis de transactions au travers d'un arbre de merkle et pour vulgariser, quitte à prendre des raccourcis, c'est la chaine ayant le plus de blocs valides qui l'emporte.

Il existe des alternatives par exemple au travers des graphs orientés acycliques ou encore des “sidechains”. Certains protocoles intègrent également des blocs “snapshots” permettant de rendre le calcul de l'intégrité de la chaine optionnelle.

On identifie également des niveaux “technologiques” autour de ces chaines. On parle alors de Layers. Bitcoin est une chaine de niveau 1 la ou Ethereum est une chaine de niveau 2, car possédant une machine virtuelle permettant d'effectuer des calculs. Il existe également le niveau 3 dont l'objectif est l’interopérabilité entre différentes chaines.

A noter que si Bitcoin est bien une chaine de niveau 1, il existe déjà des surcouches de niveau supérieur compatibles avec le protocole de niveau 1.

Comment fonctionne le système antifraude ?

L'un des arguments massue des défenseurs du vote électronique en version blockchain est la transparence. On ne peut que leur donner raison puisque le code de beaucoup de protocoles de monnaie électronique est ouvert et auditable. On peut également démarrer un nœud sur le réseau et vérifier l'intégralité des transactions afin de détecter une potentielle fraude.

Malheureusement, rien n'oblige à mettre en place cette transparence. C'est d'ailleurs pour cette raison que du point de vue technique, une blockchain privée n'est qu'une base de données décentralisée contrôlée, donc potentiellement modifiable, donc sans aucun intérêt.

Vient ensuite la preuve. Le protocole Bitcoin utilise la preuve de travail impliquant une dépense énergétique conséquente en échange de la validation d'un lot de transactions.

D'autres protocoles utilisent d'autres types de preuves. On retrouve par exemple la preuve d'enjeu (Proof Of Stake) qui consiste à mettre en gage un montant conséquent qui incite fortement à ne pas valider de fausses transactions au risque de perdre gros. Problème, ceux qui ont le plus de moyens contrôlent potentiellement le système.

Certaines preuves sont beaucoup plus folkloriques comme la preuve d'espace qui repose sur le coût de stockage et la capacité à acheter de l'espace disque. Je ne dis personnellement pas bravo.

Derrière une preuve, il y a une motivation à maintenir le réseau dans un état fonctionnel. Dans les systèmes actuels, cette motivation est purement financière.

Il existe des systèmes pour contrer des attaques ayant pour but de changer le registre, des systèmes pour contrer des “super calculateurs” mais le système anti-fraude repose également sur un consensus. Il peut donc y avoir des divergences d'opinions et donc des “forks”.


Mon vote électronique

C'est le moment de l'article où je dis que je n'ai malheureusement pas toutes les solutions. Et oui.

Comment est-on identifié ?

Créer une adresse numérique, pouvoir la récupérer en cas de perte c'est quelque chose de très bien, mais ça ne permet pas de gérer les personnes qui n'ont pas accès ou ne peuvent pas accéder à un ordinateur.

On pourrait imprimer des adresses, les papers wallets existent et il suffirait alors de scanner le code pour authentifier la personne, mais de la même façon qu'une carte d’électeur est doublée d'une carte d'identité, il faudrait que le paper wallet soit adossé à un autre document.

Quelle que soit l'option choisie, on arrive dans un problème inhérent à notre système que je résume sous la thématique suivante : la gestion de l'identité (numérique).

Ce problème doit d'abord être adressé. Tant qu'il ne le sera pas, on ne tient à mon sens, pas une solution pour du vote électronique et une double authentification (avec un email/sms par exemple) ne me parait personnellement pas suffisante. On le constate déjà sur des applications web.

Alors oui, nous pourrions continuer de demander aux électeurs de se déplacer mais l'un des éléments du débat en faveur du vote electronique est justement la lutte contre l'abstentionnisme.

Si on devait me donner carte blanche à ce niveau, j'irais personnellement transposer le protocole Macaroon sur des technologies acycliques pour aller adresser d'autres problèmes au passage, mais ce n'est pas le sujet.

Comment les transactions sont-elles authentifiées ?

Lorsque l'on vote, on signe une feuille. Je n'ai pas envie de changer cette règle. Pour cette raison, je pense que l'on doit utiliser non pas une chaine, mais deux chaines (via les sidechains ou les layers) et cette première chaine doit être pseudonyme afin d'être vérifiable.

Cette première chaine ne doit stocker que deux informations, la personne qui vote et le scrutin correspondant. Cette chaine est alors une transposition exacte de notre carte d’électeur et du coup de tampon associé ainsi que de la signature sur le registre. Si on regarde une carte d’électeur ou que l'on analyse tous les registres de votes, on connait au minimum le nombre de fois ou l'on a voté. Est-ce que c'est un problème ? Probablement pas et si ça devenait un problème, il suffirait alors de créer des chaines différentes pour chaque scrutin.

Cette intention de vote permet de valider le contenu du vote. Cela sous-entend que dans le cas où un vote frauduleux passerait dans le système, on pourrait l'invalider a posteriori et le décompter. De la même façon, si quelqu'un venait à voter pour quelqu'un d'autre, on pourrait tracer cette information. Les personnes qui ne veulent pas voter pourraient s'assurer qu'aucun vote contenant leur adresse n'est présent d'une manière beaucoup plus simple que d'arriver en bureau de vote et d'exiger de voir si l'on est présent ou non sur le carnet de signatures.

Tout ça n'est finalement qu'une transposition de ce que l'on fait déjà au travers de certains protocoles dits anonymes.

Comment analyse-t-on les transactions ?

Ça se complique un peu plus. Comme je l'ai dit, nous aurions deux chaines. La première chaine correspond à l'intention de votes et la seconde chaine contient le vote par lui-même.

Il ne doit pas être possible aux nœuds de validation du contenu du vote de remonter vers les nœuds de validation de l'intention de vote et le protocole ne doit que donner un go/no go pour accéder de la première à la seconde chaine. On a donc bien deux protocoles différents.

Concernant le décompte, nous pouvons nous tourner vers un protocole complexe basé sur un échange de clés. Au moment du vote, le contenu serait chiffré et ne pourrait être vérifié que par la clé du candidat correspondant. Chaque vote serait analysé tour à tour et le compteur mis à jour en conséquence. Ces clés pourraient être stockées à différents endroits : les partis politiques pourraient ne compter que leurs propres voix là ou d'autres comme le gouvernement pourraient compter la totalité des voix. On pourrait faire la somme des comptes pour chacun des partis pour ensuite la comparer au nombre de votants (en incluant un noeud pour le vote blanc).

Il y a donc plusieurs solutions. Reste à les implémenter et pourquoi pas, les voter.

On continue donc d'utiliser la base de nos protocoles dits anonymes et on rajoute quelque chose qui ressemble fortement à un échange PGP : le Verifiable Secret Sharing, composant indispensable des systèmes E2E.

Comment fonctionne le système antifraude ?

Nous l'avons vu dans les postulats du vote anonyme, il nous faut un nombre de nœuds importants afin de pouvoir détecter une fraude locale. Il nous faut également un code source accessible et auditable.

Quid de la nature de la chaine ? Publique, privée ou sous consortium ? Une chaine privée n'a aucun sens, une chaine publique est un idéal, une chaine sous consortium pourrait être justifiable sous condition d'un code source accessible et auditable, mais surtout d'un compte rendu de l'état de chacun des noeuds ainsi que du registre par des tiers.

À mon sens, les différents organes du gouvernement doivent bien entendu posséder des noeuds de validation, mais aussi les différents partis politiques et la presse. On pourrait même imaginer des nœuds de validation au-dessus du pays afin de valider un résultat définitif. Le système antifraude commencerait alors à se construire naturellement, car si quelqu'un triche, il pourrait être détecté. Dans le où différents acteurs se mettraient d'accord pour tricher, on pourrait appeler les noeuds supérieurs qui pourraient alors déclarer une fraude massive et invalider une élection. Une fraude massive décrédibiliserait la totalité du scrutin, des participants et pourrait avoir des répercutions sur les notes de fiabilités des pays et donc leur capacité d'endettement sans oublier l'indice même de confiance des votants réduisant l'envie de jouer avec le feu.

Malheureusement, cela fait beaucoup de “pourrait” et ne règle pas de manière technologique le problème des généraux byzantins. Quel est le couperet qui empêcherait la fraude ? Introduire une motivation monétaire dans un état qui maîtrise ses flux financiers ? Impossible. Quelque chose de “plus simple” comme l'annulation de la candidature d'un élu fraudeur ? Il faudrait certainement modifier les lois. Je pense que nous sommes tout simplement coincés ou que nous devons accepter que la résolution du problème fraude doit passer par l'humain et donc envisager que cela ne puisse pas aboutir.

La motivation ne peut qu'être la satisfaction d'un état fonctionnel et ça, c'est à priori une notion très personnelle. Tant que tout va bien, pourquoi pas, mais dès que l'on se trouve dans un état qui ne va pas bien politiquement ? Après tout, on trouve tout un lot de mauvaises personnes qui sont très satisfaites d'un état qui ne fait que servir leurs intérêts et la définition d'un état fonctionnel dans un même pays n'est pas la même suivant les personnes.


Conclusion

Comme vous pouvez le voir, ma vision technique du vote électronique ne repose absolument pas sur quelque chose de révolutionnaire. De la même façon que “la blockchain” n'est qu'un assemblage de différentes technologies, je ne propose que d'aller chercher des technologies déjà existantes et d'essayer de les mettre en oeuvre côte à côte pour apporter une possible réponse à la question du vote électronique.

Malgré tout, mon analyse personnelle m'amène à penser que cela n'est pas possible tant que l'on n'arrive pas à authentifier numériquement chaque votant et à garantir à tous la possibilité de voter.

Enfin, la mise en oeuvre. Dans le cas où une personne serait suffisamment brillante pour implémenter ce que je propose et adresser tous les problèmes que je n'ai pas encore imaginés, cette solution de vote électronique impliquerait une remise en question du système dans son ensemble et en est-il capable ?

Pour ces raisons je conclurais donc : ne faisons pas de vote électronique et commençons par faire en sorte de rétablir le dialogue et changer les modes de scrutins afin de ne plus voter contre mais pour. Travaillons pour la confiance dans le système, car en l'état, vote électronique ou pas, technologies ou pas, blockchain ou pas, rien ne va.

Bref,

“Tout cramer pour repartir sur des bases saines” ©Leodagan 2022.

 
En savoir plus...

from alexandre

Si vous ne l'avez pas encore remarqué, je commence beaucoup de choses, je disparais, je reviens avec des todos monstrueuses et ainsi de suite.

Pour faire simple, l'année 2020 n'a définitivement pas été facile et m'a obligé à mettre de côté un nombre incroyable de sujets. Le crunch est entrain de passer, l'année 2020 pour Vanoix est largement sauvée, est probablement la meilleure depuis la création de la société malgré un premier semestre désastreux et, même si différente, l'année 2021 promet son lot de surprises à tous les niveaux.

En vrac pour le perso (et pas forcément dans l'ordre)

  • Je fais maintenant partie du bureau de l'AFUP en tant que Vice-Président de l'association. J'avais pris la décision en 2020, Cécile a bien voulu de moi pour l'aider et je suis personnellement très content de pouvoir enfin me consacrer à l'AFUP ;
  • J'ai eu le plaisir d'être invité dans Message à caractère informatique pour discuter de différents sujets. Joie particulière de retrouver Quentin, ça fait bien trop longtemps que l'on n'avait pas discuté ;
  • J'ai une chaine Twitch, c'est encore très rudimentaire, mais personnellement, le format me plait. Cela va me permettre d'alimenter le côté OSS.

En vrac pour l'OSS (et pas forcément dans l'ordre)

Énormément de Sylius depuis le mois de juillet. J'accompagne une agence qui souhaite pivoter tout son ecommerce sous Sylius dans le cadre d'une prestation Vanoix. Nous avons écrit 26 plugins dont 7 sont censés revenir à l'OSS après lancement du projet (via le contrat OSS mis en place par Vanoix).

J'ai pris un peu de temps pour travailler sur les sujets suivants :

  • ModernPluginSkeleton qui me permet de bootstraper des plugins Sylius selon un nouveau format (compatible Symfony 4.x) ;
  • SyliusPluginStandard qui est une version modifiée de Sylius afin de me permettre de développer plus facilement les plugins dans un environnement “brut” ;
  • TemplateGenerator qui ne sert pour le moment pas à grand chose, mais qui devrait me permettre à terme de mieux bootstraper mes plugins/bundles.

J'ai également des choses qui devraient arriver coté Symfony mais chuuuuut.

Conclusion

La todo était énorme, elle l'est encore plus. Sylius est maintenant disponible en version 1.9 (sur une base de Symfony 5.2) ce qui m'oblige à faire monter tout l'existant mais, bien pour un mal, me permet “d'oublier” PHP 7.2 et Symfony 4.4 au quotidien.

J'ai vraiment hâte de passer aux étapes suivantes que j'espère monstrueuses en termes d'avancées pour la société, les ami·e·s, l'OSS et donc moi.

Merci à toutes les personnes qui ont été présentes en 2020, elles se reconnaîtront ❤️.

 
Read more...

from alexandre

Petite astuce, car non documentée officiellement, concernant Sylius. Si comme moi vous créez/utilisez des plugins, vous avez dû vous apercevoir que les nouveaux éléments de menus apparaissaient toujours en dernier.

Ce comportement est normal car il s'agit de listeners qui sont appelés les uns à la suite des autres en suivant l'ordre de déclaration. L'astuce consiste donc à créer un listener qui sera toujours appelé en dernier et qui ne servira qu'à modifier l'ordre des items.

Dans un premier temps, on va créer le listener.

<?php

declare(strict_types=1);

namespace App\Menu\Event;

use Sylius\Bundle\UiBundle\Menu\Event\MenuBuilderEvent;

final class AdminMenuListener
{
    public function reorderMenu(MenuBuilderEvent $event): void
    {
        $menu = $event->getMenu();
        $menu->reorderChildren([
            'catalog',
            'foo_menu', // plugin foo
            'bar_menu', // plugin bar
            'sales',
            'customers',
            'marketing',
            'configuration',
        ]);
    }
}

Puis on déclare la configuration de ce listener en pensant à lui donner l'ordre de priorité le plus bas (tous les éléments du menu doivent être connus avant d'être réordonnés).

services:
    App\Menu\Event\AdminMenuListener:
        tags:
            - { name: kernel.event_listener, event: sylius.menu.admin.main, method: reorderMenu, priority: -256 }

And voilà !

 
Read more...

from Emeric

Objectifs : Approfondir la notion de Rapid Application Development. Thèmes : Rapid Application Development, Long Terme, Direction Technique, Métier, Organisation. Longueur : ~1400 mots.

Long Terme ?

Avant de parler de Rapid Application Development (qu'on abrégera RAD dans la suite de cet article), il faut revenir sur la notion de terme d'un projet, sa période.

En général, on va considérer trois périodes possibles : le court, moyen et long terme. Wikipedia a une définition intéressante de la notion de long terme en économie :

On appelle longue période, celle au cours de laquelle la capacité productive de l'Offreur peut se modifier.

Dans un vocabulaire plus centré autour d'un projet informatique, on pourrait traduire par une période au cours de laquelle le service est amené à évoluer.

À contrario, l'article cité définit le court terme de cette manière :

On appelle courte période, celle dont la durée permet de faire varier la production, les équipements étant constants. Dans cet intervalle, c'est le taux d'utilisation ou d'emploi des équipements qui s'accroît et délivre un volume de production supérieur.

Un projet informatique court terme serait donc un projet où le service n'est pas amené à changer, seulement son intensité d'utilisation.

Enfin, le moyen terme y est défini comme la période égale à la durée de vie des équipements significatifs de l'activité : en informatique, la durée de vie des principales fonctionnalités d'un projet (hors fonctionnalités de support du métier).

Pour résumer : le court terme, c'est quand le projet informatique n'est absolument pas modifié. À partir du moment où l'on modifie uniquement des fonctionnalités annexes au métier, on est dans un projet moyen terme. Enfin, dès qu'on se met à modifier des fonctionnalités métier cœur, on se situe sur du long terme.

Rapid Application Development (RAD)

On entend beaucoup dire dans l'univers PHP que faire du RAD, c'est savoir utiliser EasyAdmin pour son client. Faire du RAD, c'est commencer un projet avec API Platform. Faire du RAD, c'est aimer concevoir son métier sous forme de CRUD.

Le RAD est une méthode formalisée et publiée par James Martin en 1991, basée sur de nombreux travaux durant les années 80. Elle est d'ailleurs considérée comme la base du postulat Agile, pour ceux qui connaîtraient mieux cette notion. Les objectifs de cette méthode étant de diminuer le time-to-market, améliorer l'évolutivité du produit, et diminuer les risques, par rapport aux méthodes “en cascade” :

Développer des logiciels dans un cycle qualifié d'itératif, d'incrémental et d'adaptatif.

Faire du RAD, c'est utiliser une méthodologie de développement logiciel qui consiste à : – concevoir petit à petit son logiciel, avec de courtes phases répétées de conception, d'implémentation, de test et de validation (itératif), – ajouter petit à petit les fonctionnalités du logiciel, avec des livrables fonctionnels à chaque nouvel ajout (incrémental), – et permettre la modification simple des fonctionnalités existantes, pour suivre au mieux les changements tactiques et stratégiques (adaptatif).

Mettre en place du RAD

En croisant les deux premières sections de cette article, on peut déduire une information intéressante :

Le long terme, c'est à partir du moment où les fonctionnalités principales d'une application évoluent. Le RAD, c'est une méthode basée sur l'évolution des fonctionnalités d'une application. Un projet en RAD doit être géré comme un projet long terme.

Oui, un projet en RAD doit être géré comme un projet long terme. Et voici quelques éléments pour aller dans ce sens.

Concéder du métier pour la technique

Le métier doit être organisé pour pouvoir faire des concessions pour la technique. Il peut s'agir de minimiser les fonctionnalités souhaitées, ou bien de prioriser différemment des sections d'un métier.

Si on prend l'exemple du métier Paiement d'une application, la décision peut être de n'accepter que le paiement via Paypal (minimisation des fonctionnalités). On peut aussi accepter le paiement via carte bleue sur la première année, et reconduire à une échéance plus lointaine l'acceptation du paiement en plusieurs fois (priorisation différente).

Produire du code accessible

La technique doit être organisée pour délivrer des produits à forte accessibilité. On peut documenter le code et l'infrastructure. Segmenter le code selon le métier permet de le documenter implicitement : il est plus aisé pour un nouveau développeur de le maîtriser car il utilise des termes proches de ceux du métier. Enfin, l'utilisation d'outils du “plus bas niveau commun” (le langage de programmation pour les développeurs, le système d'exploitation pour l'administration système) est une des démarches les plus fortes d'accessibilité. À l'inverse, l'utilisation de surcouches implique un besoin de spécialisation plus fort pour les (futurs) mainteneurs.

Dans notre implémentation du métier Paiement, on préférera par exemple l'utilisation d'un client PHP officiel de Paypal, plutôt que l'utilisation d'un bundle Symfony JMS Paypal. Niveau segmentation métier, un couplage faible dans un dossier src/Domain/Payment indépendant (inspiré de DDD), basé sur des interfaces et de la composition rendra le code plus simple à reprendre et à documenter.

Produire du code robuste

Les implémentations métier produites doivent être résilientes. Les tests automatiques permettent de s'assurer du fonctionnement normal du métier. À côté de ça, les revues de code peuvent apporter une vérification en amont et plus théorique des failles d'un système. De manière générale, la programmation défensive et ses concepts sont une bonne source d'inspiration pour aider à produire du code robuste.

Pour notre métier de Paiement, on pourrait tester unitairement les règles les plus complexes avec PHPUnit : calcul des prix, application des coupons de réduction. Pour un point aussi critique, il faudrait bien sûr définir et implémenter les comportements en cas d'échec à la moindre étape d'un paiement (que ce soit en retour de l'API, lors de l'insertion dans notre BDD, tout peut échouer).

Protocoliser les dépendances

Les dépendances doivent être placées derrière des protocoles définis par le métier. C'est bien entendu vrai pour les dépendances externes, mais c'est aussi recommandé pour les dépendances internes : au sein d'une même application, un sous-métier devrait communiquer avec un autre sous-métier à travers un protocole défini.

En reprenant l'exemple de notre Paiement, on définira un protocole (en PHP, le protocole le plus simple étant une interface) de paiement autour de notre dépendance au SDK Paypal : une méthode, ou un ensemble de méthodes, avec des paramètres et des valeurs de retour spécifiques à notre besoin métier. L'implémentation de ce protocole étant le seul code ayant une dépendance sur le SDK.

Former une équipe d'experts généralistes

L'équipe technique doit être en mesure d'adresser avec efficacité toutes ces problématiques. Pour ça, il va falloir composer avec un niveau suffisamment élevé d'expertise. Outre pour l'accessibilité et la robustesse du code, une équipe d'experts généralistes est nécessaire pour s'adapter à un métier qui peut changer de direction à n'importe quel moment et toujours trouver les meilleurs compromis.

Trouver de bonnes réponses à toutes les questions posées auparavant dans cet article nécessite une expérience globale sur les différents systèmes de Paiement existants. Il faut par exemple connaître les limites de Stripe, d'un point de vue technique, métier et financier. Il faut avoir expérimenté Dalenys pour savoir à quel point on peut l'intégrer derrière des protocoles simples.

Mettre l'humain au centre du dispositif

Le projet doit se construire autour de chacun de ses équipiers (on en revient aux méthodes agiles). Notamment autour de l'apprentissage de chacun. L'expérience, l'apprentissage passé qui permet de faire des choix plus efficace pour le contexte donné. La formation, l'apprentissage futur qui permet une meilleure efficacité sur le temps, un renouvellement des équipes et l'émergence de nouvelles options.

Pour finir avec le module de Paiement, la solution sera choisie en fonction des habitudes croisées des mainteneurs de ce métier. De la même façon, l'intégration d'un junior à l'évolution de ce métier pourra se faire via une micro-formation sur une heure, explicitant les objectifs, raisons et spécificités de l'implémentation.

C'est tout ce qu'on veut

Faire du Rapid Application Development, c'est faire un choix de complexité pour atteindre un niveau très fort d'adaptabilité pour le projet. C'est tout ce que voudrait n'importe quel client, n'importe quel product owner. Mais pour réussir, ça demande une organisation particulière et beaucoup d'expertise. Et dans un mauvais cadre, ou mal géré, ça peut coûter très cher.

En tout cas, ce n'est ni quelque chose de simple, ni quelque chose d'efficace tout le temps. Ni une méthode qui consiste à faire les choses le plus vite possible sans se poser de question. Et c'est encore moins faire du CRUD.

Liens

 
En savoir plus...

from Emeric

Objectifs : Découvrir la bibliothèque Nom pour faire du parsing de données en Rust. Thèmes : Développement, Rust, Nom, Parsing, Combinatory Parsing.

Consommer un message binaire

Je me suis remis à développer en Rust ces derniers temps, au travers de deux projets de serveurs. L'un d'eux est un serveur pour un jeu navigateur à base de WebSockets. Est donc arrivée à un moment la question :

Qu'est-ce qu'on a de bien à notre disposition pour parser des messages binaires en Rust ?

L'objectif de ce serveur en Rust étant d'avoir des performances excellentes – d'où la volonté d'avoir un protocole binaire bien conçu –, il fallait trouver un système léger et sans trop de complexité. Extensible et modifiable aussi (les spécifications du protocole changent souvent). Testable unitairement, si possible.

Et j'ai découvert Nom.

Manger de la donnée, la transformer en ensembles cohérents

Un message binaire, c'est une suite finie d'octets. En utilisant le protocole WebSocket, avec le type de message binary, on sait qu'on peut directement recevoir un message binaire. Pour exemple, voici le message Identité conçu pour le serveur (le client se présente au serveur) :

 0         1 2             3->X+3
+---------+---------------+------+
| 0x0 (8) | length X (16) | name |
+---------+---------------+------+

Sur le premier octet, on a l'opcode 0x0. Les deux octets suivants correspondent à la longueur X en octets du nom renseigné. Et enfin, X octets pour le nom en lui même.

Dans l'idéal, on souhaite que notre parser ait le prototype suivant :

fn parse_identity(input: &[u8]) -> Result<String, Error>;

Une méthode parse_identity, qui prend en paramètre une suite finie d'octets &[u8], et qui renvoie : * le nom, en tant que String, si le parsing réussit, * ou une Error descriptive si l'opération échoue.

On peut implémenter cette méthode de façon naïve :

fn parse_identity(input: &[u8]) -> Result<String, Error> {
    /// Consomme le premier octet, et vérifie que la valeur est 0x0. Sinon, renvoie une erreur.
    if let Err(error) = parse_opcode(input[0]) {
        return Err(error);
    }
    /// Puis consomme les deux prochains octets, et stock la longueur. Sinon, renvoie une erreur.
    let length = match parse_length(input[1..3]) {
        Ok(length) => length,
        Err(error) => return Err(error),
    }
    /// Puis consomme "length" octets si disponibles, et renvoie la chaîne. Sinon, renvoie une erreur.
    if 3 + length < input.len() {
        parse_name(input[3..3 + length])
    } else {
        Err(Error::new())
    }
}

fn parse_opcode(input: u8) -> Result<(), Error>;
fn parse_length(input: &[u8]) -> Result<u16, Error>;
fn parse_name(input: &[u8]) -> Result<String, Error>;

En regardant ce code, on peut visualiser un modèle de construction qui se répète : notre fonction parse_identity combine d'autres fonctions parse_x, chacune consommant petit à petit des données, et renvoyant soit une donnée construite, soit une erreur.

Ce modèle est la base d'une technique de parsing qui s'appelle le Combinatory Parsing.

Implémenter le parser avec Nom

Maintenant qu'on a une idée du fonctionnement global, on va pouvoir s'intéresser à une implémentation concrète en utilisant la bibliothèque Nom, ici en version 5.1.2.

Nom va fournir un ensemble complet de fonctions utilitaires, combinables, pour définir notre propre parser. La plus grosse difficulté consiste à connaître les parsers disponibles et savoir quand les utiliser.

On va commencer par recréer le prototype de notre fonction parse_identity, en utilisant Nom :

use nom::IResult;

fn parse_identity(input: &[u8]) -> IResult<&[u8], String>; 

IResult est un type définissant le résultat d'un parser Nom. Il a 3 paramètres : l'input restant (ici &[u8]), l'output généré (ici String), et optionnellement l'erreur lancée (ici implicitement (&[u8], nom::error::ErrorKind)).

Puis, on va implémenter une première partie, la récupération de l'opcode :

use nom::{Err, IResult};
use nom::bytes::complete::tag;
use nom::error::ErrorKind;

fn parse_identity(input: &[u8]) -> IResult<&[u8], String> {
    /// On crée un parser qui reprend le parser "tag" fourni par Nom.
    let parse_opcode = tag([0]);

    /// On exécute le parser, et on récupère l'input restant.
    let (input, _) = parse_opcode(input)?;

    /// On renvoie l'input restant et une erreur.
    Err(Err::Error((input, ErrorKind::Tag)))    
}

Le principe du parser tag est d'essayer de consommer la suite d'octets donnée, ou de renvoyer une erreur.

Ensuite, on veut implémenter notre parse_length et notre parse_name :

use nom::{Err, IResult};
use nom::bytes::complete::{tag, take};
use nom::combinator::flat_map;
use nom::error::ErrorKind;
use nom::number::complete::be_u16;

fn parse_identity(input: &[u8]) -> IResult<&[u8], String> {
    let parse_opcode = tag([0]);
    let parse_name = flat_map(be_u16, take);

    let (input, _) = parse_opcode(input)?;
    let (input, name): (_, &[u8]) = parse_name(input)?;

    match String::from_utf8(name.to_vec()) {
        Ok(name) => Ok((input, name)),
        Err(_) => Err(Err::Error((input, ErrorKind::Tag)))
    }
}

On utilise ici une combinaison de be_u16 pour récupérer la longueur sur deux octets en Big Endian, de take pour récupérer une quantité précise d'octets, et de flat_map pour automatiquement passer le résultat du premier parser en paramètre du second. On essaye de convertir le contenu de name en chaîne de caractère UTF-8 (ça peut échouer si la séquence d'octets n'est pas une séquence UTF-8 valide), et la fonction est désormais correctement implémentée !

Cela dit, on va faire encore une petite révision. Actuellement, le parser utilise un style séquentiel. On appelle manuellement d'abord parse_opcode, puis si ça ne renvoie pas d'erreur, on jette le résultat et on appelle parse_name. Il y a une fonction Nom pour ça :

use nom::{Err, IResult};
use nom::bytes::complete::{tag, take};
use nom::combinator::flat_map;
use nom::error::ErrorKind;
use nom::number::complete::be_u16;
use nom::sequence::preceded;

fn parse_identity(input: &[u8]) -> IResult<&[u8], String> {
    let parse_opcode = tag([0]);
    let parse_name = flat_map(be_u16, take);
    let parse_identity = preceded(parse_opcode, parse_name);

    let (input, name) = parse_identity(input)?;

    match String::from_utf8(name.to_vec()) {
        Ok(name) => Ok((input, name)),
        Err(_) => Err(Err::Error((input, ErrorKind::Tag)))
    }
}

Le parser preceded prend en paramètre deux parsers, qu'il appliquera séquenciellement, et ne renverra que le résultat du second. On se retrouve maintenant avec une seule méga-combinaison pour notre parser (on a juste la conversion en chaîne de caractère UTF-8 qui est une étape séparée).

Installer Nom

Pour installer Nom dans un projet rust, il suffit d'ajouter la dépendance nom = "5.1.2" (version actuelle) au fichier cargo.toml.

Un dernier point : Nom est totalement compatible avec des tests unitaires Rust. On peut ajouter au fichier créé auparavant un module test derrière un flag de compilation #[cfg(test)] (qui excluera des binaires compilés ce module), contenant directement le test unitaire. Ce qui donne un fichier final avec :

use nom::{Err, IResult};
use nom::bytes::complete::{tag, take};
use nom::combinator::flat_map;
use nom::error::ErrorKind;
use nom::number::complete::be_u16;
use nom::sequence::preceded;

fn parse_identity(input: &[u8]) -> IResult<&[u8], String> {
    let parse_opcode = tag([0]);
    let parse_name = flat_map(be_u16, take);
    let parse_identity = preceded(parse_opcode, parse_name);

    let (input, name) = parse_identity(input)?;

    match String::from_utf8(name.to_vec()) {
        Ok(name) => Ok((input, name)),
        Err(_) => Err(Err::Error((input, ErrorKind::Tag)))
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_parse_identity() {
        let data = vec![0, 0, 2, 49, 49];
        assert_eq!(
            parse_identity(&data),
            Ok((
                &data[5..5],
                String::from("11")
            ))
        )
    }
}

Comme tous les tests en Rust, ils sont exécutables en utilisant la commande cargo test. C'est vivement recommandé d'associer des tests à chacun des parsers conçus : c'est rapide à relancer et pratique pour du TDD.

Liens

 
En savoir plus...

from Emeric

Objectifs : Réfléchir à la gestion des dépendances dans un projet, et résoudre le problème de la dépendance incontrôlée sur Composer. Thèmes : Développement, PHP, Dépendances, Composer, Déploiement.

Dependency manager

L'utilisation de dépendances dans une stack logicielle est soumise à une règle d'or :

Pour chaque dépendance, mettre en place un contrôle des versions autorisées, en fonction de la confiance accordée à son mainteneur.

Dans l'univers JavaScript, on utiliserait Yarn, qui permet via un package.json de décrire un schéma des versions de dépendances acceptées.

Côté PHP, on a pris l'habitude d'utiliser Composer depuis quelques années pour gérer nos dépendances. Et ça donne :

{
    "require": {
        "monolog/monolog": "1.0.*"
    }
}

Exemple tiré de la documentation officielle de Composer

Dans cet exemple, on intègre le package monolog/monolog à notre projet, en explicitant la contrainte : version 1.0, ou tout patch sur cette version mineure.

Semantic versioning

Tout ça peut fonctionner car basé sur Semantic Versioning, une RFC qui définit un ensemble de règles pour la numérotation des versions (qui crée donc une cohésion sur la signification des numéros de version).

Dans l'exemple précédent, c'est Semantic Versioning qui nous permet de traduire la numérotation 1.0.* en version 1.0, ou tout patch sur cette version mineure.

Ensuite, c'est la confiance qu'on a envers les mainteneurs de la dépendance monolog/monolog qui nous permet d'être souple et d'accepter tous les patchs qui pourraient être publiés.

Dans le cas où l'on intègre la dépendance d'un mainteneur auquel on accorde moins de confiance, on peut remplacer par "monolog/monolog": "1.0.8", afin de laisser la place au moins de risque possible (la version choisie ayant été testée directement lors des développements, et étant donc fonctionnelle à un moment T).

Verrouillage

Le dernier mécanisme — nécessaire — apporté par les Dependency Manager modernes est le mécanisme de verrouillage des dépendances. Il permet de verrouiller les versions des dépendances de manière stricte lors de phases de release (jusque là, l'application des contraintes de version se fait lors des phases de développement).

Dans le cas de Composer, ce mécanisme est implémenté via le fichier composer.lock :

{
    "_readme": [
        "This file locks the dependencies of your project to a known state",
        "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
        "This file is @generated automatically"
    ],
    "content-hash": "2bdb78875a3c225905ed078c9d9140f6",
    "packages": [
        {
            "name": "psr/cache",
            "version": "1.0.1",
            "source": {
                "type": "git",
                "url": "https://github.com/php-fig/cache.git",
                "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8",
                "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8",
                "shasum": ""
            },
            ...
        },
        ...
     ]
}

On voit que ce fichier (qui doit faire partie des sources à release) fixe la dépendance psr/cache à la référence de commit précise d11b50. On maîtrise donc entièrement quelle version de notre dépendance va être utilisée lors d'un déploiement.

Recommandation officielle

#!/bin/sh

EXPECTED_CHECKSUM="$(wget -q -O - https://composer.github.io/installer.sig)"
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
ACTUAL_CHECKSUM="$(php -r "echo hash_file('sha384', 'composer-setup.php');")"

if [ "$EXPECTED_CHECKSUM" != "$ACTUAL_CHECKSUM" ]
then
    >&2 echo 'ERROR: Invalid installer checksum'
    rm composer-setup.php
    exit 1
fi

php composer-setup.php --quiet
RESULT=$?
rm composer-setup.php
exit $RESULT

Script d'installation automatique proposé par la documentation officielle de Composer

Composer propose un utilitaire d'installation, signé, sous la forme du script PHP composer-setup.php. La documentation indique qu'on peut choisir la version de l'utilitaire à utiliser. Cependant, il n'est pas indiqué qu'on DEVRAIT choisir la version de Composer à installer, et comment faire.

Cordonnier mal chaussé

Fin 2018, Composer sortait sa version 1.7.3 (un patch sur une version mineur, donc). Le script d'installation ci-dessus étant massivement utilisé, fleurirent sur Twitter et Github une pluie de commentaires de type :

Yesterday composer install works fine. Without any changes to composer.json & composer.lock today it fails

https://github.com/composer/composer/issues/7764 https://github.com/composer/composer/issues/7974 https://github.com/composer/composer/issues/7813

Et des réponses des mainteneurs du type :

This isn't a bug, it was a bug before which was fixed.

Avec l'arrivée de Composer 2 dans les prochains mois, les scripts de déploiement en production des applications PHP risquent de se mettre à planter. On commence à voir apparaître dans les logs de déploiement des Deprecation Notices qui n'existaient pas quelques mois auparavant.

Être strict AUSSI avec Composer

Les mainteneurs de Composer ont fait un travail admirable sur la stabilité du projet, et ont permis que puisse être utilisée quasiment n'importe quelle version de l'outil sans causer de problème. La confiance s'est donc installée, et la sécurité énormément relâchée. Afin de passer correctement le cap des prochains mois, il sera sans doute judicieux d'être à nouveau strict avec le Composer souhaité sur chaque projet.

Le script d'installation automatique pour Linux propose (voir les sources sur Github, bien que cela ne semble pas documenté) plusieurs options, dont --version qui nous intéresse particulièrement :

Composer Installer
------------------
Options
--help               this help
--check              for checking environment only
--force              forces the installation
--ansi               force ANSI color output
--no-ansi            disable ANSI color output
--quiet              do not output unimportant messages
--install-dir="..."  accepts a target installation directory
--preview            install the latest version from the preview (alpha/beta/rc) channel instead of stable
--snapshot           install the latest version from the snapshot (dev builds) channel instead of stable
--version="..."      accepts a specific version to install instead of the latest
--filename="..."     accepts a target filename (default: composer.phar)
--disable-tls        disable SSL/TLS security for file downloads
--cafile="..."       accepts a path to a Certificate Authority (CA) certificate file for SSL/TLS verification

L'option --version semble accepter en paramètre n'importe quel tag disponible au niveau du dépôt git Composer. Pour installer une version précise de Composer, on peut donc remplacer dans notre script :

php composer-setup.php --quiet --version="1.10.6"

Il ne semble malheureusement pas y avoir de possibilité d'utiliser une contrainte plus souple de version (de type 1.10.* ou ^1.10) à ce jour.

Liens

 
En savoir plus...

from Emeric

Objectif : Découvrir quelques astuces pour mieux gérer l'environnement de développement de son équipe. Thèmes : Développement, Docker-Compose, Makefile, PHP.

Docker-Compose

On débute avec seulement notre projet PHP. La première chose à faire, c'est de mettre en place l'outil qui nous permettra de créer, de recréer, à volonté, la stack qui est utilisée pour développer sur le logiciel.

On va vouloir définir l'état actif normal de notre stack de développement. Et uniquement cet état. Quelles parties du logiciel doivent tourner obligatoirement ? Lesquelles sont à contrario temporaires ? Cet état actif normal, on va vouloir l'écrire dans un fichier, et laisser notre outil gérer lui même les actions à effectuer pour l'atteindre.

Pour ça, il y a Docker-Compose. C'est un ensemble de commandes qui vont encapsuler l'accès à Docker – qu'on utilise, lui, pour gérer toutes les problématiques de portabilité –. On écrit dans un fichier descriptif, conventionnellement docker-compose.yml, l'état de la stack qu'on souhaitera faire tourner via Docker.

Un exemple de docker-compose.yml, utilisé pour faire tourner l'infrastructure de développement en local de green.vanoix.com :

version: '3.7'

services:
    application:
        build: ./docker/build/application
        volumes:
            - .:/var/www/app
        ports:
            - "80:80"
        networks:
            - backend
        secrets:
            -
                source: ssh_key
                target: /root/.ssh/id_rsa
    database:
        image: mariadb:latest
        volumes:
            - ./docker/data/database:/var/lib/mysql
        environment:
            - 'MYSQL_ALLOW_EMPTY_PASSWORD=yes'
            - 'MYSQL_DATABASE=application'
        networks:
            - backend

secrets:
    ssh_key:
        file: ~/.ssh/id_rsa

networks:
    backend: ~

On décrit : – D'abord les services qu'on va vouloir faire tourner, les volumes qui leur seront rattachés, etc. – Puis les secrets qu'on va vouloir utiliser. Qu'on va ensuite ajouter à chaque service où c'est nécessaire. – Enfin les réseaux qui devront être créés. Qu'on renseigne, de la même manière, au niveau des services qui les utiliseront.

Makefile

Maintenant que notre cible est définie, on va vouloir la rendre accessible, rapidement. D'une part, on cherche à diminuer la difficulté à mettre en route cette stack. D'une autre, on cherche à diminuer le temps pour ce faire.

Il va donc falloir réduire au maximum le nombre d'étapes, et automatiser chacune le plus possible.

Pour sa portabilité, le fait qu'il soit un standard, et qu'il permet de gérer tout ce qu'un shell peut gérer – donc, tout –, on va utiliser make. De manière assez conventionnelle, la règle principale par défaut est souvent nommée install. On retrouve aussi la règle run (ou start), qui permet de lancer le logiciel. Souvent la règle clean pour nettoyer l'environnement local. Et parfois, stop dans le cas d'un service en arrière-plan.

Dans notre cas, un fichier Makefile contenant ceci devrait suffir. Moins il y a de règles, plus le projet est simple à aborder :

.PHONY: start stop

start:
    docker-compose build
    docker-compose run application composer install
    docker-compose up -d

stop:
    docker-compose down

Rappel sur le .PHONY : chaque règle (ou cible) est normalement associée au fichier du même nom. Pour décorréler les deux, on renseigne la règle dans l'énumération .PHONY.

Makefile et Docker-Compose

Le principal problème avec ce que l'on a fait, c'est qu'on va essayer de rebuild les containers et réinstaller les dépendances Composer, à chaque lancement du projet. On perd beaucoup de temps. D'un autre côté, si on souhaite seulement réinstaller les containers, on démarre aussi l'application. On perd beaucoup en ressources. Cela traduit un problème de conception, sur les différents cas d'utilisation de nos automatismes.

Pour corriger cela, on va définir les états actifs normaux de l'environnement de développement. Puis les décrire, dans un fichier, sous forme de règles idempotentes. Puis – comme avec docker-compose – on considérera que le travail de notre outil n'est que de mettre le système dans l'état souhaité.

Ici, on définit trois états pour un environnement local : – l'état installé, lorsque les containers sont construits et les dépendances logicielles installées, – l'état démarré, lorsque le logiciel a été lancé, – et l'état stoppé, lorsque le logiciel a été arrêté.

make et docker se combinent à merveille, afin de définir les règles de passage à un état spécifique. Voici l'exemple du Makefile utilisé pour le projet green.vanoix.com :

.PHONY: install start stop

install:
	docker-compose build
	docker-compose run --no-deps --rm application composer install

start:
	docker-compose up -d

stop:
	docker-compose down

On définit une règle par état ciblé, et une série d'action pour chaque règle. Sur un plan plus technique, l'astuce consiste à profiter de la capacité de Docker-Compose à éxécuter seul (run --no-deps) un container temporaire (--rm), basé sur un service à l'arrêt (application), pour installer les dépendances PHP sur le système de fichiers (composer install).

README

Dans le cas d'applications plus complexes, il reste encore une chose à paufiner : la gestion du contexte. Souvent, on va considérer que le développeur devra avoir tel prérequis installé sur sa machine. Tel service lancé en fond. Telle configuration SSH. Tel dossier à tel emplacement. Telle connaissance acquise.

On va vouloir gérer tous ces cas particuliers. Mais sans sacrifier les performances et la simplicité de notre système. On peut encadrer notre environnement de développement en utilisant des outils de communication.

Le fichier README.md est classique et efficace, et un excellent liant avec tous les autres outils. Un exemple de template minimal qui permet de gérer la configuration du host et clef SSH sur un projet :

## Dev Environment

This project uses Docker (docker-compose version >=3.5) to manage the local development environment.

### Requirements

#### Hosts Configuration

You must have this entry in your `/etc/hosts` file:

```
127.0.0.1 myproject.localhost
```

#### SSH key forwarding

The project uses Docker Secrets to handle the SSH key forwarding to your container (mainly used to access private repositories).

Your local SSH key must be located at `~/.ssh/id_rsa`.

### Installation

```
make install && make start
```

The project should be now successfully running.

Liens

 
En savoir plus...

from Jérémy James

La naissance du projet

Octobre 2019, Vanoix est sponsor du Forum PHP 2019 organisé par l'AFUP. Comme tout bon sponsor, nous avons un budget alloué à l'événement et nous devons trouver une idée de goodies pour les 700 participants prévus.

À ce moment là, je viens tout juste d'être recruté chez Vanoix et j'ai envie de faire les choses un peu différemment de ce qu'il se fait d'habitude pour ce genre d'événement. Les stylos/carnets/stickers, c'est sympa, mais notre belle planète en a un peu marre de tout ce plastique. Nous décidons donc de partir sur quelque chose d'utile, sans plastique et dont l'impact écologique sera le plus faible possible.

Après de nombreuses discussions en interne avec Alexandre et Emeric, la décision est prise. Chaque participant aura son petit pot en fibre de bambou recyclable, une plaquette de tourbe et un sachet de graines. Chouette idée, n'est-ce pas ?

La logistique

Chouette idée, je vous le confirme. En revanche, niveau logistique, ce n'a pas été de tout repos.

Le postulat de base étant de réduire au maximum les coûts et l'impact écologique, nous avons voulu tout faire à la main. Je vous laisse imaginer la complexité de produire 700 pots, avec chacun de la terre et des graines. Nous nous sommes donc tournés vers un fournisseur d'objets publicitaires écologiques français, qui nous a fourni les 700 kits de plantation.

Une fois les kits reçus et après un samedi après-midi éprouvant à coller des stickers et des QR code sur les pots, un par un, nous sommes fin prêts pour le Forum PHP.

Un dernier obstacle se glisse sur notre chemin : mais comment donc allons-nous transporter tout ça ? Après moult discussions, toute l'équipe de Vanoix montera à Paris à bord d'une voiture de location, le coffre rempli à ras bord de pots de fleurs.

Un QR code ?

Pour les curieux qui ont scannés le QR code, vous tombez directement sur l'application green.vanoix.com.

Le principe étant de mettre en place un suivi très simple des plantes. Vous avez la possibilité de déclarer votre plante, de renseigner ou pas votre compte Twitter et le compte Twitter de votre entreprise si vous le souhaitez. Il est tout à fait possible de rester anonyme, mais de déclarer quand même sa plante. Il est aussi possible et même fortement recommandé, de mettre en ligne des photos et de partager le profil publique de votre plante sur les réseaux sociaux ! Spread the love! 💚

Petite information supplémentaire, si vous avez renseigné au moins un compte Twitter, nous partagerons vos plus belles photos avec le compte Twitter Vanoix en vous mentionnant.

Enfin, aucun suivi de vos données n'est en place sur le site. Nous avons fait le maximum pour être respectueux du RGPD et le site se veut le moins gourmand en ressources possible !

Le jour J

Les participants ont été très agréablement surpris de voir cette petite plante dans leur sac de goodies. Nous avons eu énormément de retours positifs, que ce soit sur Twitter, à notre antenne au Canada ou sur place au Conference Center du Marriott Rive Gauche à Paris. J'en profite pour vous dire merci à tous pour l'amour et la bienveillance dont vous avez fait part ! 💚

À l'heure où j'écris ces lignes, peu de plantes ont été déclarées. Une vingtaine sur 700 pots distribués. Je suppose que : – Les plus à l'aise avec les plantes attendent le bon moment pour planter leur tournesol ou leur capucine. – Nous avons trop peu communiqué sur ce projet, ou pas de la bonne manière. – Le format QR code ne convient pas (illisible ? technologie obsolète ?)

Dans tous les cas, nous sommes fiers d'avoir pu vous proposer cette aventure. Même si certains ne joueront pas le jeu de l'application, la plante sera quand même sur leur bureau. Et ça, ça nous fait chaud au cœur !

Fun facts

L'imprimante L'imprimante des QR code a sorti une page blanche au milieu des 24 pages non numérotées. Quelle joie immense de devoir comparer à l'oeil nu des planches de QR codes en 25x25mm. Merci à Emeric pour ses yeux laser et son sens du pixel aigu !

Au dernier moment L'application a été finalisée la veille du Forum à 1h du matin 🤯

Un déploiement en production a été fait en urgence le matin même, à 9h00. Pourquoi cela ? Parce que je me suis rendu compte en scannant le QR code du pot situé dans mon sac de goodies, que le serveur bloquait les uploads de plus de 2 méga octets. Impossible donc de mettre en ligne une photo prise avec un téléphone récent. Le coupable ? Le proxy Nginx mis en place en urgence la veille pour tenir la charge.

Tenir la charge ? Naïvement, nous avons cru que tous les participants allaient scanner leur QR code en même temps, par curiosité et qu'un pic de trafic allait faire tomber le serveur. Nous avons donc décidé de passer les 5h de trajet aller dans la voiture, la veille, pour mettre en place des optimisations, un système de cache, etc.

Le pic de trafic n'a jamais eu lieu 😭

Voici un petit aperçu des ressources consommées par le serveur sur les 7 jours qui ont suivi le forum PHP. On est large !

Bonnes pratiques Nous voulions avoir une application robuste, disponible, qui consomme peu de ressources et reste utilisable en mode partiellement dégradé. Nous avons donc suivi, au maximum, les bonnes pratiques de Defensive programming.

D'un autre côté, cela m'a permis de me remettre dans le bain de PHP/Symfony après 2 ans de Python. Merci Emeric pour tous les conseils et les reviews de code.

Le mot de la fin

Si le projet vous a plu, n'hésitez pas à nous le faire savoir. Il est possible que ce système de plante à scanner soit de nouveau présent dans les futurs événements que Vanoix sponsorisera.

N'attendez donc pas pour scanner votre plante 🌱 ! Encore merci à tous !

 
En savoir plus...