Le WordPress d'aujourd'hui, décodé pour les développeurs

Tests

PHP 8.4 : property hooks et leur impact sur les mocks PHPUnit

PHP 8.4 introduit les property hooks. Sur un projet WordPress migré tôt, plusieurs doublures de test qui interceptaient des accesseurs ont cessé de fonctionner comme prévu.

Par Clément Hadrot • 7 décembre 2024 • 4 min de lecture • Aucun commentaire
PHP 8.4 : property hooks et leur impact sur les mocks PHPUnit

public string $status { get => $this->normalizeStatus(); set => $this->status = strtolower($value); } — cette syntaxe, nouvelle en PHP 8.4, a suffi à faire échouer onze tests sur une extension de gestion de stock que nous maintenons depuis 2021. Rien n’avait changé dans la logique métier. Le coupable : une propriété que nous avions migrée vers un property hook pour éviter deux méthodes getStatus() et setStatus() devenues redondantes.

Les property hooks permettent d’attacher un comportement get et set directement à une propriété, sans passer par des méthodes dédiées. C’est élégant, cela réduit le code de plomberie, mais cela change la façon dont PHPUnit — et surtout les doublures de test construites autour des anciens accesseurs — interagissent avec l’objet. Ce billet documente ce que nous avons dû corriger, pas ce qui a changé côté propriétés dynamiques de PHP 8.2, déjà traité par ailleurs.

Ce que les property hooks changent concrètement

Avant PHP 8.4, une classe qui voulait valider ou transformer une valeur à l’écriture exposait généralement une méthode setStatus(string $status): void. Les tests créaient un mock de la classe et vérifiaient que setStatus était appelée avec le bon argument, via $mock->expects($this->once())->method('setStatus')->with('publie'). Avec un property hook, il n’y a plus de méthode à espionner : l’affectation $commande->status = 'publie' déclenche directement le hook set, sans passer par un appel de méthode visible pour PHPUnit.

Résultat : les mocks construits avec createMock() ou getMockBuilder() qui reposaient sur l’interception d’un appel de méthode ne voient plus rien passer. Le test ne plante pas toujours — parfois il passe silencieusement sans avoir vérifié ce qu’il croyait vérifier, ce qui est pire qu’un échec franc.

Où cela casse dans une suite WordPress typique

Sur nos projets, les property hooks sont surtout utiles dans les classes de domaine — un objet Commande, un objet Adhesion — qui ne dépendent pas directement des hooks WordPress. Trois zones ont posé problème :

  • Les classes qui exposaient un accesseur uniquement pour permettre le test (anti-pattern qu’on croyait avoir éliminé, réapparu sous une autre forme)
  • Les traits partagés entre plusieurs types d’objets, où un seul hook modifiait le comportement attendu par des tests écrits pour l’ancienne API
  • Les sérialisations vers des tableaux (toArray()) qui lisaient directement la propriété plutôt que de passer par le hook get, créant une désynchronisation entre la valeur stockée et la valeur exposée
L'essentiel à retenir : Les hooks get/set remplacent des accesseurs manuels testés par mock ; Un mock sur une propriété hookée ne se comporte plus comme avant ; Prévoir une phase de revue ciblée plutôt qu'une réécriture globale

La correction que nous avons appliquée

Plutôt que de réécrire les mocks, nous avons choisi de tester le résultat observable plutôt que l’appel intermédiaire. Un test qui vérifiait $mock->expects($this->once())->method('setStatus') a été remplacé par une assertion sur l’état final de l’objet après affectation :

public function test_status_est_normalise_en_minuscules(): void
{
    $commande = new Commande();
    $commande->status = 'PUBLIE';

    $this->assertSame('publie', $commande->status);
}

Cette approche est en réalité plus robuste : elle teste le comportement, pas l’implémentation. Le hook peut changer de forme demain sans casser le test, tant que le contrat — normaliser en minuscules — reste respecté.

Cas où le mock reste nécessaire

Quand la propriété hookée déclenche un effet de bord externe (écriture en base, appel à une API), le mock garde son intérêt, mais il doit cibler la dépendance injectée dans le hook, pas la propriété elle-même. Nous injectons désormais un service de journalisation dans le constructeur et vérifions ses appels, plutôt que d’essayer d’intercepter l’affectation de propriété.

Prévenir la régression sur les prochains projets

Nous avons ajouté une règle PHPStan personnalisée qui signale toute propriété hookée testée uniquement via un mock d’accesseur — un signal faible, mais suffisant pour déclencher une relecture manuelle avant fusion. Sur le projet audité, cela représentait quatorze tests sur une centaine, soit une proportion suffisante pour justifier une revue systématique plutôt qu’un correctif au cas par cas.

Un mock qui passe sans avoir vérifié ce qu’il prétend vérifier est plus dangereux qu’un test qui échoue franchement.

Notre verdict

Les property hooks de PHP 8.4 sont un gain net pour la lisibilité du code de domaine, mais ils ne sont pas neutres pour une suite de tests construite autour des mocks de méthode. La migration ne demande pas de réécrire la suite, seulement d’identifier les tests qui interceptaient des accesseurs et de les recentrer sur l’état observable de l’objet. Documentation officielle utile pour approfondir : php.net.

Partager :

À propos de l'auteur

Clément Hadrot

Développeur WordPress, passionné par Elementor, le FSE et l’automatisation par IA.

Voir tous ses articles

Dans la même veine

À lire aussi