Home Outils L’analyse statique PHP : un filet de sécurité pour les projets Symfony

L’analyse statique PHP : un filet de sécurité pour les projets Symfony

  Matthieu Cayet 10 min 9 juin 2026

Coder sans filet, vraiment ?

Dans le monde du développement PHP moderne, et particulièrement dans les projets Symfony, la qualité du code ne se mesure plus uniquement à l’exécution. L’analyse statique est permet de détecter des bugs, des incohérences de types et des mauvaises pratiques sans exécuter le code. C’est une pratique devenue incontournable pour toute équipe soucieuse de maintenabilité, de robustesse et de dette technique. Elle est le premier et le plus facile des filets de sécurité à mettre en place.
Cet article présente les principaux outils d’analyse statique et de qualité de code disponibles dans l’écosystème PHP, et explique comment les intégrer efficacement dans un projet Symfony.

Pourquoi l’analyse statique change vraiment la donne

Détection précoce des bugs

Un bug détecté lors de la phase de développement coûte infiniment moins cher qu’un bug détecté en production. L’analyse statique attrape :
  • Les erreurs de types (TypeError latents)
  • 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

Annoter son code pour satisfaire un analyseur statique, c’est aussi documenter ses intentions. Un @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.
Elle incite naturellement tous les développeurs à écrire du code en utilisant du typage strict et des PHPDoc précis.

Refactoring en confiance

Modifier une interface utilisée dans 40 services Symfony devient serein quand PHPStan vous dit immédiatement où vous avez cassé le contrat.

Uniformisation du code

Le code garde une uniformisation et une cohérence quelque soit les personnes qui interviennent sur le projet.

Automatisation naturelle

Ces outils s’intègrent parfaitement dans des outils de CI/CD comme GitHub Actions, GitLab CI ou autre. Un exit code 1 bloque la merge request, point. Plus de mauvaises surprise lors de la mise en production.
Ils sont aussi facilement utilisables avec des outils d’automatisation locaux tels que GrumPHP et CaptainHook qui viennent détecter dès qu’un commit est créer directement sur le poste du développeur.

Les outils de l’arsenal

PHP CS Fixer — le gardien du style

PHP CS Fixer analyse et corrige automatiquement votre code pour qu’il respecte les standards de style définis (PER-CS, Symfony coding standards…). Contrairement aux outils précédents, il ne cherche pas de bugs : il normalise la forme de votre code.

Installation

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

 

Configuration (.php-cs-fixer.php)

$finder = PhpCsFixer\Finder::create()
    ->in([__DIR__ . '/src', __DIR__ . '/tests'])
    ->exclude(['var', 'vendor']);

return (new PhpCsFixer\Config())
    ->setRules([
        '@PER-CS' => true,
        '@Symfony' => true,
        '@autoPHPMigration' => true,
        'array_syntax' => ['syntax' => 'short'],
        'ordered_imports' => ['sort_algorithm' => 'alpha'],
        'no_unused_imports' => true,
        'strict_comparison' => true,
    ])
    ->setFinder($finder);

Utilisation

# Vérifier sans corriger (pour la CI)
vendor/bin/php-cs-fixer fix --dry-run --diff

# Corriger
vendor/bin/php-cs-fixer fix

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
Conseil : configurez CS Fixer dans votre éditeur (PhpStorm, VS Code) pour qu’il s’applique à la sauvegarde. Vos git diff ne contiendront plus jamais de changements de style parasites.

PHPStan — l’analyse statique de référence

PHPStan est l’un des outils d’analyse statique les plus populaires de l’écosystème PHP. Son principe est simple : il analyse votre code PHP et détecte les erreurs sans avoir besoin de l’exécuter.

Installation

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

Configuration (phpstan.neon)

parameters:
    level: 6
    paths:
        - src
        - tests
    symfony:
        containerXmlPath: var/cache/dev/App_KernelDevDebugContainer.xml
    ignoreErrors:
        - '#Call to an undefined method Doctrine\\ORM\\QueryBuilder#'

Les niveaux de rigueur (0 à 10)

PHPStan propose 11 niveaux de rigueur croissante. Le niveau 0 détecte les erreurs les plus grossières (appels à des méthodes inexistantes), le niveau 10 impose une typage quasi-total incluant les types génériques.

Ce que PHPStan détecte

// PHPStan niveau 5+ détecte ce problème
class UserService
{
    public function findUser(int $id): User
    {
        return $this->repository->find($id); // ⚠️ retourne User|null, pas User !
    }
}
------ -----------------------------------------------
 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

Sur un projet existant avec des centaines d’erreurs, la baseline vous sauve la mise :
vendor/bin/phpstan analyse --generate-baseline
Cela génère un fichier phpstan-baseline.neon qui ignore les erreurs existantes. Il ne vous restera plus qu’à l’inclure dans votre fichier de configuration phpstan.neon :
includes:
	- phpstan-baseline.neon
Vous pouvez ainsi introduire PHPStan sans bloquer votre CI, et résorber la dette progressivement.

Rector — bien plus qu’un outil de migration

Rector est souvent présenté comme un outil de migration (PHP 7 → 8, Symfony 5 → 6…), et il excelle effectivement dans ce rôle. Mais le réduire à ça serait passer à côté de sa valeur la plus immédiate : résorber la dette technique existante, indépendamment de toute montée de version.
Là où PHPStan signale les problèmes, Rector les corrige. Il analyse et transforme votre code via son AST (Abstract Syntax Tree), en appliquant des règles configurables à la granularité que vous choisissez.

Installation

composer require --dev rector/rector
composer require --dev tomasvotruba/type-coverage

Rector comme outil de réduction de dette — sans migration

C’est l’usage le plus sous-estimé de Rector. Vous pouvez l’utiliser exclusivement sur des sets de qualité de code, sans toucher aux versions de PHP ou Symfony :
use Rector\Config\RectorConfig;
use Rector\Set\ValueObject\SetList;

return RectorConfig::configure()
    ->withPaths([__DIR__ . '/src', __DIR__ . '/tests'])
    ->withSets([
        SetList::DEAD_CODE,       // supprime le code mort
        SetList::CODE_QUALITY,    // simplifie les constructions inutilement complexes
        SetList::CODING_STYLE,    // uniformise les idiomes PHP
        SetList::TYPE_DECLARATION, // ajoute les types manquants là où ils sont inférables
    ]);
Ces quatre sets s’appliquent à n’importe quelle version de PHP et de Symfony. Voici ce que Rector corrige automatiquement :
Code mort et simplifications :
// Avant — condition toujours vraie, branche morte
if ($user instanceof User) {
    return $user->getName(); // $user est déjà typé User
}

// Après
return $user->getName();
// Avant — construction inutilement verbeuse
$result = [];
foreach ($items as $item) {
    $result[] = $item->getValue();
}
return $result;

// Après
return array_map(fn($item) => $item->getValue(), $items);
Ajout automatique des types manquants :
// Avant — types absents dans une classe dont Rector peut les inférer
class OrderService
{
    private $repository;

    public function __construct(OrderRepository $repository)
    {
        $this->repository = $repository;
    }

    public function find($id)
    {
        return $this->repository->find($id);
    }
}

// Après — types ajoutés automatiquement
class OrderService
{
    public function __construct(
        private readonly OrderRepository $repository,
    ) {}

    public function find(int $id): ?Order
    {
        return $this->repository->find($id);
    }
}
Modernisation des constructions PHP (indépendante de la version cible) :
// Avant
if ($user !== null) {
    return $user->getName();
}
return 'Anonymous';

// Après
return $user?->getName() ?? 'Anonymous';

Quand activer les sets de migration

Une fois la dette de base résorbée, vous pouvez activer des sets de migration ciblés selon votre contexte. Par exemple, pour migrer les annotations Doctrine/Symfony vers les attributs PHP 8 :
->withSets([
    SymfonySetList::ANNOTATIONS_TO_ATTRIBUTES,
    DoctrineSetList::ANNOTATIONS_TO_ATTRIBUTES,
])
// Avant (annotations)
/**
 * @Route("/api/users", methods={"GET"})
 * @IsGranted("ROLE_USER")
 */
public function list(): JsonResponse { ... }

// Après (attributs PHP 8) — appliqué automatiquement
#[Route('/api/users', methods: ['GET'])]
#[IsGranted('ROLE_USER')]
public function list(): JsonResponse { ... }

Mode dry-run : toujours vérifier avant d’appliquer

# Aperçu des changements sans les appliquer
vendor/bin/rector process --dry-run

# Application réelle
vendor/bin/rector process
Conseil de workflow : exécutez Rector en dry-run dans la CI pour que chaque merge request révèle le code qu’on aurait pu améliorer. Appliquez-le manuellement, en commits dédiés, pour garder un historique lisible et une revue de code sereine.

Levels : petit pas par petit pas

use Rector\Config\RectorConfig;

return RectorConfig::configure()
    ->withTypeCoverageLevel(0)
    ->withTypeCoverageDocblockLevel(0)
    ->withDeadCodeLevel(0)
    ->withCodeQualityLevel(0)
    ->withCodingStyleLevel(0);
Les levels vous permettent de commencer la réécriture de votre code en par les règles les moins risquées. Ainsi en corrigeant niveau par niveau vous vous assurer de fusionner progressivement l’amélioration de votre code.

Rector Swiss Knife — la boîte à outils complémentaire

Swiss Knife est un package de Rector qui fournit des utilitaires pratiques pour compléter améliorer la qualité de votre code sans les transformer en profondeur.

Installation

composer require --dev rector/swiss-knife

Fonctionnalités clés

Eviter d’avoir des restes de conflit Git dans le code :
vendor/bin/swiss-knife check-conflicts .
Détecter les blocs de code commentés :
vendor/bin/swiss-knife check-commented-code src tests
Finaliser les classes non étendues :
# Détecte les classes qui pourraient être déclarées final
vendor/bin/swiss-knife finalize-classes src
Vérifier que les fichiers ne contiennent qu’une seule classe :
vendor/bin/swiss-knife find-multi-classes src
S’assurer que le Namespace des Classes respecte l’arborescence :
vendor/bin/swiss-knife namespace-to-psr-4 src --namespace-root "App\\"

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"
        ]
    }
}
Les outils de qualité seront lancés avec la commande 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'
Astuce : Les quatres jobs s’exécutent en parallèle dans le même stage 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 ?

L’erreur classique est de vouloir tout activer d’un coup sur un projet legacy. Voici une approche pragmatique :
  1. 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.
  2. 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.
  3. 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.
  4. Peaufiner le projet → Ajoutez Swiss-knife pour ajouter les dernières amélioration possibles.

Conclusion

L’analyse statique n’est pas un luxe réservé aux grands projets : c’est une pratique que l’on peut facilement intégrer dès le premier commit qui vous fera gagner du temps et de la sérénité. PHPStan pour détecter, Rector pour transformer et résorber la dette, PHP CS Fixer pour uniformiser : ensemble, ces outils constituent un filet de qualité solide autour de votre code PHP.

Lire les articles similaires

Laisser un commentaire

Social Share Buttons and Icons powered by Ultimatelysocial