TECHNOLOGIES / PHPStan

PHPStan : analyse statique PHP

PHPStan analyse le code PHP sans l'exécuter et détecte les erreurs de type, les appels invalides et les incohérences avant qu'elles n'atteignent la production. Il est activé sur tous nos projets Symfony avec les extensions Doctrine et Symfony.

Cette fiche documente notre configuration type, notre gestion de la baseline et notre intégration dans les pipelines CI/CD.

2.x Version actuelle Sortie fin 2024 - niveau 10 disponible, PHP 7.4+ requis
1.x Encore largement utilisée Niveaux 0 à 9 uniquement - pas de niveau 10
0.12 Version historique Dernière branche avant la v1.0 - introduction de la baseline

PRÉSENTATION

Qu'est-ce que PHPStan ?

PHPStan est un outil open source d'analyse statique pour PHP. Il lit le code source sans l'exécuter et détecte les erreurs de type, les appels invalides, les variables indéfinies et des dizaines d'autres catégories de bugs avant qu'ils n'atteignent la production.

L'analyse se lance en ligne de commande avec vendor/bin/phpstan analyse et s'intègre nativement dans les pipelines CI. PHPStan propose 11 niveaux de rigueur progressifs, de 0 à 10, permettant d'adopter l'outil aussi bien sur un nouveau projet que sur une base de code existante.

Son moteur d'inférence de types comprend les génériques, les templates PHPDoc et les extensions de framework (Symfony, Doctrine...), ce qui lui permet d'analyser du code métier complexe sans multiplier les faux positifs.

Terminal affichant une analyse PHPStan avec des erreurs de type détectées

POURQUOI PHPSTAN

Ce qui en fait notre outil d'analyse de référence

PHPStan est activé sur tous nos projets Symfony. Il constitue l'un des piliers de notre démarche de qualité logicielle et s'intègre sans exception à notre pipeline CI.

Détection d'erreurs sans exécution

PHPStan analyse le code source statiquement, sans l'exécuter et détecte les appels à des méthodes inexistantes, les types incompatibles, les variables non définies et des dizaines d'autres catégories d'erreurs.

Filet de sécurité avant la production

Les erreurs détectées par PHPStan auraient causé des exceptions en production. En les bloquant en pipeline CI, on évite les incidents et on réduit le temps passé à déboguer des problèmes évitables.

Niveaux progressifs (0 à 10)

PHPStan propose 11 niveaux d'analyse, de 0 à 10, de plus en plus stricts. On peut adopter PHPStan sur un projet legacy en démarrant au niveau 0 et monter progressivement sans tout bloquer d'un coup.

Extensions Symfony et Doctrine

Les extensions phpstan-symfony et phpstan-doctrine comprennent les conteneurs de services, les repositories, les entités et les formulaires → PHPStan analyse correctement notre stack Symfony sans faux positifs.

Baseline pour les projets legacy

Sur un projet existant avec des erreurs héritées, la baseline capture l'état actuel et PHPStan ne signale que les nouvelles erreurs introduites. Cela permet d'activer l'outil sans bloquer le travail courant.

Intégration PHPStorm native

Le plugin PHPStan pour PHPStorm affiche les erreurs directement dans l'éditeur avant même le push. Les développeurs voient les problèmes au moment où ils les introduisent, pas lors de la revue de code.

NIVEAUX D'ANALYSE

11 niveaux, de 0 à 10, cumulatifs

Chaque niveau inclut les vérifications des niveaux précédents. On regroupe ces 11 niveaux en 4 paliers pour piloter une adoption progressive, du projet legacy au niveau max dès le départ.

Niveau 0–2

Minimal

Détection des erreurs évidentes : classes, méthodes et fonctions inconnues, variables toujours indéfinies.

  • <strong>Niveau 0</strong> : appels à des classes, méthodes et fonctions inexistantes
  • <strong>Niveau 1</strong> : variables possiblement indéfinies, méthodes magiques __call et __get
  • <strong>Niveau 2</strong> : méthodes inconnues vérifiées sur toute expression, validation des PHPDoc
Niveau 3–4

Standard

Vérification des types de retour et premières détections de code mort.

  • <strong>Niveau 3</strong> : types de retour et types assignés aux propriétés
  • <strong>Niveau 4</strong> : instanceof toujours faux, branches else mortes, code inatteignable après un return
Niveau 5–6

Strict

Contrôle des types d'arguments passés aux méthodes et signalement des typehints manquants.

  • <strong>Niveau 5</strong> : types des arguments passés aux méthodes et fonctions
  • <strong>Niveau 6</strong> : typehints manquants signalés sur les paramètres, propriétés et retours
Niveau 7–10

Très strict

Rigueur maximale sur les types union, les valeurs nullables et le type mixed.

  • <strong>Niveau 7</strong> : types union partiellement incorrects
  • <strong>Niveau 8</strong> : appels de méthodes et accès de propriétés sur des types nullables
  • <strong>Niveau 9 et 10</strong> : rigueur totale sur le type mixed, explicite puis implicite

Source : Documentation officielle PHPStan : Rule Levels .

NOTRE USAGE

Comment nous utilisons PHPStan chez SmartBooster

Notre approche s'adapte à l'état du projet : niveau max dès le départ sur les nouveaux projets, baseline progressive sur les projets repris.

Niveau max sur les nouveaux projets

Sur tout nouveau projet Symfony, PHPStan est configuré au niveau 10, le niveau max, dès le départ. Le coût est nul quand le code est écrit avec les types dès le début.

Baseline sur les projets existants

Sur les projets repris ou en migration, on génère une baseline initiale pour ne bloquer que les nouvelles régressions. La baseline est réduite progressivement à chaque sprint.

Job bloquant en pipeline CI

PHPStan tourne dans le stage test de nos pipelines GitLab CI. Une erreur PHPStan bloque la validation de la merge request, tout comme un test PHPUnit échoué : la qualité est structurelle, pas optionnelle.

EXTENSIBILITÉ

Le système d'extensions PHPStan

PHPStan peut être enrichi par des extensions qui ajoutent des règles spécifiques à un framework, une librairie ou une pratique métier. Chaque extension s'installe via Composer et s'active dans phpstan.neon, ou automatiquement avec phpstan/extension-installer. Voir la liste complète des extensions officielles →

phpstan-symfony

Vérifie que nos contrôleurs Symfony sont correctement définis : types des paramètres de route, services injectés, configuration du container.

phpstan-doctrine

Vérifie que nos entités Doctrine sont correctement définies : mapping des colonnes, relations et types de propriétés.

phpstan-friendly-formatter

Améliore l'affichage des messages d'erreur et le résumé des identifiants (Identifier Summary) en sortie de commande, pour un diagnostic plus rapide.

phpstan-disallowed-calls

Détecte l'usage d'appels interdits (exec, system...) pour éviter les failles de sécurité liées à l'exécution de commandes système.

phpstan-phpunit

Vérifie la bonne écriture des tests PHPUnit : assertions correctement typées (assertSame plutôt que assertTrue), mocks cohérents et tests sans assertion oubliée.

HISTORIQUE

Historique des versions PHPStan

Les versions ayant marqué une évolution notable de l'outil, de la 0.11 à la 2.2. Les versions intermédiaires sans changement majeur ne sont pas listées.

Version Date Nouveautés clés
2.2 28 mai 2026 Unsealed array shapes et clés de tableaux plus sûres : Introduction des unsealed array shapes (array{a: X, ...}) pour composer des types tableaux, des types non-decimal-int-string et decimal-int-string pour sécuriser les clés, et de la vérification des constantes passées en argument de fonction. Lire l'article →
2.1 31 décembre 2024 Support des property hooks de PHP 8.4 : Compréhension complète des property hooks introduits par PHP 8.4, de la visibilité asymétrique et de l'attribut #[Deprecated]. Lire l'article →
2.0 11 novembre 2024 Niveau 10 et refonte mémoire : Première version majeure depuis la 1.0 : introduction du niveau 10, du type list, validation du tag @var contre le type natif et réduction de 50 à 70 % de la consommation mémoire sur les gros projets. Lire l'article →
1.12 27 août 2024 Route vers PHPStan 2.0 : Généralisation de l'inférence précise des types pour les expressions régulières (preg_match), correction des angles morts sur les tags PHPDoc et premiers pas vers le support de PHP 8.4. Lire l'article →
1.11 13 mai 2024 Identifiants d'erreurs et refonte de PHPStan Pro : Ajout des identifiants d'erreurs (catégorie.sous-type) pour cibler précisément les erreurs ignorées via le nouveau tag @phpstan-ignore, et refonte complète de l'interface PHPStan Pro. Lire l'article →
1.10 21 février 2023 Validation du tag @var et détection des mensonges de type : Validation du tag @var par rapport au type natif de l'expression assignée, et renforcement de la détection des cas enum non gérés dans les expressions match. Lire l'article →
1.9 3 novembre 2022 PHPDoc asserts et type list : Introduction des tags @phpstan-assert pour décrire le rétrécissement de type dans les fonctions métier, du type list expérimental et du tag @param-out pour les paramètres passés par référence. Lire l'article →
1.6 26 avril 2022 Types de retour conditionnels : Ajout des types de retour conditionnels dans les PHPDoc (syntaxe proche du ternaire) et des masques d'entiers (int-mask), avec d'importantes optimisations mémoire. Lire l'article →
1.0 1er novembre 2021 Première version stable : Après 6 ans de développement : introduction du niveau 9 (rigueur sur le type mixed) et d'un engagement formel de stabilité (BC Promise) pour un usage professionnel serein. Lire l'article →
0.12 4 décembre 2019 Generics et niveau dédié aux typehints manquants : Introduction des generics, du type class-string et d'un nouveau niveau dédié aux typehints manquants : la base du système de niveaux progressifs encore utilisé aujourd'hui. Lire l'article →
0.11 16 janvier 2019 Ignore errors par chemin et principe de Liskov : Erreurs ignorées ciblables par chemin de fichier, meilleure compréhension des tableaux non vides et premiers contrôles du principe de substitution de Liskov. Lire l'article →

BONNES PRATIQUES & DOCUMENTATION

Nos conventions PHPStan

Documentation de référence pour l'équipe SmartBooster.

Configuration et cycle de vie d'une baseline sur un projet

La baseline permet d'activer PHPStan au niveau max sur une base de code existante sans corriger toutes les erreurs d'un coup : elle capture les erreurs actuelles pour ne signaler ensuite que les nouvelles.

1. Générer la baseline.

On lance l'analyse avec --generate-baseline, qui écrit le fichier phpstan-baseline.neon (liste des erreurs avec leur message, leur fichier et leur nombre d'occurrences) :

vendor/bin/phpstan analyse src tests --level=10 --memory-limit=1G -vv -c phpstan.neon --allow-empty-baseline --generate-baseline

Détail des options :

  • analyse src tests : commande d'analyse, ciblée sur les dossiers src et tests.
  • --level=10 : niveau de rigueur maximal, celui vers lequel on veut converger.
  • --memory-limit=1G : relève la limite mémoire, l'analyse d'un gros projet au niveau max étant gourmande.
  • -vv : mode verbeux, utile pour suivre la progression et diagnostiquer un éventuel plantage.
  • -c phpstan.dev.neon : fichier de configuration à utiliser.
  • --allow-empty-baseline : autorise l'écriture d'un fichier même quand il n'y a aucune erreur à figer.
  • --generate-baseline : déclenche l'écriture de phpstan-baseline.neon au lieu d'afficher les erreurs.

Nous avons encapsulé cette commande dans une commande Make de notre standard-bundle pour en simplifier l'usage au quotidien.

Chaque entrée du fichier généré combine trois informations : le message d'erreur (transformé en expression régulière), le fichier concerné et un count :

-
    message: '#^Method App\Entity\BroadcastMessage::getContactTypes\(\) return type has no value type specified in iterable type array\.$#'
    identifier: missingType.iterableValue
    count: 1
    path: src/Entity/BroadcastMessage.php

Le count est le nombre d'occurrences figées pour ce couple message / fichier : PHPStan ne masque que ce volume exact. Si une nouvelle occurrence du même type apparaît dans le fichier, le compteur réel dépasse celui de la baseline et l'erreur est bien remontée. La baseline fige donc un état précis, elle ne neutralise pas un motif d'erreur pour toujours.

2. Inclure la baseline dans la config.

Le fichier généré se référence dans la section includes du phpstan.neon, avant les paramètres :

includes:
    - phpstan-baseline.neon
parameters:
    # ...

À partir de là, PHPStan ignore les erreurs figées et ne fait échouer la CI que sur les nouvelles régressions.

Committer le fichier. phpstan-baseline.neon doit impérativement être versionné dans le dépôt.

La CI travaille sur un checkout propre et a besoin du fichier pour savoir quelles erreurs ignorer : s'il n'est pas commité, elle ne le trouve pas, réanalyse tout et remonte à tort l'ensemble des erreurs pourtant figées dans la baseline, faisant échouer la pipeline sans raison.

3. Réduire la baseline dans le temps.

À chaque correction d'une erreur historique, on régénère la baseline puis on la commite pour verrouiller le progrès.

Pour que ce cycle reste honnête, l'option reportUnmatchedIgnoredErrors doit rester activée (true, sa valeur par défaut). Elle fait échouer l'analyse dès qu'une entrée de la baseline ne correspond plus à aucune erreur réelle, typiquement parce que l'erreur a été corrigée.

PHPStan signale ainsi les entrées devenues obsolètes et force à régénérer le fichier : sans elle, une erreur corrigée resterait figée à tort dans la baseline, qui ne se réduirait jamais et masquerait silencieusement des erreurs qui pourraient réapparaître.

parameters:
    reportUnmatchedIgnoredErrors: true

À distinguer de l'option ignoreErrors du phpstan.neon, réservée aux faux positifs ou aux erreurs qu'on assume de ne pas corriger : la baseline est un inventaire temporaire destiné à être résorbé, ignoreErrors est un choix délibéré souvent plus durable à bien documenter en commentaire dans le phpstan.neon.

Comment bien documenter un faux positif

Si un cas très spécifique de faux positif remonté par PHPStan intervient il est possible d'ignorer temporairement l'erreur :

Scénario local spécifique à une ligne d'un seul fichier :

Dans l'exemple suivant la méthode getNextSubscriptionBirthday renvoie une valeur qui peut être null.

Or cela déclenche l'erreur clone.nonObject qui dit qu'il n'est pas possible d'appeler clone depuis null.

PHPStan remontera toujours l'erreur même si cette ligne est dans un traitement qui vérifie en amont que la date de l'objet n'est pas null car le typage retourné par la méthode indique qu'elle peut retourner null.

Voici donc un exemple de comment documenter correctement un faux positif :

// qa: Faux positif déclenché par valeur null mais qui ne l'est pas ici @phpstan-ignore clone.nonObject
$nextSubscriptionBirthday = clone $organization->getNextSubscriptionBirthday();

Format à retenir du commentaire :

// qa: Faux positif %explication complémentaire% @phpstan-ignore %code identifier%

L'explication du commentaire doit est obligatoirement avant le code identifier sinon le phpstan-ignore interprétera la suite comme le code, c'est pourquoi il est placé à la fin.

Scénario volontaire multi fichier si la règle a moins de sens

Certains fichiers de test peuvent déjà être très verbeux (fixtures, initialisations d'objets complexes). Ajouter un @phpstan-ignore sur chaque ligne concernée alourdit le code sans bénéfice réel, surtout quand l'erreur est structurelle au contexte (ex : une méthode dont le retour peut être null utilisée dans une fixture d'initialisation).

Dans ce cas, il est possible de déclarer une règle ignoreErrors dans le phpstan.neon limitée à un dossier ou fichier précis, en ciblant l'identifier de l'erreur :

ignoreErrors:
    # method.nonObject ignoré dans tests/ : les fixtures initialisent des objets partiels
    # dont certaines methodes peuvent retourner null avant d'etre completement renseignes.
    -
        identifier: method.nonObject
        path: tests/

Cela supprime les erreurs "Cannot call method X on Y|null" dans tout le dossier tests/, ce qui est acceptable : dans un contexte de test, les fixtures construisent souvent des objets de façon volontairement incomplète, et PHPStan ne peut pas distinguer un vrai null problématique d'une initialisation intermédiaire. Cette configuration est à documenter en commentaire dans phpstan.neon pour expliquer pourquoi la règle est écartée sur ce périmètre.

FAQ

Les réponses à vos questions

Et si vous ne trouvez pas ce que vous cherchez, nous serons ravis de vous répondre en direct lors d'un rendez-vous entre humains !

Nous configurons PHPStan au niveau 10, le niveau max, dès le démarrage d'un nouveau projet Symfony. Le coût est quasi nul quand le typage est écrit dès le départ, et cela évite d'accumuler de la dette à corriger plus tard.

La baseline capture l'état actuel des erreurs et ne signale que les nouvelles erreurs introduites. Cela permet d'activer PHPStan immédiatement sur un projet existant sans bloquer le travail en cours, puis de réduire la baseline progressivement, sprint après sprint, en corrigeant les erreurs historiques. Pour une erreur précise trop impactante à corriger aujourd'hui, par exemple si elle nécessite un refactoring que l'on préfère planifier plus tard, l'option ignoreErrors du fichier phpstan.neon permet de l'ignorer explicitement et durablement sur tout le projet, indépendamment de la baseline.

Non, les deux outils sont complémentaires. PHPStan analyse le code sans l'exécuter et détecte des erreurs de type et de logique avant même d'écrire un test. PHPUnit vérifie le comportement réel du code à l'exécution. Un projet fiable utilise les deux : PHPStan en première ligne, PHPUnit pour valider la logique métier.

Très peu si l'outil est bien configuré. Les extensions phpstan-symfony et phpstan-doctrine éliminent la plupart des faux positifs liés aux spécificités du framework et de l'ORM. Sur du code legacy sans typage, un démarrage à un niveau bas puis une montée progressive limite également les faux positifs liés à un typage encore incomplet.

Les deux sont des analyseurs statiques PHP avec des objectifs similaires. PHPStan bénéficie d'une communauté plus large, d'un écosystème d'extensions plus riche, notamment pour Symfony et Doctrine, et d'une adoption plus répandue dans les projets professionnels. C'est pourquoi nous l'avons retenu comme standard sur tous nos projets.

L'impact est généralement limité grâce au cache de résultats intégré, qui ne réanalyse que les fichiers modifiés entre deux exécutions. Sur un projet de taille moyenne, l'analyse complète prend quelques dizaines de secondes. Le temps gagné en incidents évités en production dépasse largement ce coût en pipeline.

Non. La version open source gratuite couvre les besoins standards d'intégration en pipeline CI et en développement local, terminal et plugin PHPStorm compris. PHPStan Pro est un service complémentaire payant proposé par l'éditeur, avec une interface web pour parcourir les erreurs plus confortablement, mais il n'est pas nécessaire pour bénéficier des vérifications de PHPStan.

Oui. PHPStan s'adapte à l'état du code grâce à ses 11 niveaux progressifs : le niveau 0 se contente de vérifications basiques, classes et méthodes inconnues, variables indéfinies, sans exiger de typage. La baseline permet ensuite de figer les erreurs existantes et de n'exiger la rigueur que sur le nouveau code, ce qui rend l'adoption possible même sur un projet PHP ancien sans typehints.

Vous avez un projet ?

Contactez-nous pour savoir comment nous pouvons vous aider.