PHP 8.5+ : remplacer les anciens constructeurs et corriger “Redefining already defined constructor”

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é.

Kinsta: Premium Managed WordPress hosting

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.

Kinsta: Premium Managed WordPress hosting

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 final si 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.

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.

Kinsta: Premium Managed WordPress hosting

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.

Distingo, le livret à 2%

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.

  1. Supprimez la méthode portant le même nom que la classe.
  2. Gardez uniquement __construct().
  3. Ajoutez declare(strict_types=1);.
  4. Ajoutez des types aux paramètres.
  5. Ajoutez des types de retour aux méthodes classiques.
  6. N’ajoutez pas de type de retour à __construct().
  7. Utilisez des propriétés typées ou promues si cela clarifie le code.
  8. Utilisez readonly pour les dépendances immuables.
  9. Remplacez les méthodes vagues par des noms explicites.
  10. 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

Demandez à l'IA son opinion
Gravatar for Matt Biscay

Je suis Matt Biscay, développeur WordPress & WooCommerce certifié chez Codeable, administrateur système et enseignant.

J’aide les entreprises à créer, optimiser et fiabiliser leurs sites WordPress avec une approche technique propre : performance, sécurité, maintenance, développement sur mesure et résolution de problèmes complexes.

Sur Skyminds, je partage des tutoriels WordPress, WooCommerce, Linux et administration système, avec des solutions testées sur des cas réels et pensées pour durer.

Découvrez mes services WordPress et WooCommerce.

Laisser un commentaire