Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Traits in PHP – any real world examples/best practices? [closed]

Tags:

php

traits

People also ask

What is a trait in PHP with suitable example?

What is a trait ? In PHP, a trait is a way to enable developers to reuse methods of independent classes that exist in different inheritance hierarchies. Simply put, traits allow you to create desirable methods in a class setting, using the trait keyword. You can then inherit this class through the use keyword.

Should you use traits PHP?

The main purpose of PHP Traits is to solve the single inheritance limitations of the language, because in PHP you can't do extends Class1, Class2 . Traits contain a declaration of the methods or the properties that you can easily apply to the classes using the keyword use .

What are traits How is it used in PHP?

Traits are used to declare methods that can be used in multiple classes. Traits can have methods and abstract methods that can be used in multiple classes, and the methods can have any access modifier (public, private, or protected).

When can you use traits *?

When to Use Traits? If the behavior will not be reused, then make it a concrete class. It is not reusable behavior after all. If it might be reused in multiple, unrelated classes, make it a trait.


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.


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.


:) 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.


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.