Traits

PHP implémente une manière pour réutiliser du code appelée Traits.

Les traits sont un mécanisme de réutilisation de code dans un langage à héritage simple tel que PHP. Un trait tente de réduire certaines limites de l'héritage simple, en autorisant le développeur à réutiliser librement des jeux de méthodes dans plusieurs classes indépendantes vivant dans des hiérarchies de classes différentes. La sémantique de la combinaison des Traits et des classes est définie de manière à réduire la complexité et à éviter les problèmes typiques associés à l'héritage multiple et aux Mixins.

Un Trait est semblable à une classe, mais il ne sert qu'à grouper des fonctionnalités d'une manière fine et cohérente. Il n'est pas possible d'instancier un Trait en lui-même. C'est un ajout à l'héritage traditionnel, qui permet la composition horizontale de comportements, c'est-à-dire l'application de membres de classe sans nécessiter d'héritage.

Exemple #1 Exemple d'utilisation de Trait

<?php

trait TraitA {
    public function sayHello() {
        echo 'Hello';
    }
}

trait TraitB {
    public function sayWorld() {
        echo 'World';
    }
}

class MyHelloWorld
{
    use TraitA, TraitB; // Une classe peut utiliser plusieurs traits

    public function sayHelloWorld() {
        $this->sayHello();
        echo ' ';
        $this->sayWorld();
        echo "!\n";
    }
}

$myHelloWorld = new MyHelloWorld();
$myHelloWorld->sayHelloWorld();

?>

L'exemple ci-dessus va afficher :

Hello World!

Précédence

Une méthode héritée depuis une classe mère est écrasée par une méthode issue d'un Trait. L'ordre de précédence fait en sorte que les méthodes de la classe courante écrasent les méthodes issues d'un Trait, elles-mêmes surchargeant les méthodes héritées.

Exemple #2 Exemple avec l'ordre de précédence

Une méthode héritée depuis la classe de base est écrasée par la méthode insérée dans MyHelloWorld depuis le Trait SayWorld. Le comportement est le même pour les méthodes définies dans la classe MyHelloWorld. L'ordre de précédence fait en sorte que les méthodes de la classe courante écrasent les méthodes du Trait, elles-mêmes surchargeant les méthodes de la classe de base.

<?php
class Base {
    public function sayHello() {
        echo 'Hello ';
    }
}

trait SayWorld {
    public function sayHello() {
        parent::sayHello();
        echo 'World!';
    }
}

class MyHelloWorld extends Base {
    use SayWorld;
}

$o = new MyHelloWorld();
$o->sayHello();
?>

L'exemple ci-dessus va afficher :

Hello World!

Exemple #3 Autre exemple d'ordre de précédence

<?php
trait HelloWorld {
    public function sayHello() {
        echo 'Hello World!';
    }
}

class TheWorldIsNotEnough {
    use HelloWorld;
    public function sayHello() {
        echo 'Hello Universe!';
    }
}

$o = new TheWorldIsNotEnough();
$o->sayHello();
?>

L'exemple ci-dessus va afficher :

Hello Universe!

Multiples Traits

Une classe peut utiliser de multiples Traits en les déclarant avec le mot-clé use, séparés par des virgules.

Exemple #4 Utilisation de plusieurs Traits

<?php
trait Hello {
    public function sayHello() {
        echo 'Hello ';
    }
}

trait World {
    public function sayWorld() {
        echo 'World';
    }
}

class MyHelloWorld {
    use Hello, World;
    public function sayExclamationMark() {
        echo '!';
    }
}

$o = new MyHelloWorld();
$o->sayHello();
$o->sayWorld();
$o->sayExclamationMark();
?>

L'exemple ci-dessus va afficher :

Hello World!

Résolution des conflits

Si deux Traits insèrent une méthode avec le même nom, une erreur fatale est levée si le conflit n'est pas explicitement résolu.

Pour résoudre un conflit de nommage entre des Traits utilisés dans la même classe, il faut utiliser l'opérateur insteadof pour choisir une des méthodes en conflit.

Puisque ce principe ne permet que d'exclure des méthodes, l'opérateur as peut être utilisé pour permettre l'inclusion d'une des méthodes conflictuelles sous un autre nom. Il est à noter que l'opérateur as ne renomme pas la méthode et n'affecte pas d'autres méthodes non plus.

Exemple #5 Résolution des conflits

Dans cet exemple, la classe Talker utilise les traits A et B. Comme A et B ont des méthodes conflictuelles, on indique que l'on souhaite utiliser la variante de smallTalk depuis le trait B, et la variante de bigTalk depuis le trait A.

La classe Aliased_Talker utilise l'opérateur as pour être capable d'utiliser l'implémentation bigTalk de B sous un alias supplémentaire talk.

<?php
trait A {
    public function smallTalk() {
        echo 'a';
    }
    public function bigTalk() {
        echo 'A';
    }
}

trait B {
    public function smallTalk() {
        echo 'b';
    }
    public function bigTalk() {
        echo 'B';
    }
}

class Talker {
    use A, B {
        B::smallTalk insteadof A;
        A::bigTalk insteadof B;
    }
}

class Aliased_Talker {
    use A, B {
        B::smallTalk insteadof A;
        A::bigTalk insteadof B;
        B::bigTalk as talk;
    }
}
?>

Changer la visibilité des méthodes

En utilisant la syntaxe as, il est aussi possible d'ajuster la visibilité de la méthode dans la classe qui l'utilise.

Exemple #6 Changer la visibilité des méthodes

<?php
trait HelloWorld {
    public function sayHello() {
        echo 'Hello World!';
    }
}

// Modification de la visibilité de la méthode sayHello
class MyClass1 {
    use HelloWorld { sayHello as protected; }
}

// Utilisation d'un alias lors de la modification de la visibilité
// La visibilité de la méthode sayHello n'est pas modifiée
class MyClass2 {
    use HelloWorld { sayHello as private myPrivateHello; }
}
?>

Traits Composés depuis d'autres Traits

Tout comme les classes peuvent utiliser des traits, d'autres traits le peuvent aussi. Un trait peut donc utiliser d'autres traits et hériter de tout ou d'une partie de ceux-ci.

Exemple #7 Traits Composés depuis d'autres Traits

<?php
trait Hello {
    public function sayHello() {
        echo 'Hello ';
    }
}

trait World {
    public function sayWorld() {
        echo 'World!';
    }
}

trait HelloWorld {
    use Hello, World;
}

class MyHelloWorld {
    use HelloWorld;
}

$o = new MyHelloWorld();
$o->sayHello();
$o->sayWorld();
?>

L'exemple ci-dessus va afficher :

Hello World!

Méthodes abstraites dans les Traits

Les traits supportent l'utilisation de méthodes abstraites afin d'imposer des contraintes aux classes sous-jacentes. Les méthodes publiques, protégées, et privées sont supportées. Avant PHP 8.0.0, seules les méthodes publiques et protégées abstraites étaient supportées.

Attention

À partir de PHP 8.0.0, la signature d'une méthode concrète doit suivre les règles de compatibilité des signatures. Auparavant, sa signature pouvait être différente.

Exemple #8 Obligations requises par les méthodes abstraites

<?php
trait Hello {
    public function sayHelloWorld() {
        echo 'Hello'.$this->getWorld();
    }
    abstract public function getWorld();
}

class MyHelloWorld {
    private $world;
    use Hello;
    public function getWorld() {
        return $this->world;
    }
    public function setWorld($val) {
        $this->world = $val;
    }
}
?>

Membres statiques dans les Traits

Les traits peuvent définir des variables statiques, méthodes statiques et propriétés statiques.

Note:

À partir de PHP 8.1.0, appeler une méthode statique ou accéder à une propriété statique directement sur un trait est obsolète. Les méthodes et propriétés statiques devraient seulement être accédées sur une classe utilisant le trait.

Exemple #9 Variables statiques

<?php

trait Counter
{
    public function inc()
    {
        static $c = 0;
        $c = $c + 1;
        echo "$c\n";
    }
}

class C1
{
    use Counter;
}

class C2
{
    use Counter;
}

$o = new C1();
$o->inc();
$p = new C2();
$p->inc();

?>

L'exemple ci-dessus va afficher :

1
1

Exemple #10 Méthodes statiques

<?php

trait StaticExample
{
    public static function doSomething()
    {
        return 'Doing something';
    }
}

class Example
{
    use StaticExample;
}

echo Example::doSomething();

?>

L'exemple ci-dessus va afficher :

Doing something

Exemple #11 Propriétés statiques

Attention

Avant PHP 8.3.0, les propriétés statiques définies dans un trait étaient partagées entre toutes les classes de la même hiérarchie d'héritage utilisant ce trait. À partir de PHP 8.3.0, si une classe enfant utilise un trait avec une propriété statique, celle-ci sera considérée comme distincte de celle définie dans la classe parente.

<?php

trait T
{
    public static $counter = 1;
}

class A
{
    use T;

    public static function incrementCounter()
    {
        static::$counter++;
    }
}

class B extends A
{
    use T;
}

A::incrementCounter();

echo A::$counter, "\n";
echo B::$counter, "\n";

?>

Résultat de l'exemple ci-dessus en PHP 8.3 :

2
1

Propriétés

Les traits peuvent aussi définir des propriétés.

Exemple #12 Définir des propriétés

<?php

trait PropertiesTrait
{
    public $x = 1;
}

class PropertiesExample
{
    use PropertiesTrait;
}

$example = new PropertiesExample();
$example->x;

?>

Si un trait définit une propriété, alors la classe ne peut pas définir une propriété de même nom sauf si elle est compatible (même visibilité, type, modificateur readonly, valeur initiale), sinon une erreur fatale est émise.

Exemple #13 Résolution des conflits

<?php
trait PropertiesTrait {
    public $same = true;
    public $different1 = false;
    public bool $different2;
    public bool $different3;
}

class PropertiesExample {
    use PropertiesTrait;
    public $same = true;
    public $different1 = true; // Erreur fatale
    public string $different2; // Erreur fatale
    readonly protected bool $different3; // Erreur fatale
}
?>

Constantes

Les traits peuvent, à partir de PHP 8.2.0, aussi définir des constantes.

Exemple #14 Trait définissant une constante

<?php
trait ConstantsTrait {
    public const FLAG_MUTABLE = 1;
    final public const FLAG_IMMUTABLE = 5;
}

class ConstantsExample {
    use ConstantsTrait;
}

$example = new ConstantsExample;
echo $example::FLAG_MUTABLE;
?>

L'exemple ci-dessus va afficher :

1

Si un trait définit une constante, alors une classe ne peut pas définir une constante avec le même nom, à moins qu'elle ne soit compatible (même visibilité, même valeur initiale et caractère final), sinon une erreur fatale est émise.

Exemple #15 Résolution des conflits

<?php
trait ConstantsTrait {
    public const FLAG_MUTABLE = 1;
    final public const FLAG_IMMUTABLE = 5;
}

class ConstantsExample {
    use ConstantsTrait;
    public const FLAG_IMMUTABLE = 5; // Erreur fatale
}
?>

Méthodes finales

À partir de PHP 8.3.0, le modificateur final peut être appliqué à l'aide de l'opérateur as aux méthodes importées depuis les traits. Cela peut être utilisé pour empêcher les classes enfants de surcharger la méthode. Cependant, la classe qui utilise le trait peut toujours surcharger la méthode.

Exemple #16 Définir une méthode provenant d'un trait comme final

<?php

trait CommonTrait
{
    public function method()
    {
        echo 'Hello';
    }
}

class FinalExampleA
{
    use CommonTrait {
        CommonTrait::method as final; // Le 'final' empêche les classes enfants de surcharger la méthode
    }
}

class FinalExampleB extends FinalExampleA
{
    public function method() {}
}

?>

Résultat de l'exemple ci-dessus est similaire à :

Fatal error: Cannot override final method FinalExampleA::method() in ...