Il vous est peut-être déjà arrivé de tomber sur cette ancienne erreur PHP lors de l’exécution d’un vieux projet :
Redefining already defined constructor for class ExampleClassLangage du code : JavaScript (javascript)
À l’origine, cette erreur apparaissait souvent dans du code prévu pour rester compatible avec plusieurs générations de PHP. Une classe contenait à la fois un constructeur moderne __construct() et un ancien constructeur portant le même nom que la classe.
En 2026, la bonne solution n’est plus d’intervertir deux méthodes pour satisfaire une vieille compatibilité. La bonne solution est de supprimer l’ancien constructeur et de ne garder que __construct().
Depuis PHP 8.0, une méthode qui porte le même nom que sa classe n’a plus de signification spéciale. Le manuel PHP précise que les anciens constructeurs nommés comme la classe n’étaient interprétés comme constructeurs que dans les classes du namespace global avant PHP 8.0. Depuis PHP 8.0, une méthode portant le même nom que la classe n’est plus traitée comme un constructeur. Voir la documentation PHP sur les constructeurs.
PHP 8.5 continue cette trajectoire de modernisation du langage. Donc, si votre objectif est de migrer vers PHP 8.5+, traitez cette erreur comme un signal clair : ce code doit être modernisé, pas simplement patché.
Le problème : une classe contient deux constructeurs historiques
Dans les vieux projets, on rencontrait souvent ce genre de classe :
<?php
class SkymindsExampleClass {
public function SkymindsExampleClass() {
$this->__construct();
}
public function __construct() {
$this->admin_page();
}
private function admin_page() {
// Register or render the admin page.
}
}Langage du code : HTML, XML (xml)
Le but était de faire fonctionner la classe avec de très anciennes versions de PHP tout en supportant __construct(). Cela avait un sens à une époque. Aujourd’hui, ce code est un fossile.
Sur PHP 8+, la méthode SkymindsExampleClass() n’est plus un constructeur. Elle devient une méthode ordinaire. Si personne ne l’appelle explicitement, elle ne sert à rien.
Le guide de migration PHP 8.0 le dit clairement : les méthodes qui portent le même nom que leur classe ne sont plus interprétées comme des constructeurs ; il faut utiliser __construct() à la place. Voir les changements incompatibles de PHP 8.0.
La mauvaise correction : garder l’ancien constructeur
L’ancienne solution consistait à placer __construct() avant la méthode portant le nom de la classe :
<?php
class SkymindsExampleClass {
public function __construct() {
$this->admin_page();
}
public function SkymindsExampleClass() {
$this->__construct();
}
private function admin_page() {
// Register or render the admin page.
}
}Langage du code : HTML, XML (xml)
Cette correction pouvait suffire à calmer certaines versions anciennes de PHP, mais elle ne modernise rien. Elle conserve une méthode inutile, ambiguë, et potentiellement trompeuse pour les développeurs qui liront le code ensuite.
Pour PHP 8.5+, ne faites pas cela. Supprimez l’ancien constructeur.
La bonne correction pour PHP 8.5+ : ne garder que __construct()
Version modernisée :
<?php
declare(strict_types=1);
final class SkymindsExampleClass {
public function __construct() {
$this->register_admin_page();
}
private function register_admin_page(): void {
// Register or render the admin page.
}
}Langage du code : HTML, XML (xml)
Ce code est plus clair :
- un seul constructeur ;
- pas de compatibilité morte ;
- pas de méthode magique déguisée ;
- un nom de méthode plus explicite ;
- un type de retour
void; strict_types=1;- une classe
finalsi elle n’a pas vocation à être étendue.
C’est le genre de nettoyage qui paraît minuscule, mais qui évite une dette technique tenace. Le vieux constructeur, c’est le câble VGA au fond du tiroir : il a probablement rendu service, mais il est temps d’arrêter de construire autour de lui.
Exemple avec propriétés typées et injection de dépendances
Pour une application PHP moderne, le constructeur sert souvent à injecter les dépendances nécessaires à la classe.
Marre des agences qui sous-traitent ?
Avec moi, vous parlez directement au développeur qui fait le travail. Pas d'intermédiaire, pas de promesses creuses. Juste du code propre et un interlocuteur joignable.
Travaillons directement ensemble →Exemple propre pour PHP 8.5+ :
<?php
declare(strict_types=1);
final class NewsletterSender {
public function __construct(
private readonly MailerInterface $mailer,
private readonly LoggerInterface $logger,
) {
}
public function send(Subscriber $subscriber, Newsletter $newsletter): void {
try {
$this->mailer->send(
$subscriber->email(),
$newsletter->subject(),
$newsletter->html()
);
} catch (Throwable $exception) {
$this->logger->error('Newsletter sending failed.', [
'email' => $subscriber->email(),
'exception' => $exception,
]);
throw $exception;
}
}
}Langage du code : HTML, XML (xml)
On évite ainsi les initialisations cachées, les dépendances globales et les méthodes historiques appelées par convention. Le constructeur expose clairement ce dont la classe a besoin pour fonctionner.
Exemple WordPress moderne : classe plugin sans ancien constructeur
Dans un plugin WordPress, on rencontre encore beaucoup d’anciennes classes qui mélangent constructeur historique, hooks et logique métier. Voici une version moderne et lisible, compatible PHP 8.5+.
<?php
/**
* Plugin Name: SKY Example Admin Page
* Description: Example plugin class using a modern PHP constructor.
* Version: 1.0.0
* Author: Matt Biscay
*/
declare(strict_types=1);
defined( 'ABSPATH' ) || exit;
/**
* Registers a small admin page.
*/
final class Sky_Example_Admin_Page {
/**
* Hook the plugin into WordPress.
*/
public function __construct() {
add_action( 'admin_menu', array( $this, 'register_admin_menu' ) );
}
/**
* Register the admin menu page.
*
* @return void
*/
public function register_admin_menu(): void {
add_options_page(
__( 'Example Page', 'sky-example' ),
__( 'Example Page', 'sky-example' ),
'manage_options',
'sky-example-admin-page',
array( $this, 'render_admin_page' )
);
}
/**
* Render the admin page.
*
* @return void
*/
public function render_admin_page(): void {
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( esc_html__( 'You do not have permission to access this page.', 'sky-example' ) );
}
echo '<div class="wrap">';
echo '<h1>' . esc_html__( 'Example Page', 'sky-example' ) . '</h1>';
echo '<p>' . esc_html__( 'This page is registered from a modern PHP constructor.', 'sky-example' ) . '</p>';
echo '</div>';
}
}
add_action(
'plugins_loaded',
static function (): void {
new Sky_Example_Admin_Page();
}
);Langage du code : HTML, XML (xml)
Ici, la classe utilise uniquement __construct(). Elle ne contient aucune méthode Sky_Example_Admin_Page(). C’est exactement ce que l’on veut dans du code WordPress moderne.
Pourquoi supprimer l’ancien constructeur au lieu de le renommer ?
Si une méthode porte le même nom que la classe, elle peut encore être appelée comme une méthode normale en PHP 8+. Mais, dans la plupart des cas, elle n’a plus aucune utilité.
Exemple :
<?php
class ExampleClass {
public function ExampleClass() {
echo 'This is not a constructor in PHP 8+.';
}
public function __construct() {
echo 'Constructor called.';
}
}
$example = new ExampleClass();Langage du code : HTML, XML (xml)
En PHP 8+, l’instanciation appelle __construct(), pas ExampleClass().
Donc, si ExampleClass() ne sert qu’à rediriger vers __construct(), elle doit disparaître. Si elle contient une vraie logique encore utilisée, extrayez cette logique dans une méthode au nom explicite.
Avant / après : migration directe
Ancien code :
<?php
class Importer {
public function Importer() {
$this->__construct();
}
public function __construct() {
$this->load_dependencies();
}
private function load_dependencies() {
require_once __DIR__ . '/includes/parser.php';
}
}Langage du code : HTML, XML (xml)
Nouveau code PHP 8.5+ :
<?php
declare(strict_types=1);
final class Importer {
public function __construct() {
$this->load_dependencies();
}
private function load_dependencies(): void {
require_once __DIR__ . '/includes/parser.php';
}
}Langage du code : HTML, XML (xml)
Encore mieux : évitez de charger des dépendances dans le constructeur si un autoloader Composer peut le faire. Le constructeur doit initialiser l’objet, pas servir de vide-grenier.
Version avec namespace et autoload Composer
Sur un projet moderne, utilisez des namespaces et un autoload Composer. Exemple :
<?php
declare(strict_types=1);
namespace Skyminds\App;
final class Importer {
public function __construct(
private readonly Parser $parser,
private readonly Storage $storage,
) {
}
public function import(string $path): ImportResult {
$items = $this->parser->parse($path);
return $this->storage->store($items);
}
}Langage du code : HTML, XML (xml)
Avec un composer.json PSR-4 :
{
"autoload": {
"psr-4": {
"Skyminds\\App\\": "src/"
}
}
}Langage du code : JSON / JSON avec commentaires (json)
Puis :
composer dump-autoload
Les namespaces ont aussi un effet intéressant ici : même avant PHP 8.0, les anciens constructeurs nommés comme la classe ne fonctionnaient pas dans les classes namespacées. Le manuel PHP précise que ce comportement spécial ne concernait que les classes du namespace global avant PHP 8.0. Voir la documentation PHP sur les anciens constructeurs.
Détecter les anciens constructeurs dans un projet
Pour moderniser un projet, il faut d’abord trouver les classes concernées.
Une recherche simple avec grep permet déjà de repérer beaucoup de cas suspects :
grep -RInE 'function[[:space:]]+[A-Z_][A-Za-z0-9_]*[[:space:]]*\(' . --include='*.php'Langage du code : PHP (php)
Cette commande remonte les méthodes dont le nom commence par une majuscule ou un underscore. Elle ne garantit pas que chaque résultat est un ancien constructeur, mais elle donne une bonne liste de départ.
Avec ripgrep :
rg 'function\s+[A-Z_][A-Za-z0-9_]*\s*\(' -g '*.php'Langage du code : JavaScript (javascript)
Pour un audit plus robuste, utilisez PHPStan, Psalm ou Rector. Les regex aident à repérer, mais l’analyse statique comprend mieux le code.
Automatiser la migration avec Rector
Rector est souvent l’outil le plus efficace pour moderniser un vieux code PHP. Il peut appliquer des transformations automatiques, puis vous laissez les tests et la revue de code faire le tri.
Installation en développement :
composer require rector/rector --devLangage du code : JavaScript (javascript)
Créer une configuration :
vendor/bin/rector init
Lancer Rector en dry-run :
vendor/bin/rector process --dry-run
Puis appliquer :
vendor/bin/rector process
Rector ne remplace pas le jugement technique, mais il accélère énormément les migrations. Sur un projet ancien, il trouve souvent bien plus de poussière que prévu. Et parfois, quelques fossiles avec des commentaires datés de 2007.
Tester la migration avec PHP 8.5+
Après suppression des anciens constructeurs, testez le projet avec la version cible de PHP.
Vérifiez la version utilisée :
php -v
Lancez l’analyse syntaxique sur les fichiers PHP :
find . -name '*.php' -not -path './vendor/*' -print0 | xargs -0 -n1 php -lLangage du code : JavaScript (javascript)
Lancez vos tests :
vendor/bin/phpunit
Pour un plugin WordPress, testez aussi l’activation avec WP-CLI :
wp plugin activate nom-du-plugin
Et surveillez les erreurs PHP :
tail -f /var/log/php8.5-fpm.logLangage du code : JavaScript (javascript)
Adaptez le chemin du log selon votre serveur.
Ne pas ajouter de type de retour à __construct()
Un constructeur PHP ne doit pas déclarer de type de retour. Même void est interdit.
Mauvais exemple :
<?php
final class BadExample {
public function __construct(): void {
}
}Langage du code : HTML, XML (xml)
Bon exemple :
<?php
final class GoodExample {
public function __construct() {
}
}Langage du code : HTML, XML (xml)
La documentation PHP rappelle que les constructeurs sont des méthodes spéciales appelées lors de la création de l’objet. Ils n’ont pas de type de retour déclaré. Voir la documentation PHP sur __construct().
Si une classe enfant surcharge le constructeur
Lorsqu’une classe enfant définit son propre constructeur, le constructeur parent n’est pas appelé automatiquement. Il faut l’appeler explicitement si nécessaire.
Exemple :
<?php
declare(strict_types=1);
class BaseController {
public function __construct(
protected readonly LoggerInterface $logger,
) {
}
}
final class AdminController extends BaseController {
public function __construct(
LoggerInterface $logger,
private readonly PermissionChecker $permissions,
) {
parent::__construct($logger);
}
}Langage du code : HTML, XML (xml)
Ce comportement est normal. Il permet à chaque classe d’initialiser ses propres dépendances, tout en appelant le parent quand cela a du sens.
Migration WordPress : attention aux anciennes classes de plugins
Dans l’écosystème WordPress, beaucoup de vieux plugins contiennent encore des classes écrites pour PHP 5.2, parfois avec des méthodes nommées comme la classe.
Exemple legacy :
<?php
class Old_Plugin_Admin {
public function Old_Plugin_Admin() {
$this->__construct();
}
public function __construct() {
add_action( 'admin_menu', array( $this, 'menu' ) );
}
public function menu() {
// Register menu.
}
}Langage du code : HTML, XML (xml)
Version modernisée :
<?php
/**
* Admin screen registration.
*/
declare(strict_types=1);
defined( 'ABSPATH' ) || exit;
/**
* Registers the plugin admin screen.
*/
final class Old_Plugin_Admin {
/**
* Register WordPress hooks.
*/
public function __construct() {
add_action( 'admin_menu', array( $this, 'register_menu' ) );
}
/**
* Register the plugin menu.
*
* @return void
*/
public function register_menu(): void {
add_options_page(
__( 'Plugin Settings', 'old-plugin' ),
__( 'Plugin Settings', 'old-plugin' ),
'manage_options',
'old-plugin-settings',
array( $this, 'render_page' )
);
}
/**
* Render the settings page.
*
* @return void
*/
public function render_page(): void {
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( esc_html__( 'You do not have permission to access this page.', 'old-plugin' ) );
}
echo '<div class="wrap">';
echo '<h1>' . esc_html__( 'Plugin Settings', 'old-plugin' ) . '</h1>';
echo '</div>';
}
}Langage du code : HTML, XML (xml)
On garde la structure WordPress, mais on supprime la compatibilité morte. Le code devient plus lisible, plus testable, et plus cohérent avec PHP 8.5+.
Checklist de modernisation pour PHP 8.5+
Lorsque vous tombez sur un ancien constructeur, profitez-en pour moderniser la classe correctement.
- Supprimez la méthode portant le même nom que la classe.
- Gardez uniquement
__construct(). - Ajoutez
declare(strict_types=1);. - Ajoutez des types aux paramètres.
- Ajoutez des types de retour aux méthodes classiques.
- N’ajoutez pas de type de retour à
__construct(). - Utilisez des propriétés typées ou promues si cela clarifie le code.
- Utilisez
readonlypour les dépendances immuables. - Remplacez les méthodes vagues par des noms explicites.
- Ajoutez ou mettez à jour les tests.
Résumé rapide
Ancien code à supprimer :
<?php
class ExampleClass {
public function ExampleClass() {
$this->__construct();
}
public function __construct() {
// Init.
}
}Langage du code : HTML, XML (xml)
Code moderne pour PHP 8.5+ :
<?php
declare(strict_types=1);
final class ExampleClass {
public function __construct() {
// Init.
}
}Langage du code : HTML, XML (xml)
Règle à retenir : une classe moderne n’a qu’un seul constructeur, __construct(). Les méthodes portant le même nom que la classe appartiennent au passé.
Conclusion
L’erreur Redefining already defined constructor for class venait d’un ancien modèle de compatibilité entre constructeurs historiques et __construct().
Pour PHP 8.5+, ne cherchez pas à préserver cette compatibilité. Supprimez les anciens constructeurs nommés comme la classe, gardez uniquement __construct(), puis modernisez la classe avec des types, des propriétés typées, des dépendances injectées et des méthodes explicites.
C’est une petite correction, mais elle marque souvent le début d’un vrai nettoyage legacy. Et franchement, si votre code contient encore des constructeurs de cette époque, il y a probablement d’autres reliques qui méritent une lampe torche.
Sources utiles
- PHP manual : constructors and destructors
- PHP manual : PHP 8.0 incompatible changes
- PHP.net : annonce officielle PHP 8.5
- PHP RFC : deprecations for PHP 8.5
Marre des agences qui sous-traitent ?
Avec moi, vous parlez directement au développeur qui fait le travail. Pas d'intermédiaire, pas de promesses creuses. Juste du code propre et un interlocuteur joignable.
Travaillons directement ensemble →

