Initialisez votre projet Symfony avec des fondations solides

Publié le 11/06/2022 • Actualisé le 12/06/2022

Dans cet article, nous voyons comment initialiser un projet Symfony sur des bases solides. Je donne quelques conseils à propos de cette phase cruciale qui va déterminer la manière dont va évoluer votre projet sur le long terme ; est-ce qu'il va rester maintenable et sera-t-il agréable de travailler avec ? Ou bien va-t-il devenir le projet legacy que tout le monde essaie d'éviter ? C'est parti ! 😎


English language detected! 🇬🇧

  We noticed that your browser is using English. Do you want to read this post in this language?

Read the english version 🇬🇧 Close

» Publié dans "Une semaine Symfonique 806" (du 6 au 12 juin 2022).

Prérequis

Je supposerai que vous avez les connaissances nécessaires pour initialiser un projet avec le Symfony CLI par exemple.

Introduction

Plus je découvre et travaille sur des projets différents, plus je pense que la phase d'initialisation d'un projet est cruciale. Quand on y pense, c'est tellement logique. C'est un peu comme l'antagonisme entre écrire du code "sale et rapide" et écrire du code "propre". On peut effectivement épargner un peu au départ, mais la perte de temps peut être énorme au final. J'espère que vous trouverez quelques idées à appliquer sur vos futurs projets.

But

Nous allons passer en revue plusieurs points que je trouve essentiels. Pour chacun, je donnerai un exemple concret sur ce que j'ai appliqué sur ce projet ou d'autres. J'essaierai de mettre à jour cet article à la publication de chaque version mineur et majeure de Symfony.

Choisir la version de Symfony

Commençons par le début : la version de Symfony. Nous avons deux options. Devrions-nous utiliser la dernière LTS (Long Term Support), ou devons-nous utiliser la dernière version mineure ? On doit toujours utiliser la dernière version mineure. Le choix n'était pas si évident par le passé, même Fabien a conseillé d'utiliser la LTS, mais plus maintenant. Commencer avec la dernière version mineure a les avantages suivants :

  • La migration d'une version mineure à une autre est directe et rapide à faire
  • On peut profiter des nouvelles fonctionnalités ! 🚀

Au moment où j'écris cet article, la dernière version mineure est 6.1 ; elle a été publiée le 27 mai 2022. C'est une version mineure spéciale, car elle a un BC break assez significatif que nous allons aborder dans le chapitre suivant.

“Conclusion: Utilisez Symfony 6.1. ”

Choisir la version de PHP

Nous avons parlé de la version Symfony mais pas de celle de PHP. Elles sont étroitement liées. Symfony spécifie la version requise de PHP dans son fichier de dépendances composer. Jetons-y un coup d'œil à https://github.com/symfony/symfony/blob/6.1/composer.json :

"require": {
    "php": ">=8.1",
    "composer-runtime-api": ">=2.1",
    "ext-xml": "*",

Comme nous pouvons le voir, Symfony 6.1 requiert au moins PHP 8.1 Ce n'est pas "normal" puisque Symfony 6.0 requiert quant à lui PHP 8.0.

Si vous suivez l'article "A week of Symfony", vous avez probablement lu celui-ci : "Symfony 6.1 va requérir PHP 8.1". Veuillez lire l'article afin de bien comprendre pourquoi. D'habitude, il n'y a pas de changement de version pendant la durée de vie d'une version majeure de Symfony ; c'est une exception.

“Conclusion: Utilisez PHP 8.1. ”

Créer et lancer l'application

Nous avons plusieurs options :

Configuration sans Docker ou hybride

Ce que j'appelle une configuration "sans Docker" est que l'application n'est pas servie par Docker, mais par le Symfony CLI. Pour créer un nouveau projet, on lance :

symfony new --webapp symfony61

Et nous avons une application fonctionnelle, on peut la servir en lançant :

cd symfony61
symfony serve --daemon

Alors, l'application est disponible localement à l'adresse https://127.0.0.1:8000 avec l'écran d'accueil. Ce que j'appelle une configuration "hybride", est que les services sont gérés par Docker (PostgreSQL, Redis, Elasticsearch...) mais l'application est servie avec le Symfony CLI. L'application de démo de Symfony est un excellent exemple de configuration "sans Docker" : elle utilise une base de données SQLite qui nécessite uniquement l'extension PHP ext-pdo_sqlite. Je peux utiliser la configuration Docker ou le Symfony CLI sur ce projet, mais j'utilise quasiment toujours le Symfony CLI, car les temps de réponse pour servir les pages sont bien inférieurs.

Configuration Docker

Cette fois, l'application tout entière est servie par Docker. Nous avons plusieurs options:

  • Créer nos fichiers Docker et la configuration Docker Compose relative
  • Utiliser un kit de démarrage Docker existant

Je ne vais pas lister tous les kits Dockers, il en existe de multiples, en voici deux populaires parmi la communauté :

Sur ces deux dépôts, vous remarquerez un bouton Use this template. Cela signifie que vous allez créer un nouveau dépôt établi à partir des fichiers de celui-ci. C'est différent d'un fork, car vous allez perdre tout l'historique et vraiment commencer un nouveau dépôt. C'est le but. Je n'ai pas testé le kit JoliCode ; j'utilise dunglas/symfony-docker sur de multiples projets ; il est facile à utiliser et fonctionne bien. Jetez un coup d'œil aux pages d'accueil de ces deux kits afin d'avoir plus d'informations.

Pas de conclusion ici. Ça dépend de multiples facteurs. Une configuration avec le Symfony CLI peut être suffisante pour de petits ou des projets personnels, mais une configuration Docker est généralement requise pour une stack plus complète.

Analyse statique

Ce point est le plus important de cet article pour moi. Je n'imagine même pas développer sans désormais. Les outils tels que PHPStan or Psalm sont vraiment excellents et procurent un gain de temps substensiel. Ils peuvent prévenir de bugs critiques en production. J'aime PHPStan, certains préfèrent Psalm (ou autre). Mon conseil ici est d'en choisir un, n'utilisez pas les deux simultanément. Voyons comment installer PHPStan :

composer require phpstan/phpstan --dev
composer require phpstan/extension-installer --dev
composer require phpstan/phpstan-symfony --dev

On installe le plugin Symfony qui permet plus de contrôles et l'analyse du conteneur d'injection de dépendances. Voici la configuration que j'utilise sur Strangebuzz :

# configuration/phpstan.neon
includes:
    # require phpstan/extension-installer to avoid including these lines                                            PHPStan 1.x compat
    #- vendor/ekino/phpstan-banned-code/extension.neon     # https://github.com/ekino/phpstan-banned-code           ✅
    #- vendor/phpstan/phpstan-symfony/extension.neon       # https://github.com/phpstan/phpstan-symfony             ✅
    #- vendor/phpstan/phpstan-deprecation-rules/rules.neon # https://github.com/phpstan/phpstan-deprecation-rules   ✅
    #- vendor/phpstan/phpstan-strict-rules/rules.neon      # https://github.com/phpstan/phpstan-strict-rules        ✅
    #- vendor/phpstan/phpstan/phpstan-doctrine             # https://github.com/phpstan/phpstan-doctrine            ✅

# These are custom rules, check-out: https://www.strangebuzz.com/en/blog/creating-custom-phpstan-rules-for-your-symfony-project
rules:
    - App\PHPStan\ControllerIsFinalRule
    - App\PHPStan\ControllerExtendsSymfonyRule
    #- App\PHPStan\NoNewinControllerRule

parameters:
    # https://phpstan.org/config-reference#rule-level
    level: max # Max is level 9 as of PHPStan 1.0

    # https://phpstan.org/config-reference#analysed-files
    # Note that I have put my configuraiton file in the "./configuration" directory
    # if you have yours at the root of your project remove the "../"
    paths:
        - ../config
        - ../src
        - ../tests
        - ../public

    # https://github.com/phpstan/phpstan-symfony#configuration
    # Specific configuration for the Symfony plugin
    symfony:
        # I use the prod env because I have false positive regarding the tests which
        # are executed in the test environment.
        container_xml_path: ../var/cache/prod/App_KernelProdDebugContainer.xml

    # https://phpstan.org/config-reference#vague-typehints
    checkMissingIterableValueType:           true # https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type
    checkGenericClassInNonGenericObjectType: true # this parameter is activated at level 6

    # Nothing ignored! (almost!) 🎉
    ignoreErrors:
        - '#Dead catch - Error is never thrown in the try block.#'
        - '#Variable method call#'

    # I don't use the Symfony PHPUnit bridge in this project, but if you do, you
    # probably will have to add the following bootstrap file:
    #bootstrapFiles:
        #- %rootDir%/../../../vendor/bin/.phpunit/phpunit/vendor/autoload.php

Quelques explications. Pour un nouveau projet, on peut utiliser le niveau maximal ! C'est excellent, car c'est impossible à faire sur un projet legacy (je vous entends : n'utilisez pas de baseline !). Habituellement, on démarre au niveau 0, on fixe les avertissements (les niveaux les plus bas avertissent des erreurs les plus critiques), puis on passe au niveau suivant. J'utilise plusieurs plugins qui ajoutent des contrôles. On peut voir le chemin vers le fichier XML du conteneur d'injection de dépendances. (il permet à la fonction ContainerInterface::get() d'avoir le bon type de retour par exemple). J'ai aussi plusieurs règles personnalisées, comme expliqué dans un précédent article : Création de règles PHPStan personnalisées pour votre projet Symfony. N'hésitez pas à y jeter un coup d'œil. 🙂

Comme je l'ai dit précédemment, c'est peut-être le point le plus critique pour moi, car la différence de qualité entre du code non analysé et du code analysé au niveau maximal est juste énorme. Vraiment énorme. ✨

“Conclusion: Utilisez l'analyse statique au niveau maximal depuis le début. ”

Coding standards

On développe avec Symfony, il semble donc logique que notre application respecte les standards de code Symfony. L'outil le plus populaire est probablement PHP-CS-Fixer. On peut l'installer avec :

composer require friendsofphp/php-cs-fixer --dev

Ce qui est top, c'est que nous avons un jeu de règles dédié à Symfony reprenant les standards officiels. Les CS Symfony s'appuient eux-même sur le jeu de règles PSR-12. Voici mon fichier de configuration :

<?php

// php-cs-fixer.php

declare(strict_types=1);

// https://cs.symfony.com/

$finder = PhpCsFixer\Finder::create()
    ->in(__DIR__)
    ->exclude('var')
    ->exclude('code')
    ->exclude('snapshots')
    ->exclude('tmp')
;

return (new PhpCsFixer\Config())->setRules([
    '@Symfony' => true,
    'array_syntax' => ['syntax' => 'short'],
    'declare_strict_types' => true,
    'no_superfluous_phpdoc_tags' => true,
    'php_unit_fqcn_annotation' => false,
    'phpdoc_to_comment' => false,
    'yoda_style' => false,
    'native_function_invocation' => [         // https://cs.symfony.com/doc/rules/function_notation/native_function_invocation.html
        'include' => ['@compiler_optimized'],
        'scope' => 'namespaced',
        'strict' => true,
    ], ])
->setFinder($finder);

Le jeu de règles Symfony est activé. L'option declare_strict_types est pour moi obligatoire : elle permet de forcer l'ajout de la ligne declare(strict_types=1); dans chaque fichier PHP. C'est important, car ne peut utiliser ce mode peut mener à des comportements inattendus à cause des conversions de type implicites. Les autres options sont plus une histoire de goûts. Avant de commiter, lancez juste :

/vendor/bin/php-cs-fixer fix --allow-risky=yes

“Conclusion: Mettez-vous d'accord avec un jeu de règles avec votre équipe et oubliez ça. ”

Tests

Les tests sont bien sûr cruciaux pour une application. Ils ne permettent pas seulement de prouver que votre code fonctionne, mais un autre point essentiel, et souvent oublié, est qu'ils permettent de refactoriser avec confiance. Il existe quatre catégories principales de tests :

  • Tests unitaires
  • Tests d'intégration
  • Tests fonctionnels
  • Tests bouts-à-bouts

J'ai dédié un article complet à ce sujet. J'y explique comment on devrait proprement les organiser depuis le début afin qu'ils restent efficaces et faciles à maintenir : Organisation des tests de votre projet Symfony. L'outil principal est PHPUnit : il est parfaitement intégré dans Symfony grâce au pont PHPUnit

“Conclusion: Écrivez des tests, déployez et refactorisez avec confiance. ”

Couverture de code

Avoir des tests est excellent, mais comment savoir s'ils exécutent bien tout le code de votre application ? On peut utiliser Xdebug ou pcov. Essayons avec Xdebug. D'abord installons-le :

pecl install xdebug
echo "zend_extension=xdebug" > /usr/local/etc/php/conf.d/99-xdebug.ini
php -v

La version de PHP montre que Xdebug est désormais actif :

PHP 8.1.6 (cli) (built: May 17 2022 16:48:09) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.1.6, Copyright (c) Zend Technologies
    with Zend OPcache v8.1.6, Copyright (c), by Zend Technologies
    with Xdebug v3.1.2, Copyright (c) 2002-2021, by Derick Rethans

Puis, la couverture de code peut être générée avec la commande suivante. Ça produit un ravissant rapport HTML :

php -d xdebug.enable=1 -d xdebug.mode=coverage -d memory_limit=-1  vendor/bin/phpunit --coverage-html=var/coverage

Si tout est OK, le rapport est disponible dans var/coverage. Voici un exemple pour les contrôleurs principaux de ce projet. Honnêtement, je ne l'avais pas lancée depuis des semaines et j'ai dû travailler plusieurs heures pour arriver à ce résultat. Ce qui est excellent avec ce rapport, c'est qu'il permet de trouver des bugs dans vos tests ! Et effectivement, j'ai décelé au moins deux bugs dans mes tests fonctionnels lors de l'écriture de cet article. Sur ce projet, je n'ai que 65% de couverture de code ; je dois améliorer ce point. 😅


La couvertude de code de certains de mes controlleurs

Vous avez probablement entendu parler du mythe des 100% de couverture de code. Est-ce que ça garanti que votre application ne contient pas de bugs ? Bien sûr que non. Mais c'est vital puisqu'une couverture de code très basse est généralement la conséquence d'une mauvaise architecture. Que vos tests sont mal écrits, ou que votre code n'est pas assez découplé pour être testé correctement. Donc quel seuil utiliser ? 100% peut être difficile à atteindre. PHPUnit considère qu'une couverture de code de 90% est déjà un bon score. Donc, ça peut être un bon point de départ. Encore une fois, nous créons un nouveau projet, donc si nous voulons une importante couverture de code, on doit la mettre en place de suite. Dans six mois ou un an, il sera plus difficile de revenir à un pourcentage élevé. Une règle simple peut être : qu'une PR ne devrait jamais faire descendre le pourcentage global.


La couvertude de code de certains de mes controlleurs

“Conclusion: Suivez la couverture de code, ayez puis gardez un haut pourcentage depuis le début. ”

Intégration continue

Ce chapitre sera concis. J'ai déjà écrit un article complet sur le sujet (je dois le mettre à jour 😫) : Mise en place d'un workflow CI/CD pour un projet Symfony à l'aide des actions GitHub. Pour résumer, une CI garantie que tous vos processus fonctionnent sans faille. L'environnement doit être identique à votre environnement de production pour ne pas avoir de mauvaises surprises au déploiement. Il peut aussi aider lors des migrations puisqu'il est facile de tester une version spécifique de PHP ou d'un composant.

“Conclusion: utilisez une CI, rendez-la stable et ennuyeuse ”

Makefile

Ce chapitre est particulier pour moi, car il traite de l'expérience développeur (DX). C'est un aspect que je trouve particuièrement important. C'est précisément le but d'un Makefile : il aider à documenter les tâches les plus courantes lorsque l'on développe. Un exemple typique est d'initialiser, de créer une base de données, puis de charger les données de test d'une projet. Voulez-vous vraiment taper tout cela à chaque fois ?

bin/console doctrine:cache:clear-metadata --env=dev
bin/console doctrine:database:create --if-not-exists --env=dev
bin/console doctrine:schema:drop --force --env=dev
bin/console doctrine:schema:create --env=dev
bin/console doctrine:schema:validate --env=dev
bin/console hautelook:fixtures:load --no-interaction --env=dev

C'est là que le Makefile peut aider ; on peut ajouter une cible ; elle ressemble à ça :

load-fixtures: ## Build the DB, control the schema validity, load fixtures and check the migration status
	@$(SYMFONY) doctrine:cache:clear-metadata
	@$(SYMFONY) doctrine:database:create --if-not-exists
	@$(SYMFONY) doctrine:schema:drop --force
	@$(SYMFONY) doctrine:schema:create
	@$(SYMFONY) doctrine:schema:validate
	@$(SYMFONY) hautelook:fixtures:load --no-interaction

Et maintenant on a juste à lancer make load-fixtures. Ce qui est brillant, c'est que l'on documente chaque cible en même temps (grâce à une petite astuce). Quand on lance make à la racine du projet, on a un résumé de toutes actions disponibles :


sortie de la commande make

Bien sûr, on peut aussi utiliser des scripts composer (ils peuvent aussi être documentés), mais je les trouve bien moins pratiques. Il y a aussi Taskfile, mais je ne l'ai pas encore testé.

“Conclusion: Documentez tout votre processus de développement dans un Makefile et simplifiez la vie des développeurs. ”

Conclusion

Voilà ; ce sont les points que je trouve essentiels. Il y a d'autres sujets à discuter, mais je voulais rester concentré sur les "fondations". Je pourrais faire une seconde partie à cet article ou pas. Bien sûr, ces points peuvent tous faire l'objet d'un article dédié. Voici un résumé de ceux que j'ai déjà écrits :

Et voilà ! J'espère que vous avez aimé. Découvrez d'autres informations en rapport à cet article avec les liens ci-dessous. Comme toujours, retours, likes et retweets sont les bienvenus. (voir la boîte ci-dessous) À tantôt ! COil. 😊

  Lire la doc

Ils m'ont donné leurs retours et m'ont aidé à corriger des erreurs et typos dans cet article, un grand merci à : rherault. 👍

  Travaillez avec moi !


A vous de jouer !

Ces articles vous ont été utiles ? Vous pouvez m'aider à votre tour de plusieurs manières : (cf le tweet à droite pour me contacter )

  • Me remonter des erreurs ou typos.
  • Me remonter des choses qui pourraient être améliorées.
  • Aimez et retweetez !
  • Suivez moi sur Twitter
  • Inscrivez-vous au flux RSS.
  • Cliquez sur les boutons Plus sur Stackoverflow pour me faire gagner des badges "annonceur" 🏅.

Merci d'avoir tenu jusque ici et à très bientôt sur Strangebuzz ! 😉

COil