Coder sans filet, vraiment ?
Pourquoi l’analyse statique change vraiment la donne
Détection précoce des bugs
- Les erreurs de types (
TypeErrorlatents) - Les accès à des propriétés ou méthodes inexistantes
- Les variables non initialisées
- Les code paths jamais atteints
- Les retours de fonction incompatibles avec leur signature
Documentation vivante du code
@param non-empty-string $slug ou un type de retour précis comme array<int, User> vaut mieux que n’importe quel commentaire. Le code devient plus compréhensible et prévisible.Refactoring en confiance
Uniformisation du code
Automatisation naturelle
exit code 1 bloque la merge request, point. Plus de mauvaises surprise lors de la mise en production.Les outils de l’arsenal
PHP CS Fixer — le gardien du style
Installation
[pastacode lang= »bash » manual= »composer%20require%20–dev%20friendsofphp%2Fphp-cs-fixer » message= » » highlight= » » provider= »manual »/]
Configuration (.php-cs-fixer.php)
[pastacode lang= »php » manual= »%24finder%20%3D%20PhpCsFixer%5CFinder%3A%3Acreate()%0A%20%20%20%20-%3Ein(%5B__DIR__%20.%20’%2Fsrc’%2C%20__DIR__%20.%20’%2Ftests’%5D)%0A%20%20%20%20-%3Eexclude(%5B’var’%2C%20’vendor’%5D)%3B%0A%0Areturn%20(new%20PhpCsFixer%5CConfig())%0A%20%20%20%20-%3EsetRules(%5B%0A%20%20%20%20%20%20%20%20’%40PER-CS’%20%3D%3E%20true%2C%0A%20%20%20%20%20%20%20%20’%40Symfony’%20%3D%3E%20true%2C%0A%20%20%20%20%20%20%20%20’%40autoPHPMigration’%20%3D%3E%20true%2C%0A%20%20%20%20%20%20%20%20’array_syntax’%20%3D%3E%20%5B’syntax’%20%3D%3E%20’short’%5D%2C%0A%20%20%20%20%20%20%20%20’ordered_imports’%20%3D%3E%20%5B’sort_algorithm’%20%3D%3E%20’alpha’%5D%2C%0A%20%20%20%20%20%20%20%20’no_unused_imports’%20%3D%3E%20true%2C%0A%20%20%20%20%20%20%20%20’strict_comparison’%20%3D%3E%20true%2C%0A%20%20%20%20%5D)%0A%20%20%20%20-%3EsetFinder(%24finder)%3B » message= » » highlight= » » provider= »manual »/]
Utilisation
[pastacode lang= »bash » manual= »%23%20V%C3%A9rifier%20sans%20corriger%20(pour%20la%20CI)%0Avendor%2Fbin%2Fphp-cs-fixer%20fix%20–dry-run%20–diff%0A%0A%23%20Corriger%0Avendor%2Fbin%2Fphp-cs-fixer%20fix » message= » » highlight= » » provider= »manual »/]
Ce que CS Fixer normalise
- Indentation, espaces, sauts de ligne
- Ordre et groupement des
use - Déclarations
strict_types - Casts, opérateurs, virgules trailing
- PHPDoc et annotations
git diff ne contiendront plus jamais de changements de style parasites.PHPStan — l’analyse statique de référence
Installation
[pastacode lang= »bash » manual= »composer%20require%20–dev%20phpstan%2Fphpstan%0Acomposer%20require%20–dev%20phpstan%2Fextension-installer%0Acomposer%20require%20–dev%20phpstan%2Fphpstan-symfony%0Acomposer%20require%20–dev%20phpstan%2Fphpstan-doctrine » message= » » highlight= » » provider= »manual »/]
Configuration (phpstan.neon)
[pastacode lang= »bash » manual= »parameters%3A%0A%20%20%20%20level%3A%206%0A%20%20%20%20paths%3A%0A%20%20%20%20%20%20%20%20-%20src%0A%20%20%20%20%20%20%20%20-%20tests%0A%20%20%20%20symfony%3A%0A%20%20%20%20%20%20%20%20containerXmlPath%3A%20var%2Fcache%2Fdev%2FApp_KernelDevDebugContainer.xml%0A%20%20%20%20ignoreErrors%3A%0A%20%20%20%20%20%20%20%20-%20’%23Call%20to%20an%20undefined%20method%20Doctrine%5C%5CORM%5C%5CQueryBuilder%23′ » message= » » highlight= » » provider= »manual »/]
Les niveaux de rigueur (0 à 10)
Ce que PHPStan détecte
[pastacode lang= »php » manual= »%2F%2F%20PHPStan%20niveau%205%2B%20d%C3%A9tecte%20ce%20probl%C3%A8me%0Aclass%20UserService%0A%7B%0A%20%20%20%20public%20function%20findUser(int%20%24id)%3A%20User%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20return%20%24this-%3Erepository-%3Efind(%24id)%3B%20%2F%2F%20%E2%9A%A0%EF%B8%8F%20retourne%20User%7Cnull%2C%20pas%20User%20!%0A%20%20%20%20%7D%0A%7D » message= » » highlight= » » provider= »manual »/]
------ -----------------------------------------------
Line src/Service/UserService.php
------ -----------------------------------------------
8 Method UserService::findUser() should return
App\Entity\User but returns App\Entity\User|null.
------ ----------------------------------------------
Baseline : apprivoiser un projet legacy
[pastacode lang= »bash » manual= »vendor%2Fbin%2Fphpstan%20analyse%20–generate-baseline » message= » » highlight= » » provider= »manual »/]
phpstan-baseline.neon qui ignore les erreurs existantes. Il ne vous restera plus qu’à l’inclure dans votre fichier de configuration phpstan.neon :[pastacode lang= »bash » manual= »includes%3A%0A%09-%20phpstan-baseline.neon » message= » » highlight= » » provider= »manual »/]
Rector — bien plus qu’un outil de migration
Installation
[pastacode lang= »bash » manual= »composer%20require%20–dev%20rector%2Frector%0Acomposer%20require%20–dev%20tomasvotruba%2Ftype-coverage » message= » » highlight= » » provider= »manual »/]
Rector comme outil de réduction de dette — sans migration
[pastacode lang= »php » manual= »use%20Rector%5CConfig%5CRectorConfig%3B%0Ause%20Rector%5CSet%5CValueObject%5CSetList%3B%0A%0Areturn%20RectorConfig%3A%3Aconfigure()%0A%20%20%20%20-%3EwithPaths(%5B__DIR__%20.%20’%2Fsrc’%2C%20__DIR__%20.%20’%2Ftests’%5D)%0A%20%20%20%20-%3EwithSets(%5B%0A%20%20%20%20%20%20%20%20SetList%3A%3ADEAD_CODE%2C%20%20%20%20%20%20%20%2F%2F%20supprime%20le%20code%20mort%0A%20%20%20%20%20%20%20%20SetList%3A%3ACODE_QUALITY%2C%20%20%20%20%2F%2F%20simplifie%20les%20constructions%20inutilement%20complexes%0A%20%20%20%20%20%20%20%20SetList%3A%3ACODING_STYLE%2C%20%20%20%20%2F%2F%20uniformise%20les%20idiomes%20PHP%0A%20%20%20%20%20%20%20%20SetList%3A%3ATYPE_DECLARATION%2C%20%2F%2F%20ajoute%20les%20types%20manquants%20l%C3%A0%20o%C3%B9%20ils%20sont%20inf%C3%A9rables%0A%20%20%20%20%5D)%3B » message= » » highlight= » » provider= »manual »/]
[pastacode lang= »php » manual= »%2F%2F%20Avant%20%E2%80%94%20condition%20toujours%20vraie%2C%20branche%20morte%0Aif%20(%24user%20instanceof%20User)%20%7B%0A%20%20%20%20return%20%24user-%3EgetName()%3B%20%2F%2F%20%24user%20est%20d%C3%A9j%C3%A0%20typ%C3%A9%20User%0A%7D%0A%0A%2F%2F%20Apr%C3%A8s%0Areturn%20%24user-%3EgetName()%3B » message= » » highlight= » » provider= »manual »/]
[pastacode lang= »php » manual= »%2F%2F%20Avant%20%E2%80%94%20construction%20inutilement%20verbeuse%0A%24result%20%3D%20%5B%5D%3B%0Aforeach%20(%24items%20as%20%24item)%20%7B%0A%20%20%20%20%24result%5B%5D%20%3D%20%24item-%3EgetValue()%3B%0A%7D%0Areturn%20%24result%3B%0A%0A%2F%2F%20Apr%C3%A8s%0Areturn%20array_map(fn(%24item)%20%3D%3E%20%24item-%3EgetValue()%2C%20%24items)%3B » message= » » highlight= » » provider= »manual »/]
[pastacode lang= »php » manual= »%2F%2F%20Avant%20%E2%80%94%20types%20absents%20dans%20une%20classe%20dont%20Rector%20peut%20les%20inf%C3%A9rer%0Aclass%20OrderService%0A%7B%0A%20%20%20%20private%20%24repository%3B%0A%0A%20%20%20%20public%20function%20__construct(OrderRepository%20%24repository)%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%24this-%3Erepository%20%3D%20%24repository%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20public%20function%20find(%24id)%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20return%20%24this-%3Erepository-%3Efind(%24id)%3B%0A%20%20%20%20%7D%0A%7D%0A%0A%2F%2F%20Apr%C3%A8s%20%E2%80%94%20types%20ajout%C3%A9s%20automatiquement%0Aclass%20OrderService%0A%7B%0A%20%20%20%20public%20function%20__construct(%0A%20%20%20%20%20%20%20%20private%20readonly%20OrderRepository%20%24repository%2C%0A%20%20%20%20)%20%7B%7D%0A%0A%20%20%20%20public%20function%20find(int%20%24id)%3A%20%3FOrder%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20return%20%24this-%3Erepository-%3Efind(%24id)%3B%0A%20%20%20%20%7D%0A%7D » message= » » highlight= » » provider= »manual »/]
[pastacode lang= »php » manual= »%2F%2F%20Avant%0Aif%20(%24user%20!%3D%3D%20null)%20%7B%0A%20%20%20%20return%20%24user-%3EgetName()%3B%0A%7D%0Areturn%20’Anonymous’%3B%0A%0A%2F%2F%20Apr%C3%A8s%0Areturn%20%24user%3F-%3EgetName()%20%3F%3F%20’Anonymous’%3B » message= » » highlight= » » provider= »manual »/]
Quand activer les sets de migration
[pastacode lang= »php » manual= »-%3EwithSets(%5B%0A%20%20%20%20SymfonySetList%3A%3AANNOTATIONS_TO_ATTRIBUTES%2C%0A%20%20%20%20DoctrineSetList%3A%3AANNOTATIONS_TO_ATTRIBUTES%2C%0A%5D) » message= » » highlight= » » provider= »manual »/]
[pastacode lang= »php » manual= »%2F%2F%20Avant%20(annotations)%0A%2F**%0A%20*%20%40Route(%22%2Fapi%2Fusers%22%2C%20methods%3D%7B%22GET%22%7D)%0A%20*%20%40IsGranted(%22ROLE_USER%22)%0A%20*%2F%0Apublic%20function%20list()%3A%20JsonResponse%20%7B%20…%20%7D%0A%0A%2F%2F%20Apr%C3%A8s%20(attributs%20PHP%208)%20%E2%80%94%20appliqu%C3%A9%20automatiquement%0A%23%5BRoute(‘%2Fapi%2Fusers’%2C%20methods%3A%20%5B’GET’%5D)%5D%0A%23%5BIsGranted(‘ROLE_USER’)%5D%0Apublic%20function%20list()%3A%20JsonResponse%20%7B%20…%20%7D » message= » » highlight= » » provider= »manual »/]
Mode dry-run : toujours vérifier avant d’appliquer
[pastacode lang= »php » manual= »%23%20Aper%C3%A7u%20des%20changements%20sans%20les%20appliquer%0Avendor%2Fbin%2Frector%20process%20–dry-run%0A%0A%23%20Application%20r%C3%A9elle%0Avendor%2Fbin%2Frector%20process » message= » » highlight= » » provider= »manual »/]
Levels : petit pas par petit pas
[pastacode lang= »php » manual= »use%20Rector%5CConfig%5CRectorConfig%3B%0A%0Areturn%20RectorConfig%3A%3Aconfigure()%0A%20%20%20%20-%3EwithTypeCoverageLevel(0)%0A%20%20%20%20-%3EwithTypeCoverageDocblockLevel(0)%0A%20%20%20%20-%3EwithDeadCodeLevel(0)%0A%20%20%20%20-%3EwithCodeQualityLevel(0)%0A%20%20%20%20-%3EwithCodingStyleLevel(0)%3B » message= » » highlight= » » provider= »manual »/]
Rector Swiss Knife — la boîte à outils complémentaire
Installation
[pastacode lang= »bash » manual= »composer%20require%20–dev%20rector%2Fswiss-knife » message= » » highlight= » » provider= »manual »/]
Fonctionnalités clés
[pastacode lang= »bash » manual= »vendor%2Fbin%2Fswiss-knife%20check-conflicts%20. » message= » » highlight= » » provider= »manual »/]
[pastacode lang= »bash » manual= »vendor%2Fbin%2Fswiss-knife%20check-commented-code%20src%20tests » message= » » highlight= » » provider= »manual »/]
[pastacode lang= »bash » manual= »%23%20D%C3%A9tecte%20les%20classes%20qui%20pourraient%20%C3%AAtre%20d%C3%A9clar%C3%A9es%20final%0Avendor%2Fbin%2Fswiss-knife%20finalize-classes%20src » message= » » highlight= » » provider= »manual »/]
[pastacode lang= »bash » manual= »vendor%2Fbin%2Fswiss-knife%20find-multi-classes%20src » message= » » highlight= » » provider= »manual »/]
[pastacode lang= »bash » manual= »vendor%2Fbin%2Fswiss-knife%20namespace-to-psr-4%20src%20–namespace-root%20%22App%5C%5C%22″ message= » » highlight= » » provider= »manual »/]
Intégration dans un projet Symfony
Structure du projet
projet/ ├── .php-cs-fixer.php ├── phpstan.neon ├── phpstan-baseline.neon ├── rector.php └── composer.json
Utilisation avec Composer
{
"scripts": {
"cs-check": "php-cs-fixer fix --dry-run --diff",
"cs-fix": "php-cs-fixer fix",
"stan": "phpstan analyse",
"rector-check": "rector process --dry-run",
"rector-fix": "rector process",
"psr4-fix": "swiss-knife namespace-to-psr-4 src --namespace-root \"App\\\\\"",
"finalize-fix": "swiss-knife finalize-classes src tests",
"swiss-check": [
"swiss-knife check-conflicts .",
"swiss-knife check-commented-code src tests",
"swiss-knife find-multi-classes src"
],
"qa": [
"@cs-check",
"@stan",
"@rector-check",
"@swiss-check"
],
"fix": [
"@cs-fix",
"@rector-fix",
"@psr4-fix",
"@finalize-fix"
]
}
}
composer qa.Pipeline CI/CD (GitLab CI)
stages:
- quality
variables:
COMPOSER_CACHE_DIR: "$CI_PROJECT_DIR/.composer-cache"
cache:
key: "${CI_COMMIT_REF_SLUG}"
paths:
- .composer-cache/
- vendor/
image: php:8.2-cli
before_script:
- apt-get update -yqq && apt-get install -yqq git unzip
- curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
- composer install --no-progress --no-interaction
cs-check:
stage: quality
script:
- composer cs-check
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
- if: '$CI_COMMIT_BRANCH'
phpstan:
stage: quality
script:
- composer phpstan
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
- if: '$CI_COMMIT_BRANCH'
rector:
stage: quality
script:
- composer rector-check
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
- if: '$CI_COMMIT_BRANCH'
swiss:
stage: quality
script:
- composer swiss-check
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
- if: '$CI_COMMIT_BRANCH'
quality, ce qui réduit le temps de pipeline. L’utilisation du cache Composer évite de re-télécharger les dépendances à chaque run.Par où commencer sur un projet existant ?
-
Style et formatage → Intégrez PHP CS Fixer avec les règles
@Symfony. Appliquez-le une fois sur toute la base de code, commitez, et activez-le en CI. -
Analyse statique niveau 0 → Ajoutez Rector en dry-run et mettez en place les nouvelles règles au niveau 0. → Ajoutez PHPStan niveau 0, générez une baseline. Zéro nouvelles erreurs autorisées dès maintenant.
-
Montée en niveau → Montez PHPStan niveau par niveau en corrigeant les erreurs de la baseline au fur et à mesure. Sur un projet existant le niveau 10 peut être trop exigeant, le niveau 6 est généralement suffisant. → Montez progressivement les niveaux des règles de Rector. Dès quelles sont atteintes, vous pourrez introduire de nouvelles règles.
-
Peaufiner le projet → Ajoutez Swiss-knife pour ajouter les dernières amélioration possibles.