Comment fonctionne la création d’alias de service dans Symfony

Quand j’ai commencé à développer sur Symfony, il y a quelques années, je voulais tester la librairie flysystem-bundle pour arriver faire du stockage de fichier via une application Symfony. Je ne connaissais pas encore vraiment bien les notions d’alias, et il y avait un point dans l’utilisation de la librairie qui me laissais perplexe. Dans le tutoriel d’installation, il était recommandé de récupérer l’object qui gère le stockage via le code suivant :

public function __construct(FilesystemOperator $defaultStorage)

Je n’arrivais pas à comprendre comment Symfony savait que $defaultStorage faisait référence à la configuration définie dans le fichier de configuration.

flysystem:
    storages:
        default.storage:
            adapter: 'local'
            options:
                directory: '%kernel.project_dir%/var/storage/default'

Depuis ca me parait logique, et c’est même écrit noir sur blanc dans la documentation :

For each storage defined under flysystem.storages, an associated service is created using the name you provide (in this case, a service default.storage will be created). The bundle also creates a named alias for each of these services.

https://github.com/thephpleague/flysystem-bundle#basic-usage

Clairement, je n’avais pas lu la documentation.

Côté Flysystem

En y revenant il y a quelques jours, je me suis demandé comment fonctionne cette création d’alias. Il doit bien y avoir un moment ou Symfony transforme cette clé de configuration en un nom de variable ? La réponse se trouve dans le fichier suivant : https://github.com/thephpleague/flysystem-bundle/blob/3.x/src/DependencyInjection/FlysystemExtension.php

On peut voir qu’à la ligne 43, il crée les « définitions de stockage » :

$this->createStoragesDefinitions($config, $container);



Pour chaque clé définie dans notre fichier de configuration cité plus haut, la librairie demande au container de Symfony de créer un alias qui sera disponible dans les arguments de fonctions.

foreach ($config['storages'] as $storageName => $storageConfig) {
    // ...
    $container->registerAliasForArgument($storageName, FilesystemOperator::class, $storageName)->setPublic(false);

Côté Symfony

Cette fonction registerAliasForArgument est déclarée dans https://github.com/symfony/dependency-injection/blob/6.2/ContainerBuilder.php#L1302

public function registerAliasForArgument(string $id, string $type, string $name = null): Alias
    {
        $name = (new Target($name ?? $id))->name;

        if (!preg_match('/^[a-zA-Z_\x7f-\xff]/', $name)) {
            throw new InvalidArgumentException(sprintf('Invalid argument name "%s" for service "%s": the first character must be a letter.', $name, $id));
        }

        return $this->setAlias($type.' $'.$name, $id);
    }

On voir qu’un attribut Target est crée. En paramètre on lui donne le nom s’il est renseigné, sinon par défaut on lui donne l’id.
Target est un attribut PHP dans https://github.com/symfony/dependency-injection/blob/6.2/Attribute/Target.php ajouté dans la version 5.3 de Symfony. Voici ce qu’il fait dans son controller :

    $this->name = lcfirst(str_replace(' ', '', ucwords(preg_replace('/[^a-zA-Z0-9\x7f-\xff]++/', ' ', $name))));

On voit que tous les caractères non alphanumériques sont remplacés par des espaces afin de créer une phrase, puis ucwords, permet de capitaliser chaque mot de cette phrase, Puis cette phrase est transformée en un mot en supprimant les espace, et on finit par mettre en minuscule la permière lettre.

Pour donner un exemple concret, ca va transformer mon-nom_d?alias qUi S3r4 transform# en monNomDAliasQUiS3r4Transform.

Donc au final, la clé de configuration default.storage est transformé en defaultStorage. Ce qui nous donne bien l’argument utilisé finalement dans notre controller FilesystemOperator $defaultStorage.

Déclaration d’alias dans services.yaml

Tout ceci m’a amené à lire un peu plus la documentation de Symfony. En particulier cette page :
https://symfony.com/doc/current/service_container/autowiring.html#dealing-with-multiple-implementations-of-the-same-type
Je me suis rendu compte qu’il est possible de déclarer un alias d’un classe spécifique de cette manière :

    App\Util\TransformerInterface $shoutyTransformer: '@App\Util\UppercaseTransformer'

dans le fichier services.yml

et on voit que c’est ce qui est fait dans la fonction registerAliasForArgument du containerBuilder de Symfony

return $this->setAlias($type.' $'.$name, $id);

« Type » est le nom de la classe, suivi d’un espace, d’un dollar pour créer la variable PHP et le nom de l’alias qui vient d’être défini.

Pour finir, grâce au fichier FlysystemExtension qui vient ajouter des définitions dans le Container de Symfony, c’est comme si lors de la création du storage dans Flysystem, la librairie état venue directement inscrire dans le fichier services.yaml la définition suivante :

FilesystemOperator $defaultStorage: 'League\Flysystem\FilesystemOperator'







Commentaires

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *