« Deprecated: Implicit conversion from float-string to int is deprecated » — ce message traîne dans les logs de production depuis la mise à jour vers PHP 8.3, mais la suite PHPUnit du projet reste verte, ligne après ligne. C’est le signe d’un problème plus profond que le message lui-même : la configuration des tests n’écoute pas ce que PHP a pourtant crié.
PHP 8.3 a ajouté de nouvelles dépréciations, notamment autour des méthodes magiques dynamiques et des propriétés non déclarées explicitement dans certaines classes. Par défaut, ces avertissements ne stoppent rien : ils s’affichent, ou pas, et l’exécution continue comme si de rien n’était.
Comprendre pourquoi PHPUnit reste silencieux
PHPUnit ne convertit les erreurs PHP en échecs de test que si on le lui demande explicitement. Sans configuration, un E_DEPRECATED est simplement journalisé selon la valeur de error_reporting, puis ignoré par le framework de test. Le test passe, le comportement problématique reste invisible jusqu’à ce qu’il devienne une vraie erreur dans une version PHP ultérieure.
Le fichier phpunit.xml du projet contenait encore convertDeprecationsToExceptions="false", un réglage hérité d’une époque où le bruit des dépréciations WordPress rendait la suite illisible. Ce choix, raisonnable en 2020, était devenu une faille de couverture en 2023.
Rendre les dépréciations visibles sans casser la suite entière

La solution brutale — activer convertDeprecationsToExceptions="true" partout — a fait exploser 340 échecs d’un coup, la plupart venant du cœur WordPress lui-même et hors de portée du projet. Il fallait un filtre plus fin.
set_error_handler(function ($errno, $errstr, $errfile) {
if ($errno === E_DEPRECATED && strpos($errfile, '/wp-content/plugins/mon-plugin/') !== false) {
throw new \PHPUnit\Framework\Error\Deprecated($errstr, $errno, $errfile);
}
return false;
}, E_DEPRECATED);
Ce handler personnalisé, chargé dans le bootstrap des tests, ne transforme en échec que les dépréciations émises par le code du plugin lui-même. Le bruit du cœur WordPress continue d’être journalisé, sans polluer le résultat de la suite.
Cibler les fonctions réellement concernées par PHP 8.3
Trois cas ont émergé de cette chasse : un cast implicite de chaîne numérique en entier dans une boucle de pagination, une comparaison utilisant == sur un objet DateTime là où === était attendu, et l’accès à une propriété dynamique non déclarée sur une classe sans #[AllowDynamicProperties].
- Cast implicite float-string vers int dans un compteur de pages
- Propriété dynamique non déclarée sur une classe de service interne
- Appel d’une méthode magique
__getsur un attribut absent
Adapter la CI plutôt que la machine locale
Un développeur travaillant en local sous PHP 8.1 ne verrait jamais ces messages. La CI a donc été configurée pour exécuter systématiquement un job dédié sous PHP 8.3, avec le handler strict activé, séparé du job principal qui reste tolérant pour ne pas bloquer les contributions en cours.
Ce job supplémentaire ajoute environ deux minutes par exécution, mais il a permis de corriger les trois cas cités avant qu’ils ne deviennent des erreurs fatales lors d’une future montée de version PHP.
Un avertissement PHP qui ne fait échouer aucun test n’est pas résolu, il est juste reporté à plus tard — souvent au pire moment.
Vérifier que la prévention tient dans le temps
Pour éviter que ce filtre ne devienne lui-même un point mort, un test unitaire vérifie que le handler personnalisé est bien enregistré au démarrage de la suite, et qu’il transforme effectivement un E_DEPRECATED simulé en exception. Sans ce garde-fou, une future réécriture du bootstrap pourrait supprimer silencieusement la protection.
En résumé
Le réglage par défaut de PHPUnit sur les dépréciations privilégie le confort immédiat au détriment de la détection précoce. Un handler ciblé, limité au code du projet, a permis de retrouver un signal exploitable sans revenir au bruit généralisé d’une conversion totale en exceptions. La leçon tient en une phrase : une suite verte n’est une bonne nouvelle que si elle écoute vraiment tout ce que PHP lui dit.