So far, you have learned that extending from classes allows you to inherit code (properties and method implementations), but it has the limitation of extending only from one class each time. On the other hand, you can use interfaces to implement multiple behaviors from the same class, but you cannot inherit code in this way. To fill this gap, that is, to be able to inherit code from multiple places, you have traits.
Traits are mechanisms that allow you to reuse code, "inheriting", or rather copy-pasting code, from multiple sources at the same time. Traits, as abstract classes or interfaces, cannot be instantiated; they are just containers of functionality that can be used from other classes.
If you remember, we have some code in the
Person
class that manages the assignment
of IDs. This code is not really part of a person, but rather part
of an ID system that could be used by some other entity that has to
be identified with IDs too. One way to extract this functionality
from Person
—and we are not saying that
it is the best way to do so, but for the sake of seeing traits in
action, we choose this one—is to move it to a trait.
To define a trait, do as if you were defining a class, just use the keyword
trait
instead of class
. Define its namespace, add the use
statements needed, declare its properties and
implement its methods, and place everything in a file that follows
the same conventions. Add the following code to the src/Utils/Unique.php
file:
<?php namespace Bookstore\Utils; trait Unique { private static $lastId = 0; protected $id; public function setId(int $id) { if (empty($id)) { $this->id = ++self::$lastId; } else { $this->id = $id; if ($id > self::$lastId) { self::$lastId = $id; } } } public static function getLastId(): int { return self::$lastId; } public function getId(): int { return $this->id; } }
Observe that the namespace is not the same as usual, since we are storing this code in a different file. This is a matter of conventions, but you are entirely free to use the file structure that you consider better for each case. In this case, we do not think that this trait represents "business logic" like customers and books do; instead, it represents a utility for managing the assignment of IDs.
We include all the code related to IDs from
Person
. That includes the properties,
the getters, and the code inside the constructor. As the trait
cannot be instantiated, we cannot add a constructor. Instead, we
added a setId
method that contains the
code. When constructing a new instance that uses this trait, we can
invoke this setId
method to set the ID
based on what the user sends as an argument.
The class Person
will have to change too. We have
to remove all references to IDs and we will have to define somehow
that the class is using the trait. To do that, we use the keyword
use
, like in namespaces, but inside the
class. Let's see what it would look like:
<?php namespace Bookstore\Domain; use Bookstore\Utils\Unique; class Person { use Unique; protected $firstname; protected $surname; protected $email; public function __construct( int $id, string $firstname, string $surname, string $email ) { $this->firstname = $firstname; $this->surname = $surname; $this->email = $email; $this->setId($id); } public function getFirstname(): string { return $this->firstname; } public function getSurname(): string { return $this->surname; } public function getEmail(): string { return $this->email; } public function setEmail(string $email) { $this->email = $email; } }
We add the use
Unique;
statement to let the class know that it is using the
trait. We remove everything related to IDs, even inside the
constructor. We still get an ID as the first argument of the
constructor, but we ask the method setId
from the trait to do everything for us. Note that we refer to that
method with $this
, as if the method was
inside the class. The updated hierarchic tree would look like the
following (note that we are not adding all the methods for all the classes or interfaces that are
not involved in the recent changes in order to keep the diagram as
small and readable as possible):

Let's see how it works, even though it does so
in the way that you probably expect. Add this code into your
init.php
file, include the necessary
use
statements, and execute it in your
browser:
$basic1 = new Basic(1, "name", "surname", "email"); $basic2 = new Basic(null, "name", "surname", "email"); var_dump($basic1->getId()); // 1 var_dump($basic2->getId()); // 2
The preceding code instantiates two customers. The first of them has a specific ID, whereas the second one lets the system choose an ID for it. The result is that the second basic customer has the ID 2. That is to be expected, as both customers are basic. But what would happen if the customers are of different types?
$basic = new Basic(1, "name", "surname", "email"); $premium = new Premium(null, "name", "surname", "email"); var_dump($basic->getId()); // 1 var_dump($premium->getId()); // 2
The IDs are still the same. That is to be
expected, as the trait is included in the Person
class, so the static property $lastId
will be shared across all the instances of
the class Person
, including Basic
and Premium
customers. If you used the trait from Basic
and Premium
customer instead of Person
(but you
should not), you would have the following result:
var_dump($basic->getId()); // 1 var_dump($premium->getId()); // 1
Each class will have its own static property. All Basic
instances will share the same $lastId
, different from the $lastId
of Premium
instances. This should make clear that the static members in traits
are linked to whichever class uses them, rather than the trait
itself. That could also be reflected on testing the following code
which uses our original scenario where the trait is used from
Person
:
$basic = new Basic(1, "name", "surname", "email"); $premium = new Premium(null, "name", "surname", "email"); var_dump(Person::getLastId()); // 2 var_dump(Unique::getLastId()); // 0 var_dump(Basic::getLastId()); // 2 var_dump(Premium::getLastId()); // 2
If you have a good eye for problems, you might start thinking about some potential issues around the usage of traits. What happens if we use two traits that contain the same method? Or what happens if you use a trait that contains a method that is already implemented in that class?
Ideally, you should avoid running into these kinds of situations; they are warning lights for possible bad design. But as there will always be extraordinary cases, let's see some isolated examples on how they would behave.
The scenario where the trait and the class implement the same method is easy. The method implemented explicitly in the class is the one with more precedence, followed by the method implemented in the trait, and finally, the method inherited from the parent class. Let's see how it works. Take for example the following trait and class definitions:
<?php trait Contract { public function sign() { echo "Signing the contract."; } } class Manager { use Contract; public function sign() { echo "Signing a new player."; } }
Both implement the sign
method, which means that we have to apply the
precedence rules defined previously. The method defined in the
class takes precedence over the one from the trait, so in this
case, the executed method will be the one from the class:
$manager = new Manager(); $manager->sign(); // Signing a new player.
The most complicated scenario would be one where a class uses two traits with the same method. There are no rules that solve the conflict automatically, so you have to solve it explicitly. Check the following code:
<?php
trait Contract {
public function sign() {
echo "Signing the contract.";
}
}
trait Communicator {
public function sign() {
echo "Signing to the waitress.";
}
}
class Manager {
use Contract, Communicator;
}
$manager = new Manager();
$manager->sign();
The preceding code throws a fatal error, as both traits implement
the same method. To choose the one you want to use, you have to use
the operator insteadof
. To use it, state
the trait name and the method that you want to use, followed by
insteadof
and the trait that you are
rejecting for use. Optionally, use the keyword as
to add an alias like we did with namespaces so
that you can use both the methods:
class Manager { use Contract, Communicator { Contract::sign insteadof Communicator; Communicator::sign as makeASign; } } $manager = new Manager(); $manager->sign(); // Signing the contract. $manager->makeASign(); // Signing to the waitress.
You can see how we decided to use the method of
Contract
instead of Communicator
, but added the alias so that both
methods are available. Hopefully, you can see that even the
conflicts can be solved, and there are specific cases where there
is nothing to do but deal with them; in general, they look like a bad sign—no pun
intended.