Mis à jour le 08/01/2018
  • 30 heures
  • Moyenne

Ce cours est visible gratuitement en ligne.

Ce cours existe en livre papier.

Vous pouvez obtenir un certificat de réussite à l'issue de ce cours.

Vous pouvez être accompagné et mentoré par un professeur particulier par visioconférence sur ce cours.

J'ai tout compris !

Les traits

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

Depuis sa version 5.4, PHP intègre un moyen de réutiliser le code d'une méthode dans deux classes indépendantes. Cette fonctionnalité permet ainsi de repousser les limites de l'héritage simple (pour rappel, en PHP, une classe ne peut hériter que d'une seule classe mère). Nous allons donc nous pencher ici sur les traits afin de pallier le problème de duplication de méthode.

Le principe des traits

Posons le problème

Admettons que vous ayez deux classes,Writer et Mailer. La première est chargée d'écrire du texte dans un fichier, tandis que la seconde envoie un texte par mail. Cependant, il est agréable de mettre en forme le texte. Pour cela, vous décidez de formater le texte en HTML. Or, un problème se pose : vous allez devoir effectuer la même opération (celle de formater en HTML) dans deux classes complètement différentes et indépendantes :

<?php
class Writer
{
  public function write($text)
  {
    $text = '<p>Date : '.date('d/m/Y').'</p>'."\n".
            '<p>'.nl2br($text).'</p>';
    file_put_contents('fichier.txt', $text);
  }
}
<?php
class Mailer
{
  public function send($text)
  {
    $text = '<p>Date : '.date('d/m/Y').'</p>'."\n".
            '<p>'.nl2br($text).'</p>';
    mail('login@fai.tld', 'Test avec les traits', $text);
  }
}

Ici, le code est petit et la duplication n'est donc pas énorme, mais elle est belle et bien présente. Dans une application de plus grande envergure, ce code pourrait être dupliqué pas mal de fois. Imaginez alors que vous décidiez de formater autrement votre texte : catastrophe, il va falloir modifier chaque partie du code qui formatait du texte ! Penchons-nous donc vers les traits pour résoudre ce problème.

Résoudre le problème grâce aux traits

Syntaxe de base

Comme vous vous en doutez peut-être, les traits sont un moyen d'externaliser du code. Plus précisément, les traits définissent des méthodes que les classes peuvent utiliser. Avant de résoudre le problème évoqué, nous allons nous pencher sur la syntaxe des traits pour pouvoir s'en servir.

Regardez ce code :

<?php
trait MonTrait
{
  public function hello()
  {
    echo 'Hello world !';
  }
}

class A
{
  use MonTrait;
}

class B
{
  use MonTrait;
}

$a = new A;
$a->hello(); // Affiche « Hello world ! ».

$b = new b;
$b->hello(); // Affiche aussi « Hello world ! ».

Commentons ce code. Dans un premier temps, nous définissons un trait. Un trait, comme vous pouvez le constater, n'est autre qu'une mini-classe. Dedans, nous n'avons déclaré qu'une seule méthode. Ensuite, nous déclarons deux classes, chacune utilisant le trait que nous avons créé. Comme vous pouvez le constater, l'utilisation d'un trait dans une classe se fait grâce au mot-cléuse. En utilisant ce mot-clé, toutes les méthodes du trait vont être importées dans la classe. Comme en témoigne le code de test en fin de fichier, les deux classes possèdent bien une méthodehello()et celle-ci affiche bien « Hello world ! ».

Retour sur notre formateur

Ce que l'on va faire maintenant, c'est retirer le gros défaut de notre code : grâce aux traits, nous ferons disparaître la duplication de notre code. Ce que je vous propose de faire, c'est de créer un trait qui va contenir une méthode format($text)qui va formater le texte passé en argument en HTML.

Procédons étape par étape. Commençons par créer notre trait (il ne contient qu'une méthode chargée de formater le texte en HTML) :

<?php
trait HTMLFormater
{
  public function format($text)
  {
    return '<p>Date : '.date('d/m/Y').'</p>'."\n".
           '<p>'.nl2br($text).'</p>';
  }
}

Maintenant, nous allons modifier nos classesWriteretMailerafin qu'elles utilisent ce trait pour formater le texte qu'elles exploiteront. Pour y arriver, je vous rappelle qu'il vous faut utiliser le mot-cléusepour importer toutes les méthodes du trait (ici, il n'y en a qu'une) dans la classe. Vous pourrez ainsi utiliser les méthodes comme si elles étaient déclarées dans la classe. Voici la correction :

<?php
class Writer
{
  use HTMLFormater;
  
  public function write($text)
  {
    file_put_contents('fichier.txt', $this->format($text));
  }
}
<?php
class Mailer
{
  use HTMLFormater;
  
  public function send($text)
  {
    mail('login@fai.tld', 'Test avec les traits', $this->format($text));
  }
}

Libre à vous de tester vos classes ! Par exemple, essayez d'exécuter ce code :

<?php
$w = new Writer;
$w->write('Hello world!');

$m = new Mailer;
$m->send('Hello world!');

Nous venons ici de supprimer la duplication de code anciennement présente. Cependant, les traits ne se résument pas qu'à ça ! Regardons par exemple comment utiliser plusieurs traits dans une classe.

Utiliser plusieurs traits

Syntaxe

Pour utiliser plusieurs traits, rien de plus simple : il vous suffit de lister tous les traits à utiliser séparés par des virgules, comme ceci :

<?php
trait HTMLFormater
{
  public function formatHTML($text)
  {
    return '<p>Date : '.date('d/m/Y').'</p>'."\n".
           '<p>'.nl2br($text).'</p>';
  }
}

trait TextFormater
{
  public function formatText($text)
  {
    return 'Date : '.date('d/m/Y')."\n".$text;
  }
}

class Writer
{
  use HTMLFormater, TextFormater;
  
  public function write($text)
  {
    file_put_contents('fichier.txt', $this->formatHTML($text));
  }
}

Je vous laisse essayer ce code, je suis sûr que vous y arriverez seuls !

Résolution des conflits

Le code donné plus haut est bien beau, mais que se passerait-il si nos traits avaient tous les deux une méthode nomméeformat()? Je vous laisse essayer... Et oui, une erreur fatale avec le message « Trait method format has not been applied, because there are collisions with other trait methods » est levée. Pour pallier ce problème, nous pouvons donner une priorité à une méthode d'un trait afin de lui permettre d'écraser la méthode de l'autre trait si il y en a une identique.

Par exemple, si dans notre classeWriternous voulions formater notre message en HTML, nous pourrions faire :

<?php
class Writer
{
  use HTMLFormater, TextFormater
  {
    HTMLFormater::format insteadof TextFormater;
  }
  
  public function write($text)
  {
    file_put_contents('fichier.txt', $this->format($text));
  }
}

Regardons de plus près cette ligne n°6. Pour commencer, notez qu'elle est définie dans une paire d'accolades suivant les noms des traits à utiliser. À l'intérieur de cette paire d'accolades se trouve la liste des « méthodes prioritaires ». Chaque déclaration de priorité se fait en se terminant par un point-virgule. Cette ligne signifie donc : « La méthodeformat()du traitHTMLFormaterécrasera la méthode du même nom du traitTextFormater(si elle y est définie). »

Méthodes de traits vs. méthodes de classes

La classe plus forte que le trait

Terminons cette introduction aux traits en nous penchant sur les conflits entre méthodes de traits et méthodes de classes. Si une classe déclare une méthode et qu'elle utilise un trait possédant cette même méthode, alors la méthode déclarée dans la classe l'emportera sur la méthode déclarée dans le trait. Exemple :

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

class MaClasse
{
  use MonTrait;
  
  public function sayHello()
  {
    echo 'Bonjour !';
  }
}

$objet = new MaClasse;
$objet->sayHello(); // Affiche « Bonjour ! ».
Le trait plus fort que la mère

À l'inverse, si une classe utilise un trait possédant une méthode déjà implémentée dans la classe mère de la classe utilisant le trait, alors ce sera la méthode du trait qui sera utilisée (la méthode du trait écrasera celle de la méthode de la classe mère). Exemple :

<?php
trait MonTrait
{
  public function speak()
  {
    echo 'Je suis un trait !';
  }
}

class Mere
{
  public function speak()
  {
    echo 'Je suis une classe mère !';
  }
}

class Fille extends Mere
{
  use MonTrait;
}

$fille = new Fille;
$fille->speak(); // Affiche « Je suis un trait ! »

Plus loin avec les traits

Définition d'attributs

Syntaxe

Nous avons vu que les traits servaient à isoler des méthodes afin de pouvoir les utiliser dans deux classes totalement indépendantes. Si le besoin s'en fait sentir, sachez que vous pouvez aussi définir des attributs dans votre trait. Ils seront alors à leur tour importés dans la classe qui utilisera ce trait. Exemple :

<?php
trait MonTrait
{
  protected $attr = 'Hello !';
  
  public function showAttr()
  {
    echo $this->attr;
  }
}

class MaClasse
{
  use MonTrait;
}

$fille = new MaClasse;
$fille->showAttr();
Conflit entre attributs

Si un attribut est défini dans un trait, alors la classe utilisant le trait ne peut pas définir d'attribut possédant le même nom. Suivant la déclaration de l'attribut, deux cas peuvent se présenter :

  • Si l'attribut déclaré dans la classe a le même nom mais pas la même valeur initiale ou pas la même visibilité, une erreur fatale est levée.

  • Si l'attribut déclaré dans la classe a le même nom, une valeur initiale identique et la même visibilité, une erreur stricte est levée (il est possible, suivant votre configuration, que PHP n'affiche pas ce genre d'erreur).

Malheureusement, il est impossible, comme nous l'avons fait avec les méthodes, de définir des attributs prioritaires. Veillez donc bien à ne pas utiliser ce genre de code :

<?php
trait MonTrait
{
  protected $attr = 'Hello !';
}

class MaClasse
{
  use MonTrait;
  
  protected $attr = 'Hello !'; // Lèvera une erreur stricte.
  protected $attr = 'Bonjour !'; // Lèvera une erreur fatale.
  private $attr = 'Hello !'; // Lèvera une erreur fatale.
}

Traits composés d'autres traits

Au même titre que les classes, les traits peuvent eux aussi utiliser des traits. La façon de procéder est la même qu'avec les classes, tout comme la gestion des conflits entre méthodes. Voici un exemple :

<?php
trait A
{
  public function saySomething()
  {
    echo 'Je suis le trait A !';
  }
}

trait B
{
  use A;
  
  public function saySomethingElse()
  {
    echo 'Je suis le trait B !';
  }
}

class MaClasse
{
  use B;
}

$o = new MaClasse;
$o->saySomething(); // Affiche « Je suis le trait A ! »
$o->saySomethingElse(); // Affiche « Je suis le trait B ! »

Changer la visibilité et le nom des méthodes

Si un trait implémente une méthode, toute classe utilisant ce trait a la capacité de changer sa visibilité, c'est-à-dire la passer en privé, protégé ou public. Pour cela, nous allons à nouveau nous servir des accolades qui ont suivi la déclaration deusepour y glisser une instruction. Cette instruction fait appel à l'opérateuras, que vous avez déjà peut-être rencontré dans l'utilisation de namespaces (si ce n'est pas le cas, ce n'est pas grave du tout). Le rôle est ici le même : créer un alias. En effet, vous pouvez aussi changer le nom des méthodes. Dans ce dernier cas, la méthode ne sera pas renommée, mais copiée sous un autre nom, ce qui signifie que vous pourrez toujours y accéder sous son ancien nom.

Quelques petits exemples pour que vous compreniez :

<?php
trait A
{
  public function saySomething()
  {
    echo 'Je suis le trait A !';
  }
}

class MaClasse
{
  use A
  {
    saySomething as protected;
  }
}

$o = new MaClasse;
$o->saySomething(); // Lèvera une erreur fatale car on tente d'accéder à une méthode protégée.
<?php
trait A
{
  public function saySomething()
  {
    echo 'Je suis le trait A !';
  }
}

class MaClasse
{
  use A
  {
    saySomething as sayWhoYouAre;
  }
}

$o = new MaClasse;
$o->sayWhoYouAre(); // Affichera « Je suis le trait A ! »
$o->saySomething(); // Affichera « Je suis le trait A ! »
<?php
trait A
{
  public function saySomething()
  {
    echo 'Je suis le trait A !';
  }
}

class MaClasse
{
  use A
  {
    saySomething as protected sayWhoYouAre;
  }
}

$o = new MaClasse;
$o->saySomething(); // Affichera « Je suis le trait A ! ».
$o->sayWhoYouAre(); // Lèvera une erreur fatale, car l'alias créé est une méthode protégée.

Méthodes abstraites dans les traits

Enfin, sachez que l'on peut forcer la classe utilisant le trait à implémenter certaines méthodes au moyen de méthodes abstraites. Ainsi, ce code lèvera une erreur fatale :

<?php
trait A
{
  abstract public function saySomething();
}

class MaClasse
{
  use A;
}

Cependant, si la classe utilisant le trait déclarant une méthode abstraite est elle aussi abstraite, alors ce sera à ses classes filles d'implémenter les méthodes abstraites du trait (elle peut le faire, mais elle n'est pas obligée). Exemple :

<?php
trait A
{
  abstract public function saySomething();
}

abstract class Mere
{
  use A;
}

// Jusque-là, aucune erreur n'est levée.

class Fille extends Mere
{
  // Par contre, une erreur fatale est ici levée, car la méthode saySomething() n'a pas été implémentée.
}

En résumé

  • Les traits sont un moyen pour éviter la duplication de méthodes.

  • Un trait s'utilise grâce au mot-cléuse.

  • Il est possible d'utiliser une infinité de traits dans une classe en résolvant les conflits éventuels avecinsteadof.

  • Un trait peut lui-même utiliser un autre trait.

  • Il est possible de changer la visibilité d'une méthode ainsi que son nom grâce au mot-cléas.

Exemple de certificat de réussite
Exemple de certificat de réussite