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
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:
- Let those classes extend from an intermediate class that has this parameter: for example a
Model
could extend aEnableableModel
that has this parameter. In that case you could updateEnableableModel::$enabled
. - 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)
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:
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!
Nice article, I always wondered that about traits (I'm not a PHP dev, just had to touch it on occasions)