163

Traits have been one of the biggest additions for PHP 5.4. I know the syntax and understand the idea behind traits, like horizontal code re-use for common stuff like logging, security, caching etc.

However, I still don't know how I would make use of traits in my projects.

Are there any open source projects that already use traits? Any good articles/reading material on how to structure architectures using traits?

dayuloli
  • 16,205
  • 16
  • 71
  • 126
Max
  • 15,693
  • 14
  • 81
  • 131
  • 8
    Here's my opinion: a [blog post](http://blog.ircmaxell.com/2011/07/are-traits-new-eval.html) on the subject that I wrote on the topic. TL;DR: Basically, I fear that while they are powerful and can be used for good, the majority of uses we'll see are going to be complete anti-patterns and cause far more pain than they solve... – ircmaxell Oct 25 '11 at 19:15
  • 1
    Take a look at [scala standard library](http://www.scala-lang.org/api/current/index.html#package) and you will find out many useful examples of traits. – dmitry Oct 03 '12 at 14:57
  • How is it that a post closed by 4 users have over 160 upvotes? – crafter Apr 20 '23 at 13:45

5 Answers5

225

I guess one would have to look into languages that have Traits for some time now to learn the accepted Good/Best practices. My current opinion on Trait is that you should only use them for code that you would have to duplicate in other classes that share the same functionality.

Example for a Logger trait:

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;
}

And then you do (demo)

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

I guess the important thing to consider when using traits is that they really are just pieces of code that get copied into the class. This can easily lead to conflicts, for instance, when you try to change visibility of methods, e.g.

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

The above will result in an error (demo). Likewise, any methods declared in the trait that are also already declared in the using class will not get copied into the class, e.g.

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

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

will print 2 (demo). These are things you will want to avoid because they make errors hard to find. You will also want to avoid putting things into traits that operate on properties or methods of the class that uses it, e.g.

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();

works (demo) but now the trait is intimately coupled to A and the whole idea of horizontal reuse is lost.

When you follow the Interface Segregation Principle you will have many small classes and interfaces. That makes Traits an ideal candidate for the things you mentioned, e.g. crosscutting concerns, but not to compose objects (in a structual sense). In our Logger example above, the trait is completely isolated. It has no dependencies on concrete classes.

We could use aggregation/composition (like shown elsewhere on this page) to achieve the same resulting class, but the drawback of using aggregation/composition is that we will have to add the proxy/delegator methods manually to each and every class then that should be able to log. Traits solve this nicely by allowing me to keep the boilerplate in one place and selectively apply it where needed.

Note: given that traits are a new concept in PHP, all opinion expressed above is subject to change. I've not have had much time to evaluate the concept myself yet. But I hope it is good enough to give you something to think about.

Gordon
  • 312,688
  • 75
  • 539
  • 559
  • 44
    Thats an interesting use case: use an interface which defines the contract, use the trait in order to satisfy that contract. Good one. – Max Oct 25 '11 at 18:41
  • 14
    I like this kind of true programmers, who propose a real working examples with short desc for each. Thx – Arthur Kushman Oct 11 '12 at 07:11
  • 1
    What if someone use an abstract class instead? Replacing the interface and trait, one can create an abstract class. Also if interface is so necessary for the application, abstract class can also implement the interface and define the methods like trait did. So can you explain why we still need traits? – sumanchalki Jan 16 '13 at 14:31
  • 12
    @sumanchalki Abstract class follow the rules of Inheritance. What if you needed a class that implements Loggable and Cacheable? You'd need the class to extend AbstractLogger which needs to extend AbstractCache then. But that means all Loggables are Caches. That is a coupling you do not want. It limits reuse and messes up your inheritance graph. – Gordon Jan 16 '13 at 14:41
  • i am wondering how the `demoLogger` getting connected with `Logger` :( – Red Dec 09 '13 at 07:33
  • @Red In the above example `demoLogger` isn't relying on any outside class to handle logging. It just echoes a message when it logs. As @Gordon explained, its native `log()` function won't be over-written by the trait it uses. So I think it's just meant to serve as an example of a class implementing the `Logger` interface *without* the use of traits. – patricksayshi Mar 04 '14 at 15:34
  • 1
    I think demo links are dead – Peyman Mohamadpour Mar 14 '16 at 06:47
  • ok, i'm going to upvote another random answer of your, only because this would deserve at least a +20: this is the best way i've found to finally avoid all the get-boilerplate code to access the components methods from the composite, like `$foo->getLogger()->log()`, Now i can do `$foo->log()` and in the same time keep using composition. Great ! – Moppo Jul 24 '16 at 09:31
104

My personal opinion is that there is actually very little application for traits when writing clean code.

Instead of using traits to hack code into a class it is better to pass in the dependencies via the constructor or via setters:

class ClassName {
    protected $logger;

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

The main reason why I find that better than using traits is that your code is much more flexible by removing the hard coupling to a trait. For example you could simply pass a different logger class now. This makes your code reusable and testable.

NikiC
  • 100,734
  • 37
  • 191
  • 225
  • 5
    Using traits, you can also use another logger class right? Just edit the trait, and all classes that use the trait gets updated. Correct me if I'm wrong – rickchristie Oct 25 '11 at 17:01
  • 17
    @rickchristie Sure, you could do that. But you would need to edit the source code of the trait. So you would change it for every class using it, not just the particular one you want a different logger for. And what if you want to use the same class but with two different loggers? Or if you want to pass a mock-logger while testing? You can't, if you use traits, you can, if you use dependency injection. – NikiC Oct 25 '11 at 17:05
  • 3
    I can see your point, I´m also pondering whether traits are worth it or not. I mean, in modern frameworks like Symfony 2 you have dependency injection all over the place which seems superioir over traits in most of the cases. At the moment I see traits as not much more then "compiler assisted copy & paste". ;) – Max Oct 25 '11 at 18:44
  • 11
    *At the moment I see traits as not much more then "compiler assisted copy & paste". ;)*: @Max: That's exactly what traits were designed to be, so that's completely correct. It makes it more "maintainable", since there's only one definition, but it's basically just c&p... – ircmaxell Oct 25 '11 at 19:32
  • @ircmaxell and NikiC – what also comes to my mind: my preferred way of injecting dependencies in a class is a DI container as the mentioned Smyfony 2 framework has. But in some situations a DI contaier is not available/cannot be implemented, like legacy code bases. Maybe traits could assist there as a language supported feature to make horizontal code reusage more easy? – Max Oct 25 '11 at 20:38
  • 1
    @Max: I'd argue that the legacy codebase you speak of would benefit more from refactoring to be able to use the DI (and hence be testable and gain all the benefits that brings) than it would from leveraging in traits there... – ircmaxell Oct 25 '11 at 21:16
  • @ircmaxell thats probably true, always depends on how large the refactoring is. I mean, e. g. its not always possible maybe because of time constraits, so if I´d be pressed I´d rather add a trait instead of copying some more code and then, as you correctly mentioned, fix it up in the next iteration. Just as an idea where traits could maybe of use. – Max Oct 25 '11 at 21:35
  • 31
    NikiC's is missing the point: using a trait doesn't prevent using Dependency Injection. In this case, a trait would just let every class that implements logging not have to duplicate the setLogger() method and creation of the $logger property. The trait would provide them. setLogger() would type hint on LoggerInterface as the example does, so that any type of logger can be passed in. This idea is similar to Gordon's answer below (only it looks like he's type hinting on a Logger super class rather than a Logger interface). – Ethan Oct 06 '12 at 21:18
  • 1
    Too often the architecture that people propose is the _"right"_ architecture is so complex that many can't wrap their head around it and this it results if more harm than good. Dependency Injection and Traits can be used for similar purposes but they are each optimized for different use-cases. Using DI when Traits is more appropriate results in a much more complex and thus difficult to use class hierarchy. – MikeSchinkel Oct 09 '13 at 21:05
  • @NikiC This just show you didn't understand traits at all. Traits are the only way, at least in PHP, to inject reusable code in classes with different ancestors. For example, reusing the same voting methods on both Post and Comment classes. It's an alternative approach to obtain multiple inheritance in a language that doesn't support it. I use heavily traits. – noun Oct 02 '14 at 21:56
  • 2
    @noun: I guess you don't know who nikiC is... hint: check the contributors to the php-src repo on github: you needn't scroll a lot, he's one of the top contributors. For all you know, he's even written part of the trait implementation. And seeing as you use traits so much: here's a blog-post about [why you shouldn't](https://eliasvo.wordpress.com/2015/06/07/php-traits-if-they-werent-evil-theyd-be-funny/). C++ supports multiple inheritance, but many devs will tell you you shouldn't go down that path, yes traits exist, does that make them good? No – Elias Van Ootegem Jun 07 '15 at 11:07
  • @Elias Really? Ipse Dixit! Since NikiC is a PHP base code contributor his opinion is relevant and mine is not? That's funny indeed. He is just wrong. To solve all the problems you described in your article just use an interface and do the trait implement the interface itself. Type hinting is preserved and so is the contract. If you change the trait, and the new implementation doesn't satisfy the interface, the lint will highlight the errors, because the contract is between the interface and the class who implemented it. A trait is just a piece of code injected by the interpreter. – noun Jun 16 '15 at 13:23
  • @noun: I'm not saying your opinion is irrelevant. I'm just pointing out that your claim that NikiC doesn't understand traits is rather absurd, seeing as he probably knows more about them than you and me combined. What I will say, though, is that you don't seem to get the point I was trying to make in my blog. Sure Trait + Interface solves most of the issues I talk about. However: Interfaces are public methods only. Trait name conflicts still apply, and most of all: I've yet to come across a problem that required me to use traits. – Elias Van Ootegem Jun 16 '15 at 14:06
  • My main problem with traits is simply this: the way traits are implementated leaves too much room for operator error. There are tons of PHP devs who don't understand things like the liskov principle, or SRP, or DI. The way you can abuse traits now won't help the image of PHP being a silly language that is badly built, and not suitable for serious programming – Elias Van Ootegem Jun 16 '15 at 14:07
  • @noun For the record, I don't think traits are entirely useless, they just have relatively little applications that I would consider consistent with well-designed code. Traits are essentially compiler-assisted copy&paste of code and there are not many cases where copy&pasting code is really the preferable solution to a problem. But of course there are some, usually as a result of other language limitations (notably here is multiple inheritance). – NikiC Jun 16 '15 at 15:47
  • @NikiC: does traits being essentially a compiler assisted c&p mechanism imply that disabling OPCode cache can cause an increase in disk IO? and do you happen to know if my suspicions about traits being implemented similarly to abstract classes are founded? thanks – Elias Van Ootegem Jun 17 '15 at 09:54
  • 1
    Dependency injection is an example of composing an instance of an object (e.g. each instance has its own specific dependencies). Traits are an example of **class** composition. You're composing the class itself, so that all instances share those traits. If you used dependency injection to accomplish this, you'd be repeating code on every instantiation. – Cully Aug 17 '15 at 19:06
  • Traits fix the issue of multiple inheritance which does not exist in PHP. – JG Estiot Oct 25 '16 at 10:27
  • IMO traits bring more niggles than they do add benefits, and don't really 'solve' anything. Especially as people wrongly use them as 'utility', which is also terrible. Just DI your things. Someone mentioned "changing a logger class" - why use a trait for logger and not a normal service class? With LoggerService, when some class needs to use LoggerB instead of loggerA, you remove loggerA from the constructor and instead DI loggerB. KISS – James May 21 '23 at 09:56
19

:) I don't like to theorize and debate about what should be done with something. In this case traits. I'll show you what I find traits useful for and you can either learn from it, or ignore it.

Traits - they are great to apply strategies. Strategy design patterns, in short, are useful when you want the same data to be handled (filtered, sorted, etc) differently.

For example, you have a list of products that you want to filter out based on some criteria (brands, specs, whatever), or sorted by different means (price, label, whatever). You can create a sorting trait that contains different functions for different sorting types (numeric, string, date, etc). You can then use this trait not only in your product class (as given in the example), but also in other classes that need similar strategies (to apply a numeric sort to some data, etc).

Try it:

<?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>';
?>

As a closing note, I think about traits like accessories (which I can use to alter my data). Similar methods and properties that can get cut out from my classes and be put into a single place, for easy maintenance, shorter and cleaner code.

D. Marti
  • 365
  • 2
  • 4
  • 1
    While this keeps the public interface clean, the internal one may become really complex with this, especially if you extend this to other things, like colors for example. I think simple functions or static methods to better here. – Sebastian Mach Jan 14 '16 at 08:40
  • I like the term `strategies`. – Rannie Ollit Sep 17 '18 at 02:59
5

I am excited for Traits because they solve a common issue when developing extensions for the Magento ecommerce platform. The problem occurs when extensions add functionality to a core class (like say the User model) by extending it. This is done by pointing the Zend autoloader (via a XML config file) to use the User model from the extension, and have that new model extend the core model. (example) But what if two extensions override the same model? You get a "race condition" and only one is loaded.

The solution right now is to edit the extensions so one extends the other's model override class in a chain, and then set the extension configuration to load them in the correct order so the inheritance chain works.

This system frequently causes errors, and when installing new extensions it's necessary to check for conflicts and edit extensions. This is a pain, and breaks the upgrade process.

I think using Traits would be a good way to accomplish the same thing without this annoying model override "race condition". Granted there could still be conflicts if multiple Traits implement methods with the same names, but I would imagine something like a simple namespace convention could solve this for the most part.

TL;DR I think Traits could be useful for creating extensions/modules/plugins for large PHP software packages like Magento.

Community
  • 1
  • 1
thaddeusmt
  • 15,410
  • 9
  • 67
  • 67
  • But normal classes already do this, you just use them in some specific way, Adaptor, Decorator, Factory, etc – James May 21 '23 at 09:57
1

You could have a trait for read-only object like this:

  trait ReadOnly{  
      protected $readonly = false;

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

You could detect if that trait is used and determine wheter or not you should write that object in a database, file, etc.

Max
  • 15,693
  • 14
  • 81
  • 131
Nico
  • 6,395
  • 4
  • 25
  • 34
  • So the class that would `use` this trait then would call `if($this -> getReadonly($value))`; but this would generate an error if you did not `use` this trait. Therefor this example is flawed. – Luceos May 23 '13 at 07:20
  • Well, you need to check if the trait is in use first. If the ReadOnly trait is defined on an object, you can then check if it is readonly or not. – Nico Jul 22 '13 at 19:19
  • I did a generic proof of concept for such a trait in https://gist.github.com/gooh/4960073 – Gordon Sep 04 '13 at 11:35
  • 3
    You should declare an interface for ReadOnly for that purpose – Michael Tsang Jan 19 '17 at 09:42