Cannot modify readonly property Configuration_Theme::$options : ce message, remonté dans les journaux d’erreurs après une mise à jour de l’hébergement vers PHP 8.3 en avril 2024, a mis plusieurs heures à être localisé, pour une raison simple — en production, l’écran affichait une page blanche silencieuse, pas le message d’erreur détaillé.
Le contexte : une classe utilitaire du thème, chargée de centraliser les réglages d’affichage utilisés par plusieurs template parts, avait été partiellement modernisée pour tirer parti des propriétés en lecture seule introduites par PHP 8.1 et généralisées dans les versions suivantes. Cette modernisation, faite avec de bonnes intentions, a cassé silencieusement un template part qui modifiait cette propriété après sa construction initiale.
Symptôme : un template part vide sans message visible
Le template part concerné, responsable de l’affichage d’un bandeau de mise en avant sur la page d’accueil, disparaissait purement et simplement du rendu public. Aucun message d’erreur n’apparaissait à l’écran, car l’affichage des erreurs PHP était désactivé sur l’environnement de production, une configuration par ailleurs recommandée pour ce type d’environnement.
Diagnostic : activer les journaux avant de chercher

La première étape du diagnostic a consisté à activer temporairement la journalisation des erreurs sans les afficher publiquement, via la configuration standard de WordPress :
define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', true );
define( 'WP_DEBUG_DISPLAY', false );
Le fichier debug.log a immédiatement révélé la cause exacte : une tentative de modification d’une propriété déclarée readonly après l’instanciation de l’objet, dans une méthode appelée depuis le rendu du template part concerné.
final class Configuration_Theme {
public function __construct(
public readonly array $options
) {}
public function ajouter_option( string $cle, mixed $valeur ): void {
$this->options[ $cle ] = $valeur; // Erreur : propriété readonly
}
}
Pourquoi cette erreur n’existait pas avant PHP 8.3
La classe fonctionnait sans problème sous PHP 8.1 et 8.2, car le mot-clé readonly y était déjà disponible mais la méthode fautive avait été ajoutée plus récemment, lors d’une évolution du thème postérieure à la dernière vérification de compatibilité. L’erreur n’a donc rien à voir avec un changement de comportement de PHP 8.3 lui-même : elle résulte d’une combinaison entre du code existant utilisant readonly et une nouvelle méthode ajoutée sans tenir compte de cette contrainte.
Correctif : reconstruire plutôt que modifier
Une propriété readonly ne peut recevoir de valeur qu’une seule fois, dans la portée de la classe qui la déclare, généralement dans le constructeur. Le correctif ne consiste donc pas à retirer le mot-clé (ce qui annulerait les garanties d’immutabilité recherchées lors de la modernisation), mais à reconstruire un nouvel objet avec les options mises à jour :
final class Configuration_Theme {
public function __construct(
public readonly array $options
) {}
public function avec_option( string $cle, mixed $valeur ): self {
return new self( array_merge( $this->options, array( $cle => $valeur ) ) );
}
}
// Utilisation dans le template part :
$configuration = $configuration->avec_option( 'affichage_bandeau', true );
Cette approche, courante en programmation dite immuable, oblige à réassigner la variable à chaque modification logique, mais garantit qu’aucune autre partie du code ne peut altérer l’état de l’objet à son insu, ce qui était précisément l’objectif recherché en introduisant readonly dans cette classe.
Prévention : tester la compatibilité avant la montée de version
- Activer temporairement
WP_DEBUG_LOGsur un environnement de préproduction avant toute montée de version PHP, même mineure en apparence. - Rechercher systématiquement, dans le code du thème, les usages du mot-clé
readonlycouplés à des méthodes de mutation ajoutées après coup. - Documenter, dans un commentaire au-dessus de chaque propriété
readonly, l’intention explicite d’immutabilité pour éviter qu’un futur développeur n’ajoute une méthode de modification par réflexe.
Une page blanche silencieuse en production n’est jamais une absence de problème : c’est un problème qui a simplement perdu son message.
En résumé
Une propriété readonly mal comprise, combinée à une méthode de mutation ajoutée après coup, suffit à casser silencieusement un template part en production. Le réflexe de diagnostic reste toujours le même : réactiver la journalisation avant de chercher à l’aveugle, puis corriger en reconstruisant l’objet plutôt qu’en contournant l’immutabilité voulue au départ.