Traits
A class has only one parent. Yet sometimes classes from different families need the same ability: a robot, a printer and a website guest, with no common parent. You don't want to copy one method into three classes, and a shared parent for them would look odd.
Back to the blueprints. Picture a ready-made module, say a balcony that a factory produces separately. It gets bolted onto houses of any blueprint family, and each house gets its own balcony.
In PHP such a module is called a trait: a set of methods you attach to a class with the word use.
Attaching a trait
You declare a trait with the word trait and attach it inside a class with use:
<?php
trait Greets
{
public function hello(): string
{
return 'hello from ' . $this::class;
}
}
class Robot
{
use Greets;
}
echo (new Robot())->hello(), "\n"; // hello from RobotLet's walk through it step by step:
trait Greetsdeclares a trait with one method,hello(). On its own, it does nothing.use Greets;insideRobotattaches the trait to the class.- PHP copies the trait's methods into the class, as if you had typed them into
Robotby hand. - That is why
$this::classinsidehello()givesRobot: the method has already become a method of the class, not of the trait.
You can write the same use Greets; in any other class, and each one gets its own hello(). This kind of reuse is called horizontal: code is shared between classes of different families instead of passing down through inheritance.
A trait may declare properties too, and they are also copied into every class. You cannot create an object of a trait, though: new Greets() gives Error: Cannot instantiate trait Greets.
Whose method wins
What if a method with the same name exists both in the class's parent and in the trait? Let's see:
class Machine
{
public function hello(): string
{
return 'beep';
}
}
class Printer extends Machine
{
use Greets;
}
echo (new Printer())->hello(), "\n"; // hello from PrinterThe trait's method turned out stronger than the parent's: Printer got hello() from Greets, and the Machine version did not run. But when a method with the same name is declared in the class itself, that one wins:
class Guest
{
use Greets;
public function hello(): string
{
return 'hi, I am a guest';
}
}
echo (new Guest())->hello(), "\n"; // hi, I am a guestThe order in which PHP picks the method:
- a method declared in the class itself;
- a method from the trait;
- a method inherited from the parent.
A class method and a trait method with the same name are not an error: PHP quietly keeps the class's method. The error comes when two traits bring methods with one name into one class. Then PHP stops the script with a fatal error at the class declaration, and you choose the winner with the word insteadof in curly braces after use.
Why traits exist when a class has a single parent is a frequent interview question. The answer: so that classes without a common parent can share ready code.
A trait is not a type
Here the balcony picture falls short. You can see a balcony bolted onto a house, but a type check does not notice a trait on an object.
The trait's methods become the class's own, but the trait itself never becomes a type. That is why $robot instanceof Greets gives false, and a trait does not work as a parameter type.
Common mistake
A trait was used as the type of a function parameter:
function welcome(Greets $g): string
{
return $g->hello();
}
echo welcome(new Robot()), "\n"; // ✗Fatal error: Uncaught TypeError: welcome(): Argument #1 ($g) must be of type Greets, Robot givenTo PHP, Greets in a parameter type is the name of a class or an interface, and a Robot object is neither: the trait is not one of its types. If you need a type, declare an interface with a hello() method and sign the class up for it: class Robot implements CanGreet { use Greets; }. The trait brings the method, and the interface gives the parameter its type.
Key points
- A trait is a set of methods and properties that
usecopies into a class. - Traits give shared code to classes without a common parent: this is horizontal reuse.
- A method of the class itself beats a trait method, and a trait method beats a parent method.
- A trait is not a type:
instanceofdoes not see it, and it does not work as a parameter type.