Traits Versus Inheritance In PHP

The other day I was conducting a code review and found that a developer had used a trait to give two classes the same group of utility methods. Whilst there was nothing wrong with this in terms of functionality, I asked the developer why they had chosen to use traits instead of inheritance. We eventually decided that an inheritance model would be better suited to the situation but I thought I would go through some of the thought processes here.

What Is A Trait?

A trait, if you weren't aware, is like a class, but you don't instantiate it directly. Traits are defined using the trait keyword and are otherwise quite like a class in structure.

The idea is that code is essentially copied into the class you want to use it in from the trait and the class acts like it had that code all along. For example, let's take a simple trait.

trait Hello {
  protected $name = 'Bob';

  public function sayHello() {
    echo 'Hello ' . $this->name;
  }
}

To use this trait in the class you include it with the use keyword, but within the structure of the class.

class Welcome {
  use Hello;
}

Now, we can call the sayHello() method from the Hello trait as if it was a part of the Welcome class.

$welcome = new Welcome();
$welcome->sayHello(); // Prints 'Hello Bob'.

What Is PHP Class Inheritance?

Whilst I am talking about how traits are used, it makes sense to (very briefly) cover class inheritance. In PHP you can represent an object using a class. For example, let's say that we want to represent a square. We need to store the height and the width, have some way of adding these properties and also have a way of getting the area.

class Square
{
  public $width;
  public $height;

  public function __construct($width, $height)
  {
    $this->width = $width;
    $this->height = $height;
  }

  public function getArea()
  {
    return $this->width * $this->height;
  }
}

If we wanted to represent a triangle then we could either create another class that is not connected to the Square at all, or we could create an abstract Shape class and inherit from what we need from that. A triangle has the same basic properties of our shape (width and height) so it makes sense to go down the inheritance route.

abstract class Shape
{
  public $width;
  public $height;

  public function __construct($width, $height)
  {
    $this->width = $width;
    $this->height = $height;
  }
}

By inheriting from this class we can represent the square and the triangle without having to copy a bunch of code. We do this using the extends keyword.

class Square extends Shape
{
  public function getArea()
  {
    return $this->width * $this->height;
  }
}

class Triangle extends Shape
{
  public function getArea()
  {
    return ($this->width * $this->height) / 2;
  }
}

How Are Traits Used?

Traits are used quite commonly in the PHP world. They mainly wrap things like loggers and utility methods and are used to inject this functionality without having to rely on complex inheritance patterns.

For example, in the Drupal JSON API module there is a trait called ResourceIdentifierTrait. This is used by the module to add common methods like getId(), getTypeName(), and getResourceType() to any class that uses it. This is used within the module by the EntityAccessDeniedHttpException exception class, which already inherits the CacheableAccessDeniedHttpException class. This means that we can allow the EntityAccessDeniedHttpException class to know about that context that it's called in without having to alter the CacheableAccessDeniedHttpException class to know about that ID of the resource being denied, which won't always exist elsewhere in Drupal.

To my mind, traits are used to solve the fact that PHP doesn't support multiple inheritance. Thinking back to the Shape inheritance example, we might also want to represent the colour of the shape. Since colours can be stored in a variety of different ways we don't want to inject this into the shape object as that would pollute what the Shape class is representing as this would violate SOLID principles. We also can't create a Square class that inherits Shape and Colour at the same time as this isn't allowed in PHP.

The classic solution to multiple inheritance was to create a sub-inheritance class. For example, to allow all shapes to use Colours, we would extend Shape into a ShapeColour class and then extend that to a Square class. Although this works, it does create lots of strange relationships between objects.

The solution to this is to create a Colour trait.

trait ColourTrait
{
  protected $colour;

  public function setColour($colour)
  {
    $this->colour = $colour;
  }

  public function getColour()
  {
    return $this->colour;
  }
}

We can then use this in the Shape class to allow all Shapes to have a Colour.

abstract class Shape
{
  use ColourTrait;

  public $width;
  public $height;

  public function __construct($width, $height)
  {
    $this->width = $width;
    $this->height = $height;
  }
}

Now, when we create a Square, we can also set the Colour at the same time.

$square = new Square(10, 10);
$square->setColour('red');
echo $square->getColour();

 

Comments

ColourTrait  or ColorTrait? please correct.

Permalink

Thanks for reading! I've corrected that now.

Name
Philip Norton
Permalink

Add new comment

The content of this field is kept private and will not be shown publicly.
CAPTCHA
1 + 17 =
Solve this simple math problem and enter the result. E.g. for 1+3, enter 4.
This question is for testing whether or not you are a human visitor and to prevent automated spam submissions.