Aller au contenu principal
Retour au blog

PHPStan 2.3, Psalm 7, Mago : quelle stack statique pour Symfony

PHPStan 2.3 et Turbo, Psalm 7 et sa taint analysis, Mago : les trois outils testés sur les mêmes bugs Symfony, et la stack d’analyse statique à retenir.

Flavien Métivier · 9 min de lecture
Sommaire · 7 parties

Il y a un an, lancer PHPStan à froid sur une grosse base de code laissait le temps d’aller chercher un café. La 2.3 a changé ça, Psalm 7 se prépare, et Mago promet de tout analyser en une seconde. Plutôt que de comparer des fiches produit, nous avons écrit une application Symfony 7.4 truffée de bugs volontaires — erreurs de type, array shapes, DQL invalide, XSS, injection SQL — et regardé ce que chacun des trois outils trouve réellement.

En 2026, la stack d’analyse statique par défaut pour Symfony est PHPStan 2.3 avec ses extensions Symfony et Doctrine ; Psalm s’ajoute pour la taint analysis, et Mago reste un complément rapide. La question n’est plus « PHPStan ou Psalm », mais « quel rôle pour chaque outil ».

PHPStan 2.3 : ce qui change vraiment

La 2.3.0 (6 octobre 2026) est présentée par son auteur comme un « leap in performance ». Sur WordPress, une analyse à froid est passée de 60,5 secondes en décembre 2025 à 19,5 secondes sans Turbo et 7,9 avec — jusqu’à 7,5 fois plus vite en dix mois. Ces chiffres sont ceux de l’éditeur, sur WordPress, pas sur une application Symfony. Le changement le plus utile au quotidien est ailleurs : le cache de résultats n’est plus invalidé que par une mise à jour de PHPStan ou un changement de sa configuration. Avant, une mise à jour Composer ou une modification du container suffisait à forcer une ré-analyse complète. Mettre le répertoire temporaire de PHPStan en cache de CI devient donc rentable.

Côté détection, la 2.3 apporte en bleeding edge la détection des variables inutilisées et un narrowing générique plus fin. La 2.2 (28 mai) a introduit les array shapes non scellées, notées array{a: int, ...} ; une shape scellée — qui interdit toute clé non déclarée — deviendra le seul comportement « dans la prochaine majeure », dont aucune date n’est annoncée. Testé sur la 2.3.1 : passer ['a' => 1, 'b' => 2] à une fonction qui attend array{a: int} ne déclenche rien par défaut, et lève une erreur en bleeding edge. Le billet de la 2.2 prévient de possibles centaines de nouvelles erreurs sur les projets riches en DTO typés en tableaux : activez-le sur une branche d’essai avant qu’il ne s’impose. La montée de niveau elle-même est détaillée dans notre guide pour monter PHPStan jusqu’au niveau 10 sans casser la CI.

Turbo : comment il s’active réellement

Turbo est une extension C++ optionnelle livrée dans le paquet PHPStan. Il n’y a rien à installer pour un projet Composer : les binaires précompilés (Linux glibc et musl, macOS, Windows ; PHP 8.3 à 8.6) sont dans vendor/phpstan/phpstan/turbo-ext/, et PHPStan se relance lui-même avec l’extension. Nous n’avons trouvé ni clé de configuration ni variable pour le piloter. En revanche, son activation dépend de l’environnement, et phpstan diagnose le montre :

  • Image avec pcntl : « Turbo extension: enabled », workers lancés par fork. C’est la configuration complète.
  • Image sans pcntl (par exemple composer:2) : Turbo n’est actif que dans les workers, le processus principal tourne sans.
  • PHAR copié hors du vendor : « Turbo extension: not loaded ». Pour ce cas, la page Packagist de phpstan/turbo indique une installation avec PIE.

Nos mesures sur un projet de 610 fichiers, au niveau 9 : 1,4 seconde à froid avec Turbo contre 3,2 sans, soit environ 2,3 fois plus vite ; 0,77 contre 0,90 seconde à chaud. Sur un projet de taille modeste, le cache compte davantage que Turbo. Retenez surtout d’ajouter pcntl à votre image de CI et de lancer vendor/bin/phpstan, pas un PHAR isolé.

phpstan-symfony et phpstan-doctrine : l’avantage décisif

Ce qui fait de PHPStan le socle pour Symfony, ce sont ses extensions : elles modélisent le container et le mapping Doctrine. Un piège confirmé en test : sans phpstan/extension-installer, il faut inclure à la fois extension.neon et rules.neon de chaque paquet. Sans le rules.neon de phpstan-doctrine, une requête DQL invalide passe inaperçue.

yaml
# phpstan.dist.neon
includes:
    - vendor/phpstan/phpstan-symfony/extension.neon
    - vendor/phpstan/phpstan-symfony/rules.neon
    - vendor/phpstan/phpstan-doctrine/extension.neon
    - vendor/phpstan/phpstan-doctrine/rules.neon

parameters:
    level: 9
    tmpDir: var/phpstan            # à mettre en cache de CI
    paths:
        - src
    symfony:
        # généré par bin/console cache:warmup --env=dev
        containerXmlPath: var/cache/dev/App_KernelDevDebugContainer.xml
    doctrine:
        objectManagerLoader: tests/object-manager.php

Le fichier tests/object-manager.php démarre le kernel et retourne l’entity manager, pour que PHPStan lise le vrai mapping. Résultat : SELECT u.mail FROM App\Entity\User u est signalé (« Class App\Entity\User has no field or association named mail »). Ni Psalm ni Mago ne le font. Conséquence en CI : cache:warmup doit précéder PHPStan.

Psalm 6.20 et 7 : la taint analysis, avec ses limites

La taint analysis suit une donnée non fiable depuis sa source — une entrée HTTP — jusqu’à un point sensible : requête SQL, sortie HTML, commande shell. C’est la seule vraie raison d’ajouter Psalm à côté de PHPStan. Psalm 6.20.0 (9 octobre) est la version stable ; la 7.0.0-rc2, publiée le même jour, étend surtout ce volet. Ses prérequis déclarés vont de PHP 8.1 à 8.5 — PHP 8.6 n’y figure pas — et ses ruptures concernent l’API des plugins, les annotations de pureté et le stockage interne des taints.

Ce que nos tests montrent, et qui tempère beaucoup de présentations :

Votre dette technique s'accumule ?

Audit complet en 10 jours. Recommandations priorisées et actionnables.

Calculer mon audit →
  • Psalm 6.20 ne considère pas la Request Symfony comme une source. Seules les superglobales déclenchent une alerte : echo $_GET[...] donne bien un TaintedHtml.
  • Le plugin Symfony exige Psalm 7. psalm/plugin-symfony 5.4 requiert la branche 7. Avec la RC2 et ce plugin, new Response('<h1>' . $request->query->get('name')) est enfin signalé en TaintedHtml.
  • L’injection SQL via Doctrine DBAL n’est détectée dans aucune configuration : Connection::executeQuery() avec une chaîne concaténée passe en 6.20 comme en 7, avec ou sans plugin. mysqli_query est détecté, PDO::query() ne l’a pas été chez nous malgré ses annotations.
  • Psalm 7 en mode taint ajoute du bruit (MissingPureAnnotation, MissingImmutableAnnotation) qu’il faudra mettre en baseline.

Psalm n’est donc pas un scanner de sécurité complet pour une application Symfony : c’est un filet partiel, à valider sink par sink sur votre code. Pour l’usage sécurité plus large, notre article sur Psalm et Progpilot pour scanner les failles PHP complète le tableau. Une release candidate ne se pose pas non plus sur un pipeline de production sans test : épinglez 7.0.0-rc2 exactement si vous l’adoptez.

Mago analyze : l’étage rapide

Installation, configuration et limites de Mago sont détaillées dans notre article sur Mago en complément de PHPStan. Sur notre projet, mago analyze 1.57 répond en 0,6 à 0,7 seconde, dont près de 0,4 seconde de surcoût Docker. Il trouve les erreurs de type pures, mais ni les variables inutilisées, ni le DQL, ni les flux de taint : une superglobale affichée telle quelle donne une erreur de conversion, pas une vulnérabilité. Sa force est la vitesse, pas la profondeur Symfony.

Mêmes bugs, trois outils : le comparatif

Projet Symfony 7.4, Doctrine ORM 3.7.5, PHP 8.5.11 ; PHPStan au niveau 9, Psalm au niveau le plus strict, Mago par défaut :

  • Erreur de type, array shape incomplète, clé inexistante, méthode sur entité nullable : les trois outils détectent.
  • Variable inutilisée : PHPStan en bleeding edge seulement, Psalm avec findUnusedVariables, Mago non.
  • DQL invalide : PHPStan seul, grâce à phpstan-doctrine.
  • XSS via superglobale : Psalm seul, en --taint-analysis.
  • XSS via Request : Psalm 7 RC2 avec plugin-symfony seulement.
  • Injection SQL via DBAL ou PDO : aucun des trois. Via mysqli_query : Psalm seul.
  • Paramètre de container inexistant (getParameter('app.does_not_exist')) : aucun des trois.

Sur les temps, à titre indicatif pour 610 fichiers : PHPStan 1,4 seconde à froid avec Turbo, Psalm 3,1 à 3,5 secondes, Mago moins d’une seconde. À cette échelle, la vitesse n’oriente pas le choix ; la capacité de détection, si. Les écarts spectaculaires annoncés sur de très grosses bases viennent de benchmarks tiers que nous n’avons pas reproduits.

Quelle stack choisir selon votre contexte ?

  • Projet Symfony et Doctrine standard : PHPStan 2.3 avec phpstan-symfony et phpstan-doctrine, niveau 9 ou 10, cache persisté en CI, bleeding edge testé sur une branche. C’est suffisant dans la majorité des cas.
  • Application exposée, à enjeu de sécurité : la même base, plus un job Psalm limité à la taint analysis — en sachant ce qu’il ne voit pas, et sans le prendre pour un audit de sécurité.
  • Gros monolithe à CI lente : mesurez d’abord PHPStan avec cache chaud et Turbo ; ajoutez Mago en pre-commit si la boucle locale reste le goulot.
  • Petit projet : PHPStan seul. N’empilez pas trois analyseurs par principe.

Pour cumuler PHPStan et Psalm sans doublon de bruit, une règle simple : PHPStan bloque la CI sur les types, Psalm ne tourne qu’en taint analysis, dans un job séparé. Le workflow ci-dessous applique cette répartition ; les actions sont épinglées par SHA, comme nous le recommandions dans notre checklist supply chain.

yaml
# .github/workflows/static-analysis.yml
name: Static analysis
on: [pull_request]
permissions:
  contents: read
jobs:
  phpstan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
        with:
          persist-credentials: false
      - uses: shivammathur/setup-php@34a5396826718e0013f08e3e639d1c315d5f6b23 # 2.35.0
        with:
          php-version: '8.5'
          extensions: pcntl, posix   # fork des workers et Turbo complet
      - run: composer install --no-interaction --prefer-dist
      - run: php bin/console cache:warmup --env=dev
      - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
        with:
          path: var/phpstan
          key: phpstan-${{ hashFiles('phpstan.dist.neon') }}-${{ github.run_id }}
          restore-keys: phpstan-${{ hashFiles('phpstan.dist.neon') }}-
      - run: vendor/bin/phpstan analyse --no-progress

  psalm-taint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
        with:
          persist-credentials: false
      - uses: shivammathur/setup-php@34a5396826718e0013f08e3e639d1c315d5f6b23 # 2.35.0
        with:
          php-version: '8.5'
      - run: composer install --no-interaction --prefer-dist
      - run: vendor/bin/psalm -c psalm.taint.xml --taint-analysis --no-progress

PHPStan 2.3 a rendu la vitesse presque hors sujet ; Psalm 7 reste un pari partiel sur la sécurité ; Mago complète sans remplacer. Action immédiate : lancez PHPStan 2.3.1 avec et sans pcntl sur votre propre base, notez les temps à froid et à chaud, puis décidez avec vos chiffres, pas avec ceux de WordPress.

Choisir entre PHPStan, Psalm et Mago est une décision d’outillage. Savoir lesquelles des milliers d’alertes menacent vraiment votre production — et ce que vos outils ne voient pas, comme l’injection SQL via DBAL — demande de lire la base de code avec eux. C’est ce que fait un Bear Scan : un audit en dix jours qui transforme la sortie des analyseurs en une liste priorisée de correctifs.

Cet article vous a été utile ? Partagez-le.

Portrait de Flavien Métivier, fondateur de The Bearded Bear : barbe longue, lunettes, sur fond sombre

Écrit par

Flavien Métivier

Expert PHP/Symfony indépendant depuis 2014, ancien CTO de Kartable et d'Extrabat. Plus de 80 projets menés, de l'audit à la migration.

Mon parcours

À lire ensuite

Votre dette technique s'accumule ?

Audit complet en 10 jours. Recommandations priorisées et actionnables.

Newsletter Bear Bytes

Veille Claude Code, Symfony et PHP, retours d'expérience terrain et conseils techniques, directement dans votre boîte mail.