Fatal error: Uncaught ValueError: "redacteur_partenaire" is not a valid backing value for enum RoleUtilisateur
Ce message est apparu sur un projet qui modélisait les rôles utilisateurs via un enum PHP à valeurs (enum RoleUtilisateur: string), quelques minutes après la mise à jour automatique d’une extension de gestion de contenu partenaire. Le code de l’enum n’avait pas changé ; c’est la donnée reçue en entrée qui, soudainement, ne correspondait plus à aucun des cas déclarés.
Symptôme : le fatal apparaît sans modification du code
L’enum en question ressemblait à ceci :
enum RoleUtilisateur: string {
case Administrateur = 'administrateur';
case Redacteur = 'redacteur';
case Contributeur = 'contributeur';
public static function depuisRoleWordPress( string $role ): self {
return self::from( $role );
}
}
La méthode statique from(), native aux enums PHP à valeurs depuis PHP 8.1, lève strictement un ValueError si la chaîne fournie ne correspond à aucun cas déclaré — contrairement à un tableau associatif classique, qui aurait simplement renvoyé null sur une clé absente. C’est précisément ce comportement strict, généralement recherché pour éviter les erreurs silencieuses, qui a provoqué le fatal ici.
Diagnostic : une extension a introduit un rôle non prévu

L’investigation a montré que la mise à jour de l’extension partenaire avait ajouté, via add_role(), un nouveau rôle WordPress nommé redacteur_partenaire, destiné à un usage interne à cette extension. Le code applicatif, qui appelait RoleUtilisateur::depuisRoleWordPress( $wp_user->roles[0] ) pour chaque utilisateur affiché dans un tableau de bord personnalisé, s’est retrouvé à recevoir cette nouvelle valeur dès qu’un utilisateur test s’est vu attribuer ce rôle par l’extension.
Le problème ne venait donc ni d’une régression du code applicatif, ni d’un bug de l’extension à proprement parler : l’extension avait le droit d’ajouter un rôle, et le code applicatif n’avait simplement jamais anticipé qu’un rôle inconnu de son enum puisse un jour se présenter en entrée.
Correctif : un cas de repli explicite
La correction consiste à ne jamais utiliser from() directement sur une valeur dont l’origine n’est pas entièrement maîtrisée, et à privilégier tryFrom(), qui renvoie null plutôt que de lever une exception en cas de valeur inconnue :
enum RoleUtilisateur: string {
case Administrateur = 'administrateur';
case Redacteur = 'redacteur';
case Contributeur = 'contributeur';
case Inconnu = 'inconnu';
public static function depuisRoleWordPress( string $role ): self {
return self::tryFrom( $role ) ?? self::Inconnu;
}
}
Ajouter un cas explicite Inconnu plutôt que de renvoyer null directement permet de conserver un typage strict tout au long du code applicatif : toute logique qui consomme RoleUtilisateur continue de travailler avec une instance de l’enum, sans avoir à gérer un cas null supplémentaire à chaque appel.
Prévention : ne jamais faire confiance à une source externe pour un enum fermé
- Utiliser
tryFrom()systématiquement dès que la valeur provient d’une donnée externe au module qui définit l’enum - Réserver
from()aux cas où la valeur est produite par le même module, avec une garantie de cohérence à la compilation - Journaliser les valeurs inconnues rencontrées, pour détecter rapidement l’apparition d’un nouveau cas non prévu
public static function depuisRoleWordPress( string $role ): self {
$resultat = self::tryFrom( $role );
if ( null === $resultat ) {
error_log( sprintf( 'Rôle WordPress non modélisé rencontré : %s', $role ) );
return self::Inconnu;
}
return $resultat;
}
Une leçon plus large sur les enums fermés
Ce cas illustre une tension propre aux enums PHP à valeurs : leur intérêt principal réside dans leur caractère fermé et exhaustif, ce qui les rend particulièrement adaptés à un ensemble de valeurs entièrement maîtrisé par le même code. Dès qu’une valeur peut provenir d’une source externe — une extension tierce, une configuration modifiable, une API — cette fermeture devient un risque de rupture si elle n’est pas explicitement gérée avec un cas de repli.
Un enum qui modélise une donnée externe doit toujours prévoir le cas d’une valeur qu’il ne connaît pas encore.
En résumé
Le fatal observé ici ne venait ni d’un bug du cœur WordPress, ni d’une erreur de l’extension partenaire, mais d’une hypothèse implicite du code applicatif : que la liste des rôles resterait figée. Remplacer from() par tryFrom() avec un cas de repli explicite, dès qu’un enum modélise une donnée dont la source peut évoluer indépendamment du code qui le consomme, évite ce type de rupture en production.