Traits are not inherited

Posted on

I recently saw this tweet by Jess Archer, which showed some Trait behavior that might not make sense at first glance. I thought it could be fun to explain why this happens.

While saying Enableable is cool and all; how in the world is it possible that the TimeCircuits::$enabled variable is still false, when it was clearly updated on the Enableable trait? The answer is: a trait is not inherited.



Single Inheritance vs. composition

PHP is a so called Single Inheritance language meaning any class can inherit context from only one parent. In most cases this works out fine, but sometimes you have code in a class you want to re-use. This is impossible if the class you want to use this on already extends another class. This is why PHP introduced traits.

A trait is a kind of mini-class that can be used inside multiple classes. It can have methods, parameters, static methods and static variables, just like a class. Even the visibility like private, protected and public works the same. But instead inheriting from these traits by using extends, you have to use the trait inside a class. You actually have to use a trait inside a class, because you cannot instantiate it on its own. A class can also use multiple traits. This way you compose a new class made up of small re-usable pieces of code.



Traits are copy-pasted

While a trait looks like any other class, this use-ing instead of extending makes a big difference. When you use a trait, the current state of the trait is copied to the class. And this copying is the reason why TimeCircuits is unaffected by the change on the Enableable trait. It is not inheriting this variable; it has it’s own copy of it. And because the parameter was set to true before the FluxCapacitor class was created, this class has a copy of that state. Resetting Enableable::$enabled to false at the end will therefore still have no impact on either TimeCircuits or FluxCapacitor. They are completely separate parameters.



Classes inherit

So let’s see how a class would react to a similar situation. We’ll create a Base class that has a static $enabled parameter, and an Extended class that extends (and therefore inherits from) Base. Then check out what happens when we change the value of this variable.

class Base {
    public static $enabled = false;
}

class Extended extends Base {
}

Base::$enabled = true;

var_dump(Base::$enabled, Extended::$enabled); // (bool) true, (bool) true
Enter fullscreen mode

Exit fullscreen mode

So here you see the inheritance in action. When we update the parameter on the Base class, it’s extending classes are affected by this update, because they are actually referencing the same parameter, and not a copy. And to prove that they are referencing the same parameter you can change Extended::$enabled to true instead of Base::$enabled and the result will still be the same.



Final notes

I’m not sure if Jess wanted a way to enable all classes that used this trait at the same time. If that is the case, I see 2 alternatives:

  1. Let those classes extend from an intermediate class that has this parameter: for example a Model could extend a EnableableModel that has this parameter. In that case you could update EnableableModel::$enabled.
  2. Register an interface on all the classes that use this trait, and put them in a container. Then retrieve all classes from the container that have this interface, and update every one separately.

You might also be interested in my blog post on Testing Traits in PHPUnit. In this post I’ll show you some handy tips and tricks for testing traits.

Leave a Reply

Your email address will not be published.