When to use Traits in PHP

TL/DR: never. Avoid using Traits or Inheritance at all.

Traits were introduced to PHP in version 5.4. While classes were introduced in version 4, class inheritance was – as in Java – limited to single inheritance only from the very beginning.

Class Inheritance: The Good, The Bad, The Ugly

Class Inheritance is a central concept in object oriented programming and exists in every programming language. However, some of the languages – such as Java – do not allow inheriting from multiple parent classes whereas languages as C++ do.

My personal opinion is that inheritance may be useful for certain scenarios. For instance, let‘s say we will build an frontend application with different screen types. The base screen type is a static page, the regular screen has a top navigation and the advanced screen provides a side menu besides to the top navigation.

In this case, it may make sense to have a base class for the static page, another TopNavigation class which inherits from the base class and a third class SideBar which inherits from TopNavigation. These classes probably will have limited functionality with less overridden methods and/or variables.

On the other hand, inheritance is getting a mess very quick once you use it as a key concept in your application architecture. For instance, subclasses can override methods and – even worse – members or constants which leads to confusion since you do not know what value the constant actually contains.

The diamond problem (with multiple inheritance and traits)
Source: Wikipedia

Looking at programming languages that allow multiple inheritance – for instance C++ – we can see that having multiple inheritance leads to more problems than it actually solves. For instance, one of the main problems with multiple inheritance is the diamond problem.

If B and C inherit from A and D inherits from both B and C, then which
version of a method does D inherit: that of B, or that of C? This is known as the diamond problem where B and C inherit from A, and D inherits from both B and C.

So what are Traits?

To be very direct: Traits are a bad compromise to bypass the „problem“ with single inheritance. A class or even a Trait can use one or more Traits and thus, you will not only introduce the potential to get the diamond problem, but may also create a chain of traits – even in a loop – and it can be valid code!

Further, traits do not allow private or protected methods/members. Traits may assume that some methods/members are just there and use them as you would use private methods in a regular class. In terms of object oriented programming, Traits do not have an „identity“ and thus, you can not do check with the instanceof operator.

Benefits of using Traits

Again, to be very direct: There are limited scenarios where Traits are acceptable. One scenario would be in a enterprise/legacy environment where refactoring to concepts like Dependency Injection is simply the effort not worth it. Another scenario is where Traits do not influence the overall architecture at all. For instance, imagine you want to restrict magic getters/and setters. A Trait can be helpful, as described here:

Again, this is acceptable not my preferred way. Using strict typing with static code analysis and code sniffer tools should help avoiding stuff like magic methods.

Better Alternatives to Traits

I personally avoid not only Traits, but also inheritance at all. Once introduced into the architecture, inheritance will make it hard to keep an overview of the class hierarchy and structure. Same – or even worse – counts for Traits: thanks to the flexible nature of multiple Traits in a class, chaining and overriding methods and constants, Traits will get a mess very quickly.

The alternative is: Dependency Injection, sometimes also described as composition over inheritance. Sure, DI has also its downsides but one major advantage is the decoupling nature of services. The services are registered in a central container – ideally against an interface, not an actual implementation – and the classes consuming the services are only aware of the results of method calls. There is no chaining, no overriding of methods or members and no direct interaction between caller and consumer. Further, if the actual implementation needs to be changed then the caller class does need to care about this.