Loick Piera
Développeur chez JoliCode et expert trottinette de bureau.
Les versions de Symfony 3.4 et 4.0 viennent tout juste de sortir et avec elles vient une nouvelle façon de développer des applications Symfony. Dîtes adieu à la Standard Edition et bonjour aux tout nouveaux symfony/skeleton
, Symfony Flex et les recettes qui vont avec.
D’un point de vue technique, Symfony Flex est juste un plugin Composer. Il se branche sur les événements Composer dès lors que vous lancez une commande qui installe, met à jour ou supprime un paquet PHP ou bundle Symfony. Son but ? Automatiser l’installation et la suppression de vos dépendances en fournissant une configuration par défaut sans avoir à aller lire la doc pour trouver quelle configuration écrire, quelles routes charger ou autre tâche rébarbative à effectuer. Et dès Symfony 4.0, Flex sera le moyen par défaut pour développer une application Symfony.
Il paraît qu’un bon exemple vaut mieux qu’un long discours. Alors imaginons que vous ayez une application existante en Symfony 3.3 (parce que vous êtes un dev cool et avez effectué les migrations vers les dernières versions de Symfony au fur et à mesure 💪). Vous voulez y ajouter une API en installant api-platform. Vous allez donc effectuer les tâches suivantes :
composer require api-platform
// app/AppKernel.php
public function registerBundles()
{
$bundles = [
// ...
new ApiPlatform\Core\Bridge\Symfony\Bundle\ApiPlatformBundle(),
];
// ...
}
# app/config/routing.yml
api:
resource: '.'
type: 'api_platform'
prefix: '/api' # Optional
config.yml
Rien d’insurmontable me direz-vous. Mais rappelez-vous qu’il est nécessaire de faire ces manipulations pour chaque bundle que vous ajoutez dans votre application (sans parler des autres tâches comme créer des entités Doctrine requises par certains bundles, lancer des commandes CLI, etc). C’est encore pire si nous tombons sur un bundle qui aurait des dépendances sur plusieurs autres bundles (coucou Sonata 👋). Et c’est tout de suite moins sympathique. 😬
Imaginons maintenant le futur. Vous êtes désormais l’heureux propriétaire d’une application en Symfony 4. Et surtout, vous rêvez toujours d’installer une API REST (puisque sans ça, mon exemple n’aurait plus beaucoup d’intérêt, avouons-le). Vous lancez donc la commande suivante :
composer require api
Et c’est tout. Oui, vous avez bien lu, c’est tout. Flex va se charger lui-même d’enregistrer le bundle dans le kernel de Symfony, de fournir une configuration par défaut, de charger les routes nécessaires, etc. Une seule commande Composer vous aura suffi pour mettre en place une API REST fonctionnelle et sa documentation swagger/openapi.
Au cœur de tout ce système d’automatisation se trouvent les recipes - ou recettes dans la langue de notre bon vieux Molière. Les recettes, hébergées sur un dépôt open source disponibles ici pour les officielles (comprendre celles approuvées et maintenues par la core team) et ici pour celles de la communauté, contiennent des instructions pour indiquer à Flex ce qu’il doit faire pour chaque paquet.
Ainsi, une recette définit plusieurs informations, la première étant les alias.
Vous vous souvenez de l’exemple précédent ?
composer require api
Le package api
n’existe pas pour Composer. Mais Flex intervient pour détecter si le nom de package demandé correspond à un alias, et si c’est le cas, fait en sorte que Composer installe le paquet correspondant.
Il existe déjà tout un tas d’alias (admin
, log
, orm
, etc) et ils seront forcément non objectifs puisque que définis par la core team (les alias sont uniquement disponibles pour les recettes officielles). Là encore, cela simplifie un peu le développement Symfony en mettant plus en avant des packages de qualité, testés et maintenus.
Mais ce qui fait réellement l’intérêt des recettes, c’est la partie configurator
.
Il existe actuellement 8 types de configurateurs disponibles pour les recettes Flex. Chaque type permet au plugin de configurer une partie de votre application. En voici la liste :
bundles
permet de connaître les bundles à instancier suivant l’environnement ;container
donne les paramètres à ajouter dans votre DIC ;copy-from-package
indique les fichiers à copier du paquet Composer vers votre application Symfony ;copy-from-recipe
indique les fichiers à copier de la recette vers votre application Symfony ;env
donne les variables d’environnement à ajouter dans les fichiers .env.dist
et .env
de l’application ;composer-scripts
liste les commandes à ajouter dans la section scripts
de votre composer.json
pour qu’elles soient lancées à la fin des commandes install
et update
;gitignore
donne les entrées à ajouter à votre .gitignore
;post-install-output
permet d’afficher du texte lorsque l’installation par Composer se termine.Flex est suffisamment intelligent pour également supprimer ce qui a été ajouté par une recette lorsque vous supprimez le paquet associé.
Pour terminer sur le fonctionnement interne de Flex, prenons rapidement l’exemple du manifest.json
présent dans la recette du BazingaJsTranslationBundle (bundle permettant d’exposer les traductions du site à votre JavaScript) :
{
"bundles": {
"Bazinga\\Bundle\\JsTranslationBundle\\BazingaJsTranslationBundle": ["all"]
},
"copy-from-recipe": {
"config/": "%CONFIG_DIR%/"
},
"aliases": ["js-translation", "js-translator"]
}
Nous constatons qu’il existe 2 alias disponibles pour ce bundle, qu’il y a un bundle à instancier (pour tous les environnements, pas uniquement prod
ou dev
par exemple) et qu’il faut copier le contenu du dossier config/
provenant du recipe dans le dossier de configuration de votre application Symfony.
Pour plus d’informations sur les recettes, n’hésitez pas à aller lire le README très complet du dépôt symfony/recipes.
Vous en savez maintenant un peu plus sur Symfony Flex (et son fonctionnement interne). Vous êtes convaincu de ses bienfaits ? Alors regardons comment migrer votre application.
Avant de démarrer, il est à noter que Symfony Flex requiert notamment 2 choses. La première, c’est PHP 7 : les premières versions de Flex étaient même compatibles uniquement PHP 7.1+ mais la dépendance a été abaissée à PHP 7.0 récemment afin de faciliter la migration pour un maximum de gens.
La deuxième contrainte pour utiliser Flex est d’utiliser la structure d’application qui sera celle de Symfony 4.0. En effet, tout comme Symfony 3 en son temps, Symfony 4 apporte quelques changements dans l’arborescence par défaut :
app/AppKernel.php
vers src/Kernel.php
;app/config/
est déplacé à la racine config/
(les paramètres spécifiques à votre infrastructure pourront d’ailleurs être définis en variables d’environnement, dans un .env
pour les environnements de développement) ;assets/
et translations/
dans app/Resources/
sont également déplacés à la racine ;app/Resources/views/
est déplacé à la racine et est renommé templates/
;src/{App, ...}Bundle/
est déplacé directement dans src/
;web/
devient public/
et nous nous retrouvons avec un unique contrôleur frontal index.php
à la place des anciens app_dev.php
et app.php
. Désormais, l’environnement sera piloté uniquement par une variable d’environnement APP_ENV
;.yaml
au lieu de .yml
.Nous pouvons noter dans ces changements une réelle volonté de se rapprocher des standards du marché, employés par les autres frameworks et langages. Et ce n’est pas pour nous déplaire. ✨
Flex fonctionnait dès Symfony 3.3, mais en version alpha. Cela a permis à de nombreuses personnes de tester Flex au plus tôt. Plein de suggestions ont ainsi pu être apportées par la communauté et la core team en a profité pour faire plusieurs ajustements. Désormais, Symfony Flex est considéré comme stable depuis Symfony 3.4 et 4.0.
Malheureusement, il n’existe pas d’outils pour automatiser la migration vers Symfony Flex. Et non, il ne suffit pas simplement de lancer un composer require symfony/flex
. Non, le plus simple est de démarrer une nouvelle application à côté et de rapatrier votre code et configuration de l’ancienne vers la nouvelle application. C’est ce que nous allons voir dans la suite, étape par étape. Cette migration permettra d’ailleurs de mieux comprendre les choix techniques qui ont conduit à tous ces changements dans l’arborescence.
Comme je l’ai dit, pour simplifier la migration, nous devrons créer une toute nouvelle application. Vous avez le choix entre démarrer en Symfony 3.4 ou 4.0. Gardez à l’esprit que la nouvelle version majeure est encore jeune et tout l’éco-système n’est pas encore compatible, ce qui risque de vous poser problème. Dans le doute, nous allons démarrer en 3.4 pour profiter de la longévité de cette version LTS.
Symfony Flex rend l’installateur officiel obsolète. En effet, nous retrouvons à nouveau une initialisation classique via Composer :
composer create-project symfony/skeleton:3.4 ma-nouvelle-app
Si vous regardez le repository du squelette, vous constaterez qu’il s’agit d’un unique composer.json
avec des dépendances notamment sur symfony/flex
, symfony/framework-bundle
. Oui, tout ce que vous trouvez d’autre dans ma-nouvelle-app/
a été généré par Flex : la console dans bin/
, le public/index.php
, le kernel dans src/
ou encore toute la configuration dans config/
. Nous reviendrons notamment sur le contenu de dossier de config/
dans la suite.
Après avoir initialisé votre toute nouvelle application, vous allez pouvoir rapatrier vos anciennes dépendances. Pour ça, récupérez la liste des packages présents dans les sections require
et require-dev
de votre ancien composer.json
et copiez les dans votre nouveau composer.json
.
On lance l’installation avec la commande suivante :
composer update
Par défaut, le squelette est configuré pour ne pas autoriser les recettes qui viennent du dépôt contrib
. Si c’est le cas pour un ou plusieurs de vos packets, Flex va vous demander interactivement ce que vous voulez faire :
Libre à vous de toujours accepter une recette contribuée par la communauté ou d’accepter au cas par cas. Il n’y a pas énormément de risques de les accepter les yeux fermés mais il faut tout de même garder à l’esprit que ces recettes ne sont pas nécessairement validées par des membres de la core team.
Il y a des risques que la commande Composer échoue au moment où elle lance le script qui va nettoyer le cache (la commande cache:clear
de Symfony). En effet, certains bundles de vos dépendances n’ont peut-être pas encore de recettes Flex pour générer une configuration par défaut. Que ce soit le cas ou pas, pas d’inquiétude, nous nous en occupons dans la suite.
Attention ! Il ne faut pas oublier que Flex apporte une nouvelle façon de penser le développement Symfony. Puisque nous n’avons plus de dépendance sur le framework full-stack (c’est-à-dire le paquet symfony/symfony), nous n’avons plus accès aux logs Monolog, la fonction dump()
du composant VarDumper ou encore au web profiler. Il va donc va falloir ajouter à nouveau ces outils bien pratiques lors du développement. Heureusement, Symfony nous propose des pack, c’est-à-dire des méta-packages Composer qui nous permettent d’installer tous ces outils rapidement :
composer require symfony/profiler-pack symfony/debug-pack
Et c’est la même chose si votre application utilise des formulaires, de la traduction ou toute autre fonctionnalité de Symfony qui ne serait plus incluse par défaut. 😉
Il est temps de nous intéresser à la partie configuration de vos dépendances et de tout ce qui se trouve dans le dossier config/
. Dans ce dernier, nous allons avoir le contenu suivant :
bundles.php
packages/
routes/
routes.yaml
services.yaml
bundles.php
contient la liste des bundles. Mis à jour par Flex évidemment, ce fichier PHP sera importé par notre tout nouveau Kernel pour lire les bundles à instancier pour l’environnement actuel.
packages/
et routes/
contiennent respectivement la configuration de vos dépendances et leurs routing à importer. Ils fonctionnent de la même manière : chaque fichier dedans concerne un seul paquet. Plus simple à comprendre et moins fourre-tout que ce que nous avions précédemment dans nos app/config/config.yml
. En bonus, tous les fichiers .php
, .yaml
et .xml
présents dans ces dossiers seront automatiquement importés, c’est-à-dire que vous n’avez plus à définir manuellement les import
pour chacun.
Par exemple, la configuration du FrameworkBundle (sous la clef framework
dans le app/config/config.yml
) trouvera sa place dans config/packages/framework.yaml
. De même pour le TwigBundle (clef twig
) dans config/packages/twig.yaml
. Vous aviez de la configuration ou du routing spécifique à un environnement (comme dans app/config/config_dev.yml
par exemple) ? Pas de soucis, si dans les dossiers config/{packages ou routes}/
se trouve un dossier du nom d’un environnement (par exemple config/packages/dev/
, alors son contenu ne sera chargé que pour l’environnement associé. Malin !
routes.yaml
contiendra les routes de votre application. Mis à part le nom du fichier (anciennement routing.yml
) qui change, il ne devrait pas y avoir de gros changements.
services.yaml
contient la définition des services et paramètres de votre application. Grâce à l’autowiring et l’autoconfigure, ce fichier risque de ne pas grossir très vite. Je vous laisse relire l’article de Nicolas Grekas pour ce calendrier de l’Avent à ce sujet. Pour avoir déjà développé/migré plusieurs applications avec Flex, je vous assure que ne plus avoir à enregistrer ses services est devenu un vrai confort de développement, sans aucune contrepartie.
Étant donné que la plupart de la configuration est générée par Flex et ses recettes, il ne faut pas hésiter à la vérifier soi-même pour s’assurer qu’elle est adaptée à votre application. La migration de configuration, en plus d’être la partie la moins agréable et la plus longue, est probablement celle où le risque d’erreurs est le plus important. Soyez vigilant.
À ce stade, il ne devrait vous rester que les paramètres à migrer pour en terminer avec la configuration. Dans la nouvelle arborescence, Symfony ne nous propose plus de fichiers parameters.yml
(et son parameters.yml.dist
associé) comme nous en avions l’habitude. Les paramètres propres à votre infrastructure (comme les accès à votre base de données, au serveur de mail) ou les paramètres qui doivent pouvoir changer sans réinitialiser le cache (comme une clef d’API) pourront être définis en variable d’environnement (dans le .env
en local et en véritable variable d’environnement en production). Pour rappel, voici un exemple pour référencer une variable d’environnement en YAML :
services:
App\\MyService:
arguments:
$myParameter: '%env(MY_ENV_VAR)%'
Pour les autres paramètres qui dépendent uniquement de votre application (comme la locale), ils peuvent être définis dans le config/services.yaml
. Si vous avez tout de même besoin de définir des paramètres non versionnés en YAML, vous êtes libres de rajouter un config/packages/parameters.yaml
que vous prendrez soin de rajouter dans le .gitignore
.
Dernière étape, vous pouvez maintenant récupérer votre code. Rappelez-vous, nous n’avons plus de bundle applicatif (bye bye le AppBundle), donc vous aurez certainement le namespace à changer dans toutes vos classes PHP. N’hésitez pas utiliser les outils de refactorisation de votre IDE pour vous facilitez la tache.
Si vous avez des modifications à faire dans le Conteneur d’Injection de Dépendances, il n’est plus obligatoire de devoir le faire dans une Extension ou une CompilerPass. N’hésitez pas à les faire directement dans le Kernel se trouvant dans src/
.
Vous allez maintenant pouvoir rapatrier le reste de votre application (templates, traductions, assets ou encore tests) en déplaçant tout ce petit monde à la racine de votre projet.
Voilà, la migration est désormais terminée. 🎉 N’hésitez pas à bien tester votre application pour vous assurer que tout est bien opérationnel.
Pour terminer, voici quelques petis tips pour vous aider dans la migration :
templates/bundles/{Nom du bundle}/{chemin/vers/le/template.html.twig}
. Ainsi, les templates d’erreurs Twig peuvent être modifiés en les surchargeant dans le dossier templates/bundles/TwigBundle/Exception/
;symfony.lock
. Ajouté il y a quelques semaines seulement dans Flex, ce fichier permet d’aider Flex à se rappeler des recettes qu’il a déjà appliquées. Ainsi, grâce au lock, si vous supprimez un fichier généré par une recette, Flex ne cherchera pas à le recréer au prochain install
/ update
;Invalid type for path "swarrot.connections.rabbitmq.port". Expected int, but got string.
, l’erreur est probablement liée à une limitation de Symfony. En effet, il n’est pas encore possible de passer des variables d’environnement à n’importe quelle configuration d’un bundle. Le problème est dû à Symfony qui ne résoud pas la valeur d’une variable d’environnement au moment de la validation de la configuration (une PR est en cours pour réparer ça). En attendant, vous devrez donc passer par un paramètre intermédiaire dans le DIC (perdant au passage un des intérêts principaux d’une variable d’environnement, à savoir la modification au runtime, sans avoir à effacer le cache) que vous injecterez dans la configuration à la place de la variable d’environnement ;app_*.php
) vers le nouveau public/index.php
si nécessaire ;Symfony Flex apporte une toute nouvelle approche pour développer une application Symfony. En plus de se rapprocher des standards industriels, cette nouvelle façon de faire simplifie l’expérience développeur (DX) en automatisant tout ce qui peut l’être.
Nous sommes forcés de prendre conscience et d’expliciter nos dépendances. En effet, nous n’utilisons plus une “distribution” classique avec tout activé par défaut. Au contraire, terminée la dépendance au framework full-stack ! Nous démarrons désormais avec une base de code ultra-minimaliste et nous allons venir ajouter nous-même les composants Symfony dont nous aurons réellement besoin. Ce changement de paradigme permettra peut-être de mettre un terme (ou pas) à la réputation de framework plus lourd et moins rapide que se traîne parfois (et souvent à tort) Symfony.
Et là où Symfony se débrouille souvent très bien d’après moi, c’est que toutes ces nouveautés et changements ne sont jamais obligatoires. Il vous sera toujours possible de développer comme vous le souhaitez, quitte à vous passer de Flex si nécessaire.