$ php -l extension-catalogue.php ne révèle rien : le fichier est syntaxiquement valide, l’extension s’active sans erreur, et pourtant le lien « Réglages » qui apparaît sous son nom dans la liste des extensions renvoie vers une page blanche après un récent refactoring. Ce genre de régression ne casse rien de visible immédiatement, ce qui la rend particulièrement sournoise : elle attend qu’un administrateur clique sur ce lien précis pour se manifester.
Le contexte : une extension de gestion de catalogue produit avait vu sa classe principale renommée de Catalogue_Plugin à Catalogue_Core lors d’un nettoyage de nommage. Le fichier principal, lui, n’avait pas changé de nom. C’est précisément cette asymétrie qui a provoqué le problème.
Symptôme : un lien qui pointait vers une page fantôme
Le lien de réglages ajouté via le filtre plugin_action_links_{$plugin_file} pointait vers options-general.php?page=catalogue_plugin, alors que la page de réglages réelle était désormais enregistrée sous le slug catalogue_core. Résultat : un clic sur « Réglages » menait vers l’écran général des réglages WordPress, sans message d’erreur, sans notice, juste une page qui n’était pas celle attendue.
Diagnostic : l’URL construite à partir du nom de classe

Le code fautif construisait dynamiquement le slug de la page de réglages à partir du nom de la classe principale, via get_class( $this ), plutôt que d’utiliser une constante ou un slug fixe défini une fois pour toutes.
class Catalogue_Core {
public function ajouter_lien_reglages( $liens ) {
$slug = strtolower( get_class( $this ) ); // devient "catalogue_core" après renommage
$url = admin_url( 'options-general.php?page=' . $slug );
$liens[] = '<a href="' . esc_url( $url ) . '">Réglages</a>';
return $liens;
}
}
Tant que la classe s’appelait Catalogue_Plugin, le slug calculé correspondait par coïncidence au slug réellement enregistré via add_options_page() ailleurs dans le code. Le renommage de la classe a rompu cette coïncidence, sans qu’aucun message d’erreur ne signale le décalage : PHP a simplement construit une URL syntaxiquement valide, mais fonctionnellement fausse.
Pourquoi ce genre de bug reste invisible en test rapide
Un test superficiel de l’extension après le refactoring — activation, vérification que l’écran principal fonctionne — ne révèle rien, car l’écran de réglages reste accessible directement par son URL réelle. Seul le lien contextuel de la liste des extensions est cassé, un élément que peu de suites de tests automatisées vérifient.
Correctif : ancrer le lien sur une constante de slug
La correction consiste à définir le slug de la page de réglages une seule fois, sous forme de constante ou de propriété de classe explicite, et à le réutiliser partout où il est nécessaire : à l’enregistrement de la page, et dans la construction du lien d’action.
class Catalogue_Core {
const SLUG_REGLAGES = 'catalogue-reglages';
public function enregistrer_page_reglages() {
add_options_page(
'Réglages du catalogue',
'Catalogue',
'manage_options',
self::SLUG_REGLAGES,
array( $this, 'afficher_page_reglages' )
);
}
public function ajouter_lien_reglages( $liens ) {
$url = admin_url( 'options-general.php?page=' . self::SLUG_REGLAGES );
array_unshift( $liens, '<a href="' . esc_url( $url ) . '">Réglages</a>' );
return $liens;
}
}
register_activation_hook( __FILE__, array( 'Catalogue_Core', 'activer' ) );
add_filter(
'plugin_action_links_' . plugin_basename( __FILE__ ),
array( new Catalogue_Core(), 'ajouter_lien_reglages' )
);
Le hook plugin_action_links_{$plugin_file} dépend du chemin relatif du fichier principal de l’extension, obtenu via plugin_basename( __FILE__ ). Ce chemin ne change pas lors d’un simple renommage de classe interne, ce qui garantit la stabilité du filtre lui-même. Le vrai risque venait de la donnée dérivée à l’intérieur, pas du hook.
Ce qu’il faut vérifier après tout renommage de classe
- Rechercher dans tout le code les usages de
get_class(),__CLASS__oustatic::classemployés pour construire une valeur fonctionnelle, et non un simple message de log. - Vérifier que chaque slug de page d’administration provient d’une constante unique, jamais recalculé à partir d’un nom de classe.
- Cliquer manuellement sur chaque lien d’action de la liste des extensions après un refactoring de nommage, même mineur.
Un nom de classe est un détail d’implémentation ; un slug d’URL est un contrat. Ne jamais dériver le second du premier.
En résumé
Ce bug illustre un principe plus large : toute valeur qui doit rester stable dans le temps, comme un slug d’URL ou une clé d’option, doit être définie explicitement, jamais déduite d’un élément susceptible de changer lors d’un refactoring, comme un nom de classe ou de méthode.