Aller au contenu
Efficience IT
·6 min de lecture·Qualité de code

Comment PHPStan peut vous aider à améliorer la qualité de votre code PHP

Par Louis-Arnaud Catoire

Mis à jour le

Comment PHPStan peut vous aider à améliorer la qualité de votre code PHP

L'analyse statique : un compilateur pour PHP

PHP est un langage interprété. Contrairement à Java ou Go, aucune phase de compilation ne vérifie la cohérence des types avant l'exécution. Pendant des années, les développeurs PHP ont découvert leurs erreurs de typage en production, à travers des TypeError ou des null inattendus. L'analyse statique comble ce vide : elle simule une compilation en parcourant l'AST (Abstract Syntax Tree) du code source, sans jamais l'exécuter.

PHPStan est l'outil de référence dans cet espace. Il parse votre code, reconstruit un graphe de types et applique des règles de vérification sur ce graphe. Là où les tests unitaires vérifient des comportements, PHPStan vérifie des invariants structurels : cohérence des signatures, nullabilité, exhaustivité des unions. Les deux approches sont complémentaires et non substituables.

Installation et première exécution

L'installation se fait via Composer, le gestionnaire de dépendances PHP :

composer require --dev phpstan/phpstan

Puis lancez l'analyse sur votre code :

./vendor/bin/phpstan analyse src

Centralisez la configuration dans un fichier phpstan.neon à la racine du projet :

parameters:
    level: 6
    paths:
        - src
    excludePaths:
        - src/Migrations

Avec ce fichier en place, un simple ./vendor/bin/phpstan analyse suffit.

Les niveaux de règles : une adoption progressive

PHPStan propose 11 niveaux de règles, de 0 à 10. Chaque niveau inclut les vérifications du précédent.

Niveaux 0-2 couvrent les bases : classes et fonctions inconnues, nombre d'arguments incorrect, variables potentiellement non définies, types inconnus sur les expressions mixed.

Niveaux 3-5 ajoutent la vérification systématique des types de retour, la détection des appels sur des types nullables, et le contrôle des arguments passés aux fonctions.

Niveau 6 est le seuil recommandé pour les projets matures. Le typage strict entre en jeu : les annotations manquantes sont signalées. En dessous, PHPStan tolère l'implicite. Au-dessus, il l'interdit.

Niveaux 7-9 affinent les unions, les intersections, la nullabilité imbriquée. Le niveau 9 traite mixed comme un type opaque qui doit être narrowé avant utilisation, ce qui force une rigueur comparable à un langage fortement typé.

Pour un projet existant, commencez au niveau 0 et montez progressivement. Pour un nouveau projet, visez directement le niveau 6.

Exemples concrets de bugs détectés

Un appel de méthode sur un type potentiellement null :

function getUsername(User $user): string
{
    return $user->getProfile()->getName();
}

Si getProfile() peut retourner null, PHPStan signale : Cannot call method getName() on null. Ce type d'erreur passe systématiquement en code review humaine parce que le chemin nominal fonctionne.

Un type de retour incohérent :

function findUser(int $id): User
{
    $user = $this->repository->find($id);

    return $user;
}

Si find() retourne User|null, PHPStan détecte l'incohérence. En production, c'est un crash sur le premier appel avec un ID invalide.

PHPStan excelle aussi à repérer le code mort dans vos projets, ces branches jamais atteintes qui alourdissent la base de code. Coupler PHPStan avec des tests automatisés garantit une couverture maximale des erreurs, tant structurelles que comportementales.

Une condition dupliquée, signe de copier-coller :

if ($status === 'active' || $status === 'active') {

PHPStan repère la redondance. La deuxième condition masque probablement un autre état qui n'est jamais vérifié.

Besoin d'accompagnement sur votre projet ?

Parlons-en

La baseline : dette technique maîtrisée

Sur un projet existant, lancer PHPStan au niveau 6 peut générer des centaines d'erreurs. Cette accumulation est un symptôme classique de dette technique qu'il faut traiter méthodiquement. La baseline résout ce problème en gelant l'état actuel :

./vendor/bin/phpstan analyse --generate-baseline

Cette commande crée un fichier phpstan-baseline.neon qui liste toutes les erreurs existantes. Référencez-le dans votre configuration :

includes:
    - phpstan-baseline.neon

parameters:
    level: 6
    paths:
        - src

À partir de ce moment, seules les nouvelles erreurs sont signalées. La baseline n'est pas un passe-droit permanent : c'est un outil de gestion de la dette technique. Traitez-la comme un backlog. Chaque sprint, corrigez un lot d'erreurs et regénérez la baseline. Suivez son évolution : une baseline qui grossit est un signal d'alerte pour un lead.

Un réflexe utile : intégrez dans votre CI un check qui compte les entrées de la baseline et échoue si ce nombre augmente. Cela empêche l'accumulation silencieuse de dette.

Intégration CI et workflow d'équipe

PHPStan prend toute sa valeur dans un pipeline d'intégration continue. Il s'intègre naturellement aux côtés de PHP-CS-Fixer et des conventions de codage PHP pour former un pipeline de qualité complet. Dans un .gitlab-ci.yml :

phpstan:
    stage: test
    script:
        - composer install
        - vendor/bin/phpstan analyse --no-progress

Chaque merge request est vérifiée automatiquement. Si PHPStan échoue, le développeur corrige avant de merger. Le --no-progress supprime l'affichage de la barre de progression, inutile dans les logs CI.

Pour les équipes avec un cache CI, ajoutez --error-format=json et exploitez la sortie pour des dashboards de qualité ou des notifications Slack ciblées.

L'extension phpstan-symfony : aller au-delà du code vanilla

Les frameworks PHP utilisent massivement des patterns que l'analyse statique ne peut pas résoudre seule : injection de dépendances, configuration YAML, méthodes magiques. L'extension phpstan-symfony comble ce fossé :

composer require --dev phpstan/phpstan-symfony
includes:
    - vendor/phpstan/phpstan-symfony/extension.neon

parameters:
    level: 6
    paths:
        - src
    symfony:
        containerXmlPath: var/cache/dev/App_KernelDevDebugContainer.xml

Avec cette configuration, PHPStan comprend les types retournés par $container->get(), valide les noms de services, type correctement les objets Request et InputInterface. Sans cette extension, des dizaines de faux positifs polluent l'analyse et poussent les développeurs à ignorer les vrais problèmes.

Règles custom : PHPStan comme gardien d'architecture

C'est ici que PHPStan cesse d'être un simple linter et devient un outil d'architecture. Vous pouvez écrire des règles custom qui encodent les décisions structurelles de votre projet.

Prenons un exemple concret : interdire qu'un service du domaine dépende directement de l'infrastructure.

use PHPStan\Rules\Rule;
use PHPStan\Analyser\Scope;
use PhpParser\Node;
use PhpParser\Node\Stmt\Use_;

/**
 * @implements Rule<Use_>
 */
class NoDomainToInfrastructureDependencyRule implements Rule
{
    public function getNodeType(): string
    {
        return Use_::class;
    }

    public function processNode(Node $node, Scope $scope): array
    {
        $file = $scope->getFile();
        if (!str_contains($file, '/Domain/')) {
            return [];
        }

        foreach ($node->uses as $use) {
            $name = $use->name->toString();
            if (str_contains($name, 'Infrastructure\\')) {
                return [
                    'Les classes du Domain ne doivent pas importer depuis Infrastructure.',
                ];
            }
        }

        return [];
    }
}

Cette règle transforme une convention verbale ("le domaine ne dépend pas de l'infra") en une contrainte vérifiée à chaque commit. C'est la différence entre un ADR (Architecture Decision Record) que personne ne lit et une garde-fou automatisée.

Enregistrez vos règles custom dans la configuration :

services:
    -
        class: App\PHPStan\NoDomainToInfrastructureDependencyRule
        tags:
            - phpstan.rules.rule

D'autres cas d'usage courants pour les règles custom : interdire l'utilisation de new dans les controllers (tout doit passer par l'injection), forcer l'usage de value objects pour certains paramètres, ou empêcher les dépendances circulaires entre bounded contexts. Ce type de règle rejoint la philosophie du domaine qui ne devrait jamais connaître Symfony, où PHPStan devient le gardien des frontières architecturales.

Le système de types avancé : generics et template types

À partir du niveau 7, PHPStan exploite pleinement son système de types. Les generics PHP via les annotations @template permettent d'exprimer des contrats que le langage natif ne supporte pas.

/**
 * @template T of object
 * @param class-string<T> $className
 * @return T
 */
function create(string $className): object
{
    return new $className();
}

Avec cette annotation, PHPStan sait que create(User::class) retourne un User, pas un object générique. Ce niveau de précision se propage dans tout le graphe de types et élimine des catégories entières de bugs liés au downcasting.

Les template types brillent dans les repositories, les collections typées et les factories. Un Repository<User> dont la méthode find() retourne User|null au lieu de object|null : c'est ce niveau de typage qui rapproche PHP d'un langage à typage fort sans sacrifier sa flexibilité.

Les conditional return types (@return ($flag is true ? Foo : Bar)) et les assertions (@phpstan-assert) complètent l'outillage pour modéliser des API complexes que le type system natif de PHP ne peut pas capturer.

PHPStan comme philosophie de design

Au-delà de l'outil, adopter PHPStan au niveau max transforme la façon de concevoir du code. L'obligation de satisfaire l'analyseur pousse vers des designs plus explicites : moins de mixed, moins de magie, des contrats clairs entre les couches. Le code devient sa propre documentation. Pour découvrir les erreurs concrètes les plus fréquentes à ce niveau, l'article sur les 10 erreurs PHPStan niveau max sur un projet Symfony fournit des exemples et des corrections directement applicables.

Un architecte qui configure PHPStan avec des règles custom, des generics et un niveau 9 ne cherche pas à satisfaire un outil. Il encode les invariants de son système dans un vérificateur automatique qui tourne à chaque commit. C'est de la gouvernance technique exécutable. Notre service d'audit de code PHP s'appuie sur ce type d'outillage pour identifier les failles structurelles d'un projet, première étape d'un parcours de modernisation applicative.

Pour aller plus loin

Faites auditer votre code PHP

Un regard extérieur sur votre base de code peut révéler des problèmes structurels que l'habitude fait oublier. Profitez d'un audit technique gratuit de 30 minutes.

Demander un audit gratuit

Questions fréquentes

PHPStan est un outil d'analyse statique qui détecte les erreurs dans votre code PHP sans l'exécuter. Il vérifie la cohérence des types, les appels de méthodes inexistantes, les variables non définies et les problèmes de logique. C'est un complément aux tests unitaires qui attrape les bugs avant qu'ils n'arrivent en production.

Commencez par le niveau 1 et montez progressivement. Le niveau 5 est un bon objectif pour la plupart des projets : il vérifie les types de retour, les paramètres et les propriétés. Les niveaux 8 à 10 imposent un typage strict quasi complet, idéal pour les projets critiques mais plus exigeant à maintenir.

Oui, c'est même la pratique recommandée. PHPStan s'exécute en ligne de commande et retourne un code d'erreur non nul si des problèmes sont détectés. Il s'intègre dans GitLab CI, GitHub Actions ou tout autre outil de CI en une seule ligne de configuration.

Articles connexes