DEV Community

Cover image for Traits are not inherited
Doeke Norg
Doeke Norg

Posted on • Originally published at doeken.org

Traits are not inherited

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.

Top comments (3)

Collapse
 
bdelespierre profile image
Benjamin Delespierre • Edited

That actually makes sense. The static property belongs to the trait so changing its value will effectively change it from every using class' perspective.

As a seasonned PHP developper, I would advise you to avoid static as much as possible. Rules of static inheritance in PHP are difficult to understand and the staticness will make your code more rigid. Use dependency injection instead.

Want an example? PHP will let you write this monstruosity:

class A
{
    public funciton foo()
    {
        return static::bar();
    }

    abstract protected static function bar();
}

class B extends A 
{
    protected static function bar()
    {
        return "Hello!";
    }
}

B::foo(); // Hello!
Enter fullscreen mode Exit fullscreen mode
Collapse
 
doekenorg profile image
Doeke Norg

Hi @bdelespierre , I'm not quite sure what you mean. The static property on the trait does not change it on every class using it; that was the point of this post :-) But maybe I'm misunderstanding your comment.

I do somewhat agree on your point of view of static methods. They should be used sparingly, albeit only for the testing hell. And although singletons have their time and place; dependency injection is preferred almost always. But singletons are only one use-case of static methods of course :-)

I think some people have a hard time grasping the difference between static and non-static when it comes to classes or instances of those classes. I think proper education is key in using these concepts to your benefit. And I hope a post like this contributes to that end :-)

Thank you for your comment and insights!

Collapse
 
nombrekeff profile image
Keff • Edited

Nice article, I always wondered that about traits (I'm not a PHP dev, just had to touch it on occasions)