Fin 2020, WordPress 5.6 a officiellement ajouté la prise en charge de PHP 8, sorti la même année. Un an plus tard, plusieurs hébergeurs mutualisés proposaient PHP 8.0 par défaut sur les nouvelles installations, et nos clients ont commencé à nous demander de vérifier la compatibilité de leurs extensions métier maison. Ce retour d’expérience porte sur la migration d’une extension de gestion de commandes B2B, environ dix mille lignes de code PHP écrites entre 2016 et 2019, jamais pensée pour PHP 8 à l’origine.
Contrairement à ce que la peur ambiante autour des « breaking changes » de PHP 8 pouvait laisser penser, la migration s’est révélée rapide : trois jours de travail effectif, tests inclus, pour une extension de cette taille. Voici les points qui ont réellement posé problème, et ceux qui, malgré leur réputation, n’ont causé aucun souci.
Ce qui a effectivement cassé
Les fonctions de chaînes avec un argument null
PHP 8.1 (sorti fin novembre 2021, donc encore récent au moment de cette migration) a commencé à déprécier le passage de null aux paramètres non nullables de nombreuses fonctions internes comme strlen(), trim() ou str_replace(). Sous PHP 8.0, ce comportement était encore silencieusement toléré mais déjà signalé par un avertissement en environnement de développement avec WP_DEBUG actif.
// Cassait avec un avertissement de dépréciation sous PHP 8.1
$reference = trim( $commande->reference ); // reference peut être null en base
// Correction systématique
$reference = trim( (string) $commande->reference );
// ou, plus lisible :
$reference = trim( $commande->reference ?? '' );
Ce fut, de loin, le correctif le plus fréquent : une quarantaine d’occurrences dans le code, principalement autour des champs de métadonnées facultatifs récupérés via get_post_meta(), qui retourne une chaîne vide et non null, mais dont certaines valeurs héritées de l’ancienne version de l’extension étaient réellement null en base pour des enregistrements anciens.
Un tri de tableau devenu stable
PHP 8.0 a rendu le tri des tableaux stable par défaut (deux éléments de même valeur conservent leur ordre relatif d’origine). Un comportement globalement positif, mais qui a changé l’ordre d’affichage d’une liste de produits triée par une fonction de comparaison personnalisée avec usort(), où l’ancien tri instable masquait involontairement un bug de comparaison préexistant. La correction a consisté à revoir la fonction de comparaison elle-même plutôt qu’à contourner le nouveau comportement.

Ce qui n’a, en pratique, posé aucun problème
Contrairement aux craintes initiales, plusieurs changements réputés « cassants » de PHP 8 n’ont eu aucun impact sur cette extension :
- Les erreurs de type
TypeErrorsur les arguments internes mal typés : le code, bien que non typé explicitement, passait déjà des types cohérents dans l’immense majorité des cas. - La suppression de certaines fonctions dépréciées depuis longtemps (
create_function(),each()) : absentes du code car déjà signalées par les linters utilisés en amont. - Les changements de tri par clé dans certaines fonctions de tableau : l’extension ne dépendait d’aucun ordre implicite non documenté à cet endroit.
Ce qui a été amélioré, pas juste corrigé
La migration a aussi été l’occasion d’adopter des fonctionnalités PHP 8 qui simplifient réellement le code existant.
// Avant : recherche de sous-chaîne verbeuse
if ( strpos( $statut, 'annule' ) !== false ) { /* ... */ }
// Après PHP 8 : lisibilité immédiate, plus d'erreur classique sur le === false
if ( str_contains( $statut, 'annule' ) ) { /* ... */ }
// Avant : un switch de huit cas
switch ( $type_commande ) {
case 'standard':
$delai = 3;
break;
case 'express':
$delai = 1;
break;
default:
$delai = 5;
}
// Après PHP 8 : match, plus concis, sans fall-through possible
$delai = match ( $type_commande ) {
'standard' => 3,
'express' => 1,
default => 5,
};
match présente un avantage de sécurité par rapport à switch : la comparaison est stricte (équivalent à ===) et l’absence de break élimine tout risque d’oubli provoquant un fall-through silencieux vers le cas suivant, une classe de bug qui avait déjà causé un incident en production sur cette même extension, des années plus tôt.
Le vrai piège : les callbacks dynamiques et les arguments nommés
Un point plus subtil a nécessité une vérification manuelle attentive : les hooks WordPress qui appellent un callback avec des arguments positionnels stricts. PHP 8 introduit les arguments nommés (ma_fonction( id: 42, statut: 'actif' )), une fonctionnalité que l’extension n’utilisait pas mais qui aurait pu entrer en conflit avec des callbacks dont les noms de paramètres ne correspondaient pas exactement à ceux attendus par des filtres tiers, si une future évolution du code avait adopté cette syntaxe sans vérification. Ce risque, bien que non concrétisé sur cette migration, a été documenté pour l’équipe.
Notre conclusion après cette migration, et plusieurs autres menées depuis sur des extensions similaires : la peur de PHP 8 est largement disproportionnée par rapport au risque réel, à condition que le code ait déjà respecté des pratiques raisonnables de typage implicite. Le vrai travail se situe dans les zones où les données en base sont plus permissives que ce que le code suppose.
La méthode de test qui a limité les mauvaises surprises
- Activer
WP_DEBUGetWP_DEBUG_LOGsur un environnement de recette sous PHP 8.0, et parcourir manuellement chaque écran de l’extension en observant le fichier de log - Lancer la suite de tests existante (PHPUnit) sous PHP 8.0 avant toute correction, pour établir une base de référence des échecs
- Utiliser
wp plugin verify-checksumset un simplephp -lsur chaque fichier pour écarter d’emblée les erreurs de syntaxe grossières
En résumé
La migration d’une extension métier vers PHP 8 s’est révélée moins coûteuse que redouté, à condition de traiter sérieusement le sujet des valeurs null transmises à des fonctions de chaînes. Les nouveautés du langage — match, str_contains(), la coercition explicite des types — ont, en prime, permis de simplifier une partie du code existant à l’occasion de cette mise à niveau.