Utilisation des traits en PHP 5.4

By | 2 octobre 2011

Nous allons à travers cet article, découvrir ce que sont les traits, leur utilité et la manière de les utiliser.

Avant de commencer cet article, je tiens à préciser que les traits ne sont disponible uniquement depuis la version php5.4 beta, disponible sur le lien suivant: http://qa.php.net.

A quoi servent les traits

Les traits permettent de simuler l’héritage multiple, qui n’est pas possible en Php, contrairement au C++ par exemple.

La déclaration d’un trait est similaire à celle d’une classe, il existe néanmoins quelques différences.

Ce qu’un trait ne peut faire contrairement à une classe:

  • N’est pas instanciable
  • Ne peut pas implémenter d’interface
  • Ne peut pas étendre une classe

Ci-dessous la déclaration d’un trait

trait MyTrait {

   protected $_sName = "trait";

   public function whoami() {
      echo "MyTrait\n";
   }
}

Alors, la grande nouveauté réside dans l’ajout du mot clé trait. Pour le reste vous pouvez constater que la définition des méthodes est la même que dans une classe.

Utilisation des traits

Utilisation classique

Nous allons commencer par un exemple simple, déclaration d’un trait et utilisation au sein d’une classe

trait MyTrait {
   public function whoami() {
      echo "MyTrait\n";
   }
}

class Foo {
   use MyTrait;
}

$foo = new Foo();
$foo->whoami(); // MyTrait

Comme vous avez pu le constater, pour qu’une classe puisse utiliser un trait, elle doit le déclarer en utilisant le mot clé use. Les méthodes définies dans le trait seront donc disponible depuis une instance de la classe.

Utilisation de plusieurs traits

Une classe peut utiliser plusieurs traits, il suffit pour cela de les déclarer après le mot clé use en les séparant par une virgule.

trait MyTrait {
   public function whoami() {
      echo "MyTrait\n";
   }
}

trait ScdTrait {
   public function scdWhoami() {
      echo "ScdTrait\n";
   }
}

class Foo {
   use MyTrait, ScdTrait;
}

$foo = new Foo();
$foo->whoami(); // MyTrait
$foo->scdWhoami(); // ScdTrait

Voilà, rien de plus simple.

Utilisation de insteadof

Maintenant que se passe t il si deux traits utilisés par une même classe ont des méthodes portant le même nom.

trait MyTrait {
   public function whoami() {
      echo "MyTrait\n";
   }
}

trait ScdTrait {
   public function whoami() {
      echo "ScdTrait\n";
   }
}

class Foo {
   use MyTrait, ScdTrait;
}

$foo = new Foo();
$foo->whoami(); // MyTrait

Lors de l’éxécution du script précédent Php me renvoie une erreur:

PHP Fatal error:  Trait method whoami has not been applied, because there are collisions with other trait methods on Foo in traits.php on line 17

Apparemment la surcharge de méthode d’un trait par un autre trait n’est pas possible, ceci crée un conflit. Mais Php nous offre la possibilité à travers le mot clé insteadof (au lieu de) de choisir la méthode de quel trait utiliser.

trait MyTrait {
   public function whoami() {
      echo "MyTrait\n";
   }
}

trait ScdTrait {
   public function whoami() {
      echo "ScdTrait\n";
   }
}

class Foo {
   use MyTrait, ScdTrait {
      ScdTrait::whoami insteadof MyTrait;
   }
}

$foo = new Foo();
$foo->whoami(); // ScdTrait

Voilà, le conflit est résolu. Nous avons demandé à Php d’utiliser la méthode ScdTrait::whoami au lieu de celle de MyTrait.

Création d’alias

Très bien, mais en fait je souhaitais utiliser les méthodes de chacun de ces traits, l’utilisation de insteadof n’a donc pas résolu mon problème.

Encore une fois les développeurs de Php ont pensé à tout, ils nous offrent la possibilité de créer des alias sur nos méthodes, afin de faire cohabiter plusieurs méthodes provenant de différents traits et portant le même nom en utilisant le mot clé as.

trait MyTrait {
   public function whoami() {
      echo "MyTrait\n";
   }
}

trait ScdTrait {
   public function whoami() {
      echo "ScdTrait\n";
   }
}

class Foo {
   use MyTrait, ScdTrait {
      MyTrait::whoami insteadof ScdTrait;
      ScdTrait::whoami as scdWhoami;
   }
}

$foo = new Foo();
$foo->whoami(); // MyTrait
$foo->scdWhoami(); // ScdTrait

Et voilà, je peux maintenant utiliser chacune de ces deux méthodes. Vous remarquerez que la création de l’alias ne résout pas le conflit, et qu’il faut donc tout de même le résoudre via insteadof.

Modification de la portée

Je vais être chiant, mais je ne voulais pas que les méthodes provenant des traits que j’utilise, puissent être appelées depuis une instance de ma classe. Et bien, nous pouvons modifier la portée de la méthode du trait toujours en utilisant le mot clé as.

trait MyTrait {
   public function whoami() {
      echo "MyTrait\n";
   }
}

class Foo {
   use MyTrait {
      MyTrait::whoami as protected;
   }
}

$foo = new Foo();
$foo->whoami(); // MyTrait

Et maintenant lorsque j’appelle la méthode whoami depuis une instance de ma classe, php me renvoie une erreur:

PHP Fatal error:  Call to protected method Foo::whoami()

Surcharge d’une méthode

Surcharge d’une méthode provenant d’un trait:

trait MyTrait {
   public function whoami() {
      echo "MyTrait\n";
   }
}

class Foo {
   use MyTrait;

   public function whoami() {
      echo "Foo\n";
   }
}

$foo = new Foo();
$foo->whoami(); // Foo

Rien de plus simple concernant la surcharge d’une méthode d’un trait par une classe.

Trait composé de trait

Oui oui, vous avez bien lu. Il est donc possible de créer des traits composés d’autres traits.

trait MyTrait {
   public function whoami() {
      echo "MyTrait\n";
   }
}

trait ScdTrait {
   public function scdWhoami() {
      echo "ScdTrait\n";
   }
} 

trait Composed {
   use MyTrait, ScdTrait;
}

class Foo {
   use Composed;
}

$foo = new Foo();
$foo->whoami(); // MyTrait
$foo->scdWhoami(); // ScdTrait

Attribut d’un trait

Un trait peut déclarer des attributs, qui seront disponibles pour la classe utilisant ce trait.

trait MyTrait {
   protected $_sName = "MyTrait";
}

class Foo {

   use MyTrait;

   public function getName() {
      return $this->_sName;
   }
}

$foo = new Foo();
echo $foo->getName(); // MyTrait

J’ai néanmoins trouvé certains comportements bizarre, un en particulier. Une classe utilisant un trait déclarant un attribut, ne pourra pas le surcharger.

trait MyTrait {
   public $_sName = "MyTrait";
}

class Foo {

   use MyTrait;

   protected $_sName = "Foo";

   public function getName() {
      return $this->_sName;
   }
}

$foo = new Foo();
echo $foo->getName(); // MyTrait

Le code ci-dessus est interdit, php me renvoi l’erreur suivante:

PHP Fatal error:  Foo and MyTrait define the same property ($_sName) in the composition of Foo

Mais imaginons que l’attribut du trait soit déclaré avec une portée publique, et que nous ne souhaitons pas qu’il soit accessible depuis une instance de notre classe. Il nous est impossible d’en modifier la portée, si nous n’avons pas la main sur le trait.

php.net ne parle pas des attributs au sein des traits, si quelqu’un à la solution à ce problème j’en serai ravi 🙂

Conclusion

Voilà le confort que va nous apporter les traits.

Bien entendu il va falloir être patient, et attendre la version stable de php 5.4 (et aussi que nos chers sysAdmin nous mettent ça en prod :p)

Category: Php
  • très intéressant.
    maintenant je vais voir ton bench if/switch
    😛

  • Juck

    Bon article , bien expliqué merci