Traits: The Right Way

Posted on

Every iteration of PHP brings more object oriented programming features to the language. In PHP 5.3, we got namespaces, a vital aspect to the current PHP landscape. As of PHP 5.4, a new feature was introduced called Traits. Traits are not a new concept; they have existed as concepts in many object-oriented languages. PHP is one of the first to implement them directly as traits.

What Are Traits?

To answer this question simply: traits are PHP's answer to Multiple Inheritance. The PHP language is a single inheritance language. This means that any given class can only extend a single other class. As an example, let's say you have two base classes:

abstract class BaseA {
    function A() { }
   }

abstract class BaseB {
    function B() { }
}

Unfortunately, as it stands in PHP, you cannot perform multiple inheritance by extending classes. You can extend either BaseA or BaseB, but not both at the same time:

class A extends BaseA { }
class B extends BaseB { }

But what if you want the functionality of both of these in a single class? Traits can solve this problem:

trait BaseA {
    function A() { }
}

trait BaseB {
    function B() { }
}

class AB {
    use BaseA, BaseB;
}

This method of inheritance is known as "Horizontal Reuse". It is intended to reduce code duplication and share code horizontally. In this article, I will explain how to use traits, as well as when to use traits versus traditional inheritance.

Using Traits

As described above, traits are very easy to use. In their most basic form, you declare a trait using the trait keyword:

trait MyTrait {
    public function TraitFunction() {
        echo "This is my trait.";
    }
}

You can then have classes which use this trait:

class ClassWithTrait {
    use MyTrait;

    public function UseTrait() {
        $this->TraitFunction();
    }
}

Traits can also contain class properties:

trait PropertyTrait {
    public $myProperty;
}  

Basically, when a trait is used in a class, it is more or less directly copied into the class. I'll go over some basic properties of traits, and some common use cases.

Trait Scope

One thing to note is that the scope of functions and properties within a trait are inherited by the child class. This means a private, protected, or public object will act in the class the same way it acts as it does in the trait. One way to think about it is that the contents of the trait are more or less directly copied into the classes which use them. What does thas mean for each scope specifically?

Private

Unlike single inheritance, private scope in a trait will be available to the extending classes. Take this example:

trait PrivateTrait {
       private privateFunc() { 
           echo "This is private"; 
    }
}

class PrivateClass {
     use privateTrait;

    // This is allowed.
    public publicFunc() {
         $this->privateFunc();
    }
}

class ExtendingClass extends PrivateClass {

    // This is NOT allowed - privateFunc() is only available
    // to the class which directly utilizes it.
      public someFunc() {
          $this->privateFunc();
    }
}

In the above code, the ExtendingClass will throw an error - this is because it is trying to access a function which is private to PrivateClass.

Protected

Protected scope for a trait means that any class which extends the trait, or the children of the trait are able to access the function or variable. In the previous example, if privateFunc were a protected function, then ExtendingClass would be valid.

Public

Anyone can access the variable or function. Simple as that.

Naming Conflicts

Sometimes, it is possible that you need to import a trait into a class, but there is a conflicting function or variable name. This can be solved using either the as or insteadof operator:

trait A {
    function someFunc() { }
    function otherFunc() { }
}

trait B {
    function someFunc() { }
    function otherFunc() { }
}

class MyClass {
    use A, B {
        A::someFunc insteadof B;
        B::otherFunc as differentFunc;
    }
}

The insteadof specifies that you want to use someFunc from A instead of B. The as operator specifies a different name for the imported function - basically an alias. If you use the insteadof operator and don't alias the replaced function, then it is essentially discarded. In the case above, B::someFunc was discarded since it was not aliased.

Traits vs. Inheritance

Now that we know how to use traits, when do we use them?

The important thing is to know when to use traits and when to use inheritance. I tend to follow a this rule when deciding between using a trait or inheritance: Inheritance is for extending logic; traits are for sharing behavior. These cases are known as Vertical Inheritance and Horizontal Reuse respectively.

For example, If you are implementing a controller within a framework (a la MVC Pattern), you may want to provide a BaseController object, which provides basic controller behavior. The intention is for users to write more customized controller objects which extend this controller. However, in the end, the objects single responsibility is still to be a controller. This is why it's called vertical inheritance, because in a diagram, it looks vertical:


Example Controller UML Diagram

Let's say a few of these controllers (but not all of them) require a database connection. To keep performance up, we shouldn't give every controller the database connection. What we could do is write an abstract class which extends BaseController which provides a database connection. But, in the future, what if an object that is not a controller requires a database connection? Instead of duplicating this logic, we can use horizontal reuse.

A simple trait can be created:

trait DatabaseAware {    
    protected $db;

    public function setDatabase($db) {
        $this->db = $db;
    }

    protected function query($query) {
        $this->db->query($query);
    }
}

This trait now provides classes with common database functionality. Any class which requires a database connection, be it a controller or a manager (or anything), can use this trait:

class IndexController extends BaseController {
    use DatabaseAware;

    public function indexAction() {
        $this->query("SELECT * FROM `someTable`");
    }
}

Here is an example diagram, explaining why it's known as "horizontal reuse". As you can see, the DatabaseAware trait is utilized across multiple different types of objects.

UML Diagram with a Trait

Example of a Trait

Here's an example of a trait that I had used extensively in a project. There was an event system in place, and one of the events fired after a certain portion of the application was initialized. A lot of the logic in the classes worked only before or after this 'bootstrap' process was executed. As a result, the following trait was created to assure this condition was met:

trait BootstrapBehavior {
    private $isBootstrapped = false;

    protected function assertBootstrapped() {
        if(!$this->isBootstrapped) {
            throw new NotBootstrappedException();
        }
    }

    protected function assertNotBootstrapped() {
        if($this->isBootstrapped) {
            throw new BootstrappedException();
        }
    }

    protected function bootstrap() {
        $this->isBootstrapped = true;
    }
}

We were able to use this trait extensively across the project, and add or modify it as we pleased.

comments powered by Disqus