Skip to content
Theory · Inheritance and interfaces · lesson 5 of 8

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
<?php

trait Greets
{
    public function hello(): string
    {
        return 'hello from ' . $this::class;
    }
}

class Robot
{
    use Greets;
}
echo (new Robot())->hello(), "\n"; // hello from Robot

Let's walk through it step by step:

  1. trait Greets declares a trait with one method, hello(). On its own, it does nothing.
  2. use Greets; inside Robot attaches the trait to the class.
  3. PHP copies the trait's methods into the class, as if you had typed them into Robot by hand.
  4. That is why $this::class inside hello() gives Robot: 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:

PHP
class Machine
{
    public function hello(): string
    {
        return 'beep';
    }
}

class Printer extends Machine
{
    use Greets;
}

echo (new Printer())->hello(), "\n"; // hello from Printer

The 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:

PHP
class Guest
{
    use Greets;

    public function hello(): string
    {
        return 'hi, I am a guest';
    }
}

echo (new Guest())->hello(), "\n"; // hi, I am a guest

The order in which PHP picks the method:

  1. a method declared in the class itself;
  2. a method from the trait;
  3. 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:

PHP
function welcome(Greets $g): string
{
    return $g->hello();
}

echo welcome(new Robot()), "\n"; // ✗
TEXT
Fatal error: Uncaught TypeError: welcome(): Argument #1 ($g) must be of type Greets, Robot given

To 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 use copies 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: instanceof does not see it, and it does not work as a parameter type.