If you prefer a video version of this post, then you can watch it here: https://devlob.com/courses/design-patterns-in-php-to-solve-real-life-software-problems/antipatterns/traits
Inheritance is very common in OOP languages, however, traits is a unique feature available in PHP that provides multiple inheritance.
Let’s start with inheritance, because if inheritance is bad then traits must be awful!
Before I go through the explanation, I want to be transparent about this post.
I don’t find inheritance a disaster, I just think developers overuse it. Most of the times it is not needed. If I have to use it, then I swap for an abstract class if possible.
In this post, I want to discuss a critical problem that arises with inheritance and traits.
The problem of inheritance
In my course Design Patterns in PHP to Solve real-life Software Problems we used inheritance, but we always extended an abstract class.
Is it bad to extend a class? How bad is it?
Let’s set up a small example to understand the problem behind inheritance.
class Operations
{
public function edit()
{
echo 'Edit';
}
public function delete()
{
echo 'Delete';
}
}
The class above contains 2 methods, edit, and delete. However, only an admin can call delete. Now let’s create an Editor class that extends Operations.
class Editor extends Operations
{
}
Because Editor extends Operations we can call the edit method on an editor object.
$alex = new Editor();
$alex->edit();
That’s fine, but because of inheritance, the object also has access to the delete method.
$alex->delete();
However, this is a huge problem. An editor should not be able to delete a post.
The delete method right now simply has an echo, but imagine that inside that we have more code. Imagine that delete will take some actions, actions that an editor should not be able to take, that will result in errors.
The same issue arises with traits in PHP. Let’s create an AddressTrait.
<?phptrait AddressTrait
{
protected $address1, $address2; public function setAddress1(string $address)
{
if(empty($address))
throw new \Exception('Address cannot be empty'); $this->address1 = $address;
} public function setAddress2(string $address)
{
if(empty($address))
throw new \Exception('Address cannot be empty'); $this->address2 = $address;
}
}
We will use this trait for Destination and User classes.
<?phpclass Destination
{
use AddressTrait;
}
The problem, in this case, is that a User should have only 1 address. Destination class is fine. We can set up address 1 and address 2, but a User should have only 1 address.
<?phpnamespace DesignPatternsInPHP\Antipatterns\Traits;class User
{
use AddressTrait;
}
Because of the way traits work, a user can call setAddress2.
$user = new User();
$user->setAddress1('Tirana');// Called by mistake. User table only has address1,
// this will throw a database error
// Trying to set a field that doesn't exist.
$user->setAddress2('London');
The code above will throw a database error. The table ‘users’ doesn’t have an address2 field and since setAddress2 will update that field on the ‘users’ table, it will throw a database error.
To better understand this problem, imagine the following scenario:
We have Jhon the developer, Alice the project manager and Alex the editor.
Alice — John please hurry, we need this feature deployed in 10 minutes.
Jhon — Ok Alice, just a second “accidentally calls delete instead of edit”
… 10 minutes later …
Alice — Alex you have a typo here, please fix it.
Alex — Ok Alice. “Fixes type and clicks on update”. Guys, I lost the article, I think it got deleted.
If Jhon had used an abstract class instead with an empty body for delete, this would have never happened.
In a nutshell. By using inheritance and traits we get access to methods that shouldn’t be there!
The solution
The solution to this problem is to use abstract classes and interfaces as we did in Design Patterns in PHP to Solve real-life Software Problems.
We solved real-life programming problems and we never extended a class, we always extended an abstract class.
Let’s start with interfaces.
An interface is just a contract. You simply define what methods a class should implement. Methods inside an interface DO NOT have a body!
Let’s now go back to the first example. We will change the Operations class to an interface.
interface OperationsInterface
{
public function edit();
public function delete();
}
Editor class will implement that interface:
class Editor implements OperationsInterface
{ public function edit()
{
echo 'Edit';
}
public function delete()
{}
}
Oh, wait… Do we now have an empty method? Yes, we do! Is that bad? NO!
An empty method that does nothing is better than a method that has code inside and takes some actions and modifies our data.
$alex = new Editor();
$alex->edit();
$alex->delete();
Alex can edit as usual and if you accidentally call delete on Alex… well, nothing happens! The method is empty!
However, in the case of inheritance, the delete method will have code. Code that can be catastrophic for your application, simply because an editor cannot delete a post and calling delete on an editor will result in errors.
If you prefer an abstract class, then you can have the following.
abstract class AbstractOperations
{
abstract public function edit()
{
echo 'Edit';
}
abstract public function delete();
}
You can have your usual code inside edit and keep delete empty since it should be implemented only by authorized users.
Conclusion
This post focuses on an important OOP design. You could easily have an if statement inside delete that checks whether a user is an admin, but as I said, we focused on an important OOP design here. Inheritance and traits are an OOP design in PHP that describe how classes communicate together.
If you want to take a look at my course, then you can watch it here Design Patterns in PHP to Solve real-life Software Problems. It is a premium course, but I have some free videos there as well.