In PHP we don’t have multiple inheritance. Our child classes should always have only one parent class. Some people argue that this is a bad approach because they want to inherit from multiple classes to get access to some useful methods. I hope you are not one of them. Do you remember that inheritance is not about code reuse? It is about one basic type and its specifications. But there are situations where we want to share some functionality between classes that are not related. Copy and paste?
For example, we have an online store application. It has a catalog, that consists of categories and products. We want our URLs to be pretty with nice human readable slugs instead of ids. So, we need to use slugs for both categories and products.
So we end up with code duplication. Duplication leads to bugs. So this is not a very good implementation. Create a
BaseModelWithSlug also is not a very smart idea. You see the problem: we have some shared functionality in not related classes. So we need some way to extract this functionality and then paste it in our classes. And this is exactly the purpose of traits.
Here is a description from php.net:
Traits are a mechanism for code reuse in single inheritance languages such as PHP. A Trait is intended to reduce some limitations of single inheritance by enabling a developer to reuse sets of methods freely in several independent classes living in different class hierarchies.
In other words, traits allow us to extract a closely related group of methods into a single reusable item. And then this item can be pasted into a class. So what is the difference here with a multiple inheritance? A trait is not about inheritance at all. A trait is not inherited from, it is included into a class as a sort of mixin. Traits also provide more control on resolving conflicts that arise when using multiple inheritance. They give us an ability to control ambiguities if a method is declared in several mixed-in traits. Like an abstract class traits cannot be instaintiated.
How does it work? Consider trait as a copy-paste at the language level. When a class uses a trait, this trait will be pasted into a class at a compile time, before the class definition will be parsed. So, when we create objects of this class, all the methods and properties from the trait will be available despite not having them in the class definition itself.
Now, when we know enough about traits and how they work, it’s time to create one. Let’s call it HasSlug.
Now our code looks much better, but not yet perfect. We have our
HasSlug trait and the logic responsible for creating slugs is no longer duplicated.
Category classes simply use this trait. With keyword
use, we tell PHP interpreter to copy the contents of
HasSlug trait and put it in the location where this keyword is used. The line with keyword
use will be replaced with the contents of trait before any code will be executed because PHP needs to find out the final structure of the class.
But, what if we want slugs to be created from different properties? Now we have hardcoded
$this->name, but later we decided that product should have slug from its
shortName property. We need our trait to be customizable.
In the snippet above we have added a one abstract method
getStringForSlug, that returns a string. Then a slug will be generated from this string. So, in every class, we can now customize our slugs, but at the same time, the logic of slugs creation is located in one place. No code duplication. Win.
But with benefits comes problems. And again we come back to types and interfaces. Let’s consider a bit more complex example of
What public interface does this class have? Before using trait it consisted of two methods:
products. But the trait has implicitly modified the class interface and added a new
slug method to it. And there is no guarantee for other classes that
Category provides this method. We need to visit its source code, then jump to trait source code and find it out. Looks like we need an interface here to make
Category again trustable for our code base.
Sluggable interface, we can safely type-hint it in places where we need to call
slug method and sleep safely. The class that consumes
HasSlug trait now also guarantees that it has
Sluggable functionality by implementing the appropriate interface. So, any class that wants to consume
HasSlug trait should also implement
Sluggable interface. It’s time to modify
With this approach, our application stays robust. For small and crud applications it will be too overwhelming, but if you have a large complex code base, may be a corresponding interface for a trait will not be superfluous.
One more problem can arise with traits when trait uses a property that is supposed to be in the consuming class. Remember our first implementation of
HasSlug trait? I’ll remind it’s code to you:
And then we use it in
Product class that has
title property instead of
name. Now application will be broken every time we call
slug method in this class.
Traits are very powerful and flexible tool in languages without multiple inheritance. They can improve your code base and remove duplication from your classes. But from the other side, they can add more complexity or even break your application.