PHP Programmation Orientée Objet : Comprendre les Traits
Apprenez les traits PHP : déclaration, utilisation, combinaison, résolution de conflits avec insteadof et as, et membres abstraits et statiques.
Un trait est un bloc réutilisable de méthodes (et de propriétés) que vous pouvez mélanger dans n'importe quelle classe. Les traits résolvent un problème spécifique en PHP : une classe ne peut étendre qu'un seul parent, donc lorsque deux classes non liées ont besoin de partager le même comportement, l'héritage simple ne suffit pas. Les traits vous permettent de partager ce comportement horizontalement — entre des classes qui n'ont aucune relation parent-enfant.
Ce chapitre explique ce que sont les traits, comment les déclarer et les utiliser, comment combiner plusieurs traits, comment PHP résout les conflits de noms de méthodes, et les pièges (méthodes abstraites, membres statiques, propriétés) qui font trébucher les développeurs.
Qu'est-ce qu'un trait ?
Un trait ressemble presque exactement à une classe, mais il est déclaré avec le mot-clé trait au lieu de class. Les différences clés :
- Un trait ne peut pas être instancié seul — il n'existe pas de
new MyTrait(). - Un trait est utilisé à l'intérieur d'une classe avec le mot-clé
use. Ses méthodes sont ensuite copiées dans la classe comme si vous les y aviez écrites. - Une classe peut utiliser un nombre quelconque de traits, et un trait peut utiliser d'autres traits.
Pensez à un trait comme à un copier-coller assisté par le compilateur : le code du trait est littéralement aplati dans chaque classe qui l'utilise.
Déclarer et utiliser un trait
Déclarez un trait avec trait, puis importez-le dans une classe avec use :
<?php
trait Greetable
{
public function greet(): string
{
return "Hello, my name is {$this->name}.";
}
}
class User
{
public function __construct(public string $name) {}
use Greetable;
}
$user = new User("Ada");
echo $user->greet();
// Output: Hello, my name is Ada.Notez que le trait référence $this->name même s'il ne le définit pas. Un trait s'exécute dans le contexte de la classe qui l'utilise, il peut donc s'appuyer sur les propriétés et méthodes fournies par cette classe.
Utiliser plusieurs traits
Une classe peut utiliser plusieurs traits à la fois. Séparez-les par des virgules, ou listez chacun avec son propre use :
<?php
trait Loggable
{
public function log(string $message): string
{
return "[LOG] " . $message;
}
}
trait Jsonable
{
public function toJson(): string
{
return json_encode(get_object_vars($this));
}
}
class Order
{
use Loggable, Jsonable;
public function __construct(public int $id, public float $total) {}
}
$order = new Order(7, 49.99);
echo $order->log("created") . PHP_EOL;
echo $order->toJson();
// Output:
// [LOG] created
// {"id":7,"total":49.99}Résoudre les conflits avec insteadof et as
Si deux traits définissent une méthode avec le même nom, PHP lève une erreur fatale sauf si vous lui indiquez laquelle conserver. Utilisez insteadof pour choisir le gagnant et as pour conserver le perdant sous un alias :
<?php
trait FileStorage
{
public function save(): string
{
return "Saved to a file.";
}
}
trait DatabaseStorage
{
public function save(): string
{
return "Saved to the database.";
}
}
class Report
{
use FileStorage, DatabaseStorage {
DatabaseStorage::save insteadof FileStorage;
FileStorage::save as saveToFile;
}
}
$report = new Report();
echo $report->save() . PHP_EOL; // DatabaseStorage wins
echo $report->saveToFile(); // still reachable via the alias
// Output:
// Saved to the database.
// Saved to a file.Le mot-clé as peut également modifier la visibilité d'une méthode, par exemple protected reset as private — pratique lorsque vous souhaitez qu'une méthode de trait soit accessible en interne mais pas dans l'API publique.
Membres abstraits et statiques
Les traits peuvent faire plus que contenir des méthodes d'instance concrètes.
- Les méthodes abstraites permettent à un trait d'exiger que la classe utilisatrice implémente quelque chose. C'est ainsi qu'un trait déclare une dépendance envers son hôte.
- Les méthodes et propriétés statiques se comportent comme des membres statiques normaux, mais chaque classe utilisatrice obtient sa propre copie de toute propriété statique.
<?php
trait Counter
{
private static int $count = 0;
public static function increment(): int
{
return ++self::$count;
}
// The using class MUST provide this:
abstract public function label(): string;
}
class PageView
{
use Counter;
public function label(): string
{
return "views";
}
}
echo PageView::increment() . PHP_EOL; // 1
echo PageView::increment() . PHP_EOL; // 2
echo (new PageView())->label(); // views
// Output:
// 1
// 2
// viewsTraits vs. héritage et interfaces
Choisissez le bon outil :
- L'héritage (
extends) modélise une relation "est-un" et vous donne un seul parent. Utilisez-le lorsque les classes partagent véritablement une hiérarchie de types. Voir PHP Inheritance. - Les interfaces définissent un contrat — quelles méthodes existent — mais ne contiennent aucune implémentation. Voir PHP Interfaces.
- Les traits fournissent une implémentation que vous pouvez mélanger dans des classes non liées. Un modèle courant est une interface + un trait : l'interface annonce la capacité, le trait fournit le code par défaut.
Si vous êtes novice dans ces concepts, commencez par PHP Classes and Objects et PHP Abstract Classes.
Pièges courants
- Les collisions de noms sont silencieuses jusqu'à ce qu'elles ne le soient plus. Combiner des traits qui définissent la même méthode est une erreur fatale ; résolvez toujours avec
insteadof/as. - Les traits sont aplatis, pas hérités. Une méthode définie directement dans la classe remplace la version du trait, et la version du trait remplace tout ce qui est hérité d'une classe parente.
- Les propriétés statiques sont par classe. Deux classes utilisant le même trait ne partagent pas son état statique — chacune obtient une copie séparée.
- N'en abusez pas. Un trait qui a besoin de nombreuses propriétés de son hôte est souvent le signe que la composition (un vrai objet) ou l'héritage convient mieux.
Conclusion
Les traits donnent à PHP un moyen propre de partager des implémentations de méthodes entre des classes non liées, contournant la limitation de l'héritage simple. Déclarez-les avec trait, importez-les avec use, résolvez les conflits avec insteadof et as, et rappelez-vous que le code du trait est aplati dans chaque classe qui l'utilise. Utilisés judicieusement, les traits maintiennent les comportements transversaux — journalisation, sérialisation, compteurs — en un seul endroit plutôt que dispersés en copies.