web-dev-qa-db-fra.com

Traits in PHP - des exemples concrets / meilleures pratiques?

Traits ont été l’un des plus importants ajouts pour PHP 5.4. Je connais la syntaxe et comprends l’idée des traits, comme la réutilisation de code horizontal pour des tâches courantes telles que la journalisation. , sécurité, mise en cache, etc.

Cependant, je ne sais toujours pas comment utiliser les traits dans mes projets.

Existe-t-il des projets open source qui utilisent déjà des traits? De bons articles/matériel de lecture sur la manière de structurer des architectures à l'aide de traits?

145
Max

Mon opinion personnelle est qu’il ya très peu d’application pour les traits lors de l’écriture de code propre.

Au lieu d'utiliser des traits pour pirater du code dans une classe, il est préférable de passer les dépendances via le constructeur ou via des setters:

class ClassName {
    protected $logger;

    public function __construct(LoggerInterface $logger) {
        $this->logger = $logger;
    }
    // or
    public function setLogger(LoggerInterface $logger) {
        $this->logger = $logger;
    }
}

La principale raison pour laquelle je trouve que mieux que d'utiliser des traits est que votre code est beaucoup plus flexible en supprimant le couplage dur à un trait. Par exemple, vous pouvez simplement passer une classe de consignateur différente maintenant. Cela rend votre code réutilisable et testable.

84
NikiC

Je suppose qu’il faudrait se pencher sur les langues qui présentent des traits pendant un certain temps pour apprendre les bonnes/meilleures pratiques acceptées. Mon opinion actuelle sur Trait est que vous ne devriez les utiliser que pour du code que vous auriez à dupliquer dans d'autres classes partageant les mêmes fonctionnalités.

Exemple pour un trait Logger:

interface Logger
{
    public function log($message, $level);    
}

class DemoLogger implements Logger
{
    public function log($message, $level)
    {
        echo "Logged message: $message with level $level", PHP_EOL; 
    }
}

trait Loggable // implements Logger
{
    protected $logger;
    public function setLogger(Logger $logger)
    {
        $this->logger = $logger;
    }
    public function log($message, $level)
    {
        $this->logger->log($message, $level);
    }
}

class Foo implements Logger
{
    use Loggable;
}

Et puis vous faites ( démo )

$foo = new Foo;
$foo->setLogger(new DemoLogger);
$foo->log('It works', 1);

Je suppose que l’important, lorsqu’on utilise des traits, c’est qu’ils ne sont en réalité que des morceaux de code copiés dans la classe. Cela peut facilement entraîner des conflits, par exemple lorsque vous essayez de modifier la visibilité des méthodes, par exemple.

trait T {
    protected function foo() {}
}
class A { 
    public function foo() {}
}
class B extends A
{
    use T;
}

Ce qui précède entraînera une erreur ( démo ). De même, toutes les méthodes déclarées dans le trait qui le sont déjà dans la classe using ne seront pas copiées dans la classe, par exemple.

trait T {
    public function foo() {
    return 1;
}
}
class A { 
    use T;
    public function foo() {
    return 2;
}
}

$a = new A;
echo $a->foo();

imprimera 2 ( démo ). Ce sont des choses que vous voudrez éviter car elles rendent les erreurs difficiles à trouver. Vous voudrez également éviter de mettre des éléments dans des traits qui opèrent sur des propriétés ou des méthodes de la classe qui les utilise, par exemple.

class A
{
    use T;
    protected $prop = 1;
    protected function getProp() {
        return $this->prop;
    }
}

trait T
{
    public function foo()
    {
        return $this->getProp();
    }
}

$a = new A;
echo $a->foo();

fonctionne ( démo ) mais maintenant le trait est intimement couplé à A et toute l'idée de réutilisation horizontale est perdue.

Lorsque vous suivez le Principe de séparation d’interface , vous aurez de nombreuses petites classes et interfaces. Cela fait de Traits un candidat idéal pour les choses que vous avez mentionnées, par exemple. préoccupations transversales , mais pas pour composer des objets (dans un sens structurel). Dans notre exemple d'enregistreur ci-dessus, le trait est complètement isolé. Il n'a pas de dépendances sur des classes concrètes.

Nous pourrions utiliser agrégation/composition (comme indiqué ailleurs sur cette page) pour obtenir la même classe résultante, mais l'inconvénient de l'utilisation de l'agrégation/composition est que nous devrons ajouter les méthodes proxy/délégateur manuellement. chaque classe devrait pouvoir se connecter. Les caractères résolvent ce problème en me permettant de garder le passe-partout au même endroit et de l'appliquer de manière sélective là où cela est nécessaire.

Remarque: étant donné que les traits sont un nouveau concept en PHP, toutes les opinions exprimées ci-dessus sont susceptibles de changer. Je n'ai pas encore eu beaucoup de temps pour évaluer le concept moi-même. Mais j’espère que c’est suffisant pour vous donner à réfléchir.

194
Gordon

:) Je n'aime pas théoriser et débattre de ce qui devrait être fait avec quelque chose. Dans ce cas, traits. Je vais vous montrer pourquoi je trouve les traits utiles et vous pouvez en tirer des leçons ou les ignorer.

Traits - ils sont intéressants à appliquer stratégies. En bref, les modèles de conception de stratégie sont utiles lorsque vous souhaitez que les mêmes données soient traitées (filtrées, triées, etc.) différemment.

Par exemple, vous avez une liste de produits que vous souhaitez filtrer en fonction de certains critères (marques, spécifications, etc.) ou triés par différents moyens (prix, libellé, etc.). Vous pouvez créer un trait de tri contenant différentes fonctions pour différents types de tri (numérique, chaîne, date, etc.). Vous pouvez ensuite utiliser cet attribut non seulement dans votre classe de produit (comme indiqué dans l'exemple), mais également dans d'autres classes nécessitant des stratégies similaires (appliquer un tri numérique à certaines données, etc.).

Essayez le:

<?php
trait SortStrategy {
    private $sort_field = null;
    private function string_asc($item1, $item2) {
        return strnatcmp($item1[$this->sort_field], $item2[$this->sort_field]);
    }
    private function string_desc($item1, $item2) {
        return strnatcmp($item2[$this->sort_field], $item1[$this->sort_field]);
    }
    private function num_asc($item1, $item2) {
        if ($item1[$this->sort_field] == $item2[$this->sort_field]) return 0;
        return ($item1[$this->sort_field] < $item2[$this->sort_field] ? -1 : 1 );
    }
    private function num_desc($item1, $item2) {
        if ($item1[$this->sort_field] == $item2[$this->sort_field]) return 0;
        return ($item1[$this->sort_field] > $item2[$this->sort_field] ? -1 : 1 );
    }
    private function date_asc($item1, $item2) {
        $date1 = intval(str_replace('-', '', $item1[$this->sort_field]));
        $date2 = intval(str_replace('-', '', $item2[$this->sort_field]));
        if ($date1 == $date2) return 0;
        return ($date1 < $date2 ? -1 : 1 );
    }
    private function date_desc($item1, $item2) {
        $date1 = intval(str_replace('-', '', $item1[$this->sort_field]));
        $date2 = intval(str_replace('-', '', $item2[$this->sort_field]));
        if ($date1 == $date2) return 0;
        return ($date1 > $date2 ? -1 : 1 );
    }
}

class Product {
    public $data = array();

    use SortStrategy;

    public function get() {
        // do something to get the data, for this ex. I just included an array
        $this->data = array(
            101222 => array('label' => 'Awesome product', 'price' => 10.50, 'date_added' => '2012-02-01'),
            101232 => array('label' => 'Not so awesome product', 'price' => 5.20, 'date_added' => '2012-03-20'),
            101241 => array('label' => 'Pretty neat product', 'price' => 9.65, 'date_added' => '2012-04-15'),
            101256 => array('label' => 'Freakishly cool product', 'price' => 12.55, 'date_added' => '2012-01-11'),
            101219 => array('label' => 'Meh product', 'price' => 3.69, 'date_added' => '2012-06-11'),
        );
    }

    public function sort_by($by = 'price', $type = 'asc') {
        if (!preg_match('/^(asc|desc)$/', $type)) $type = 'asc';
        switch ($by) {
            case 'name':
                $this->sort_field = 'label';
                uasort($this->data, array('Product', 'string_'.$type));
            break;
            case 'date':
                $this->sort_field = 'date_added';
                uasort($this->data, array('Product', 'date_'.$type));
            break;
            default:
                $this->sort_field = 'price';
                uasort($this->data, array('Product', 'num_'.$type));
        }
    }
}

$product = new Product();
$product->get();
$product->sort_by('name');
echo '<pre>'.print_r($product->data, true).'</pre>';
?>

En guise de conclusion, je pense à des traits comme les accessoires (que je peux utiliser pour modifier mes données). Des méthodes et propriétés similaires peuvent être supprimées de mes classes et placées dans un seul endroit, pour un entretien facile, un code plus court et plus propre.

19
D. Marti

Je suis enthousiasmé par Traits parce qu’ils résolvent un problème commun lors du développement d’extensions pour la plate-forme de commerce électronique Magento. Le problème se produit lorsque des extensions ajoutent des fonctionnalités à une classe principale (comme par exemple le modèle User) en l'étendant. Pour ce faire, pointez l'autoloader Zend (via un fichier de configuration XML) pour qu'il utilise le modèle User à partir de l'extension et que ce nouveau modèle étende le modèle principal. ( exemple ) Mais que se passe-t-il si deux extensions remplacent le même modèle? Vous obtenez une "condition de concurrence" et un seul est chargé.

À l'heure actuelle, la solution consiste à modifier les extensions afin que l'une d'eux étende la classe de substitution de modèle de l'autre dans une chaîne, puis définissez la configuration de l'extension de manière à ce qu'elles soient chargées dans le bon ordre pour que la chaîne d'héritage fonctionne.

Ce système génère souvent des erreurs et lors de l'installation de nouvelles extensions, il est nécessaire de vérifier les conflits et de modifier les extensions. C'est une douleur et brise le processus de mise à niveau.

Je pense que l’utilisation de Traits serait un bon moyen d’accomplir la même chose sans cette "condition de concurrence" gênante pour le modèle. Certes, il pourrait toujours y avoir des conflits si plusieurs traits utilisaient des méthodes portant les mêmes noms, mais j'imagine qu'une convention comme un simple espace de noms pourrait résoudre la plupart des problèmes.

TL; DR Je pense que Traits pourrait être utile pour créer des extensions/modules/plugins pour de gros PHP paquets logiciels tels que Magento.

4
thaddeusmt

Vous pourriez avoir un trait pour un objet en lecture seule comme ceci:

  trait ReadOnly{  
      protected $readonly = false;

      public function setReadonly($value){ $this->readonly = (bool)$value; }
      public function getReadonly($value){ return $this->readonly; }
  }

Vous pouvez détecter si cet trait est utilisé et déterminer si vous devez ou non écrire cet objet dans une base de données, un fichier, etc.

0
Nico