Introduction
Avec chaque nouvelle version majeure de PHP, les dépréciations d'aujourd'hui deviennent les erreurs fatales de demain. PHP 8.5 ne fait pas exception, et si votre suite de tests tourne en mode strict, vous avez peut-être déjà croisé des avertissements qui méritent toute votre attention.
Cet article s'appuie sur le retour d'expérience de Russell Jones (source originale sur dev.to), qui a mené un chantier méthodique sur un monorepo PHP : partir de 34 dépréciations PHPUnit, les regrouper en patterns, et descendre à zéro — sur 7 497 tests et 18 118 assertions. Voici ce que cela enseigne concrètement sur deux points critiques : la fin de setAccessible() dans l'API Reflection et le nettoyage des propriétés dynamiques.
1. La fin de Reflection*::setAccessible() : 22 sites à corriger
La catégorie la plus volumineuse de l'exercice concernait ReflectionMethod::setAccessible() et ReflectionProperty::setAccessible(). Ces méthodes, largement utilisées dans les tests pour accéder à des membres privés ou protégés, sont dépréciées en PHP 8.1 et leur suppression se profile dans les versions suivantes.
Pourquoi ce pattern est si répandu dans les tests ?
Le cas classique ressemble à ceci :
$ref = new ReflectionProperty(MyService::class, 'privateValue');
$ref->setAccessible(true);
$ref->setValue($instance, 'test_value');
Depuis PHP 8.1, appeler setAccessible(true) est un no-op : les méthodes getValue(), setValue(), invoke() etc. sont accessibles par défaut via Reflection, sans avoir besoin d'appel préalable. Le correctif est donc mécanique :
// Avant (déprécié)
$ref = new ReflectionProperty(MyService::class, 'privateValue');
$ref->setAccessible(true);
$ref->setValue($instance, 'test_value');
// Après (PHP 8.1+)
$ref = new ReflectionProperty(MyService::class, 'privateValue');
$ref->setValue($instance, 'test_value');
Suppression pure et simple de la ligne setAccessible(true). Sur 22 sites, le volume est significatif mais chaque correction est triviale — idéale pour un passage automatisé via un script de remplacement ou un outil comme rector.
Ce que cela révèle sur votre base de code
Si vous avez de nombreux appels à setAccessible(), c'est souvent le symptôme d'un design où l'on teste l'implémentation interne plutôt que le comportement observable. C'est une opportunité de refactoring plus profond : peut-être que certaines propriétés privées mériteraient d'être exposées via une méthode, ou que la logique devrait être extraite dans une classe testable directement.
2. Les propriétés dynamiques : un héritage à solder
Le deuxième pattern majeur concerne les propriétés dynamiques (dynamic properties), dépréciées depuis PHP 8.2. Une propriété dynamique, c'est quand on assigne une propriété non déclarée directement sur un objet :
class User {
public string $name;
}
$user = new User();
$user->name = 'Alice'; // OK : propriété déclarée
$user->role = 'admin'; // ⚠️ Propriété dynamique, dépréciée en 8.2
Les solutions disponibles
Option 1 — Déclarer explicitement la propriété
C'est la solution privilégiée quand la propriété a une sémantique claire :
class User {
public string $name;
public string $role = '';
}
Option 2 — Utiliser #[AllowDynamicProperties]
Pour les classes qui ont légitimement besoin d'un comportement dynamique (wrappers, Value Objects génériques, proxies), PHP 8.2 introduit l'attribut dédié :
#[AllowDynamicProperties]
class FlexibleDataBag {
// Les propriétés dynamiques sont autorisées explicitement
}
Option 3 — Implémenter __get / __set
Pour les cas où la logique d'accès doit être contrôlée :
class Config {
private array $data = [];
public function __set(string $name, mixed $value): void {
$this->data[$name] = $value;
}
public function __get(string $name): mixed {
return $this->data[$name] ?? null;
}
}
Dans le contexte des tests
Les propriétés dynamiques apparaissent fréquemment dans les stubs et fixtures de test. Un objet partiel créé à la main sans respecter le contrat de la classe peut facilement en générer. Là encore, c'est un signal : vos factories de test ou vos builders méritent peut-être une revue.
3. Méthode : transformer 34 warnings en 0 de façon systématique
Ce qui rend ce retour d'expérience instructif, c'est la démarche autant que les corrections elles-mêmes.
Activer le mode strict PHPUnit est le prérequis. Par défaut, PHPUnit peut laisser passer des dépréciations sans les comptabiliser comme échecs. En mode strict, chaque dépréciation est capturée et comptée. Votre phpunit.xml devrait inclure :
<phpunit failOnDeprecation="true">
Ou via l'attribut de configuration selon votre version de PHPUnit.
Grouper les warnings par pattern avant de corriger. Les 34 avertissements se réduisaient à 3 catégories distinctes. Attaquer site par site sans cartographier d'abord, c'est travailler en aveugle.
Utiliser Rector pour l'automatisation. L'outil Rector dispose de règles préconfigurées pour les migrations PHP, notamment RemoveSetAccessibleRule pour exactement ce cas de figure. Sur un monorepo de taille réelle, l'automatisation n'est pas un luxe.
Valider en CI à chaque étape. Le critère de sortie du chantier était mesurable : zéro dépréciation dans la matrice CI. Pas une estimation, pas un ressenti — un nombre.
Conclusion
Les dépréciations PHP 8.5 ne sont pas des détails cosmétiques. setAccessible() et les propriétés dynamiques représentent des patterns qui ont pu sembler pratiques mais qui fragilisent la maintenabilité et la compatibilité future du code. La bonne nouvelle : les corrections sont mécaniques, automatisables, et la dette se solde rapidement quand on s'appuie sur une suite de tests solide en mode strict.
Si vous n'avez pas encore activé failOnDeprecation dans votre configuration PHPUnit, c'est le premier geste à poser. Ce que vous découvrirez dans vos logs est peut-être inconfortable — mais mieux maintenant qu'en urgence lors du prochain upgrade.
Chez MulerTech, la migration proactive vers les versions PHP récentes fait partie de notre approche qualité sur les projets Symfony. N'hésitez pas à nous contacter si vous souhaitez accompagner votre équipe sur ce type de chantier.