Zend PHP 7 Certification – OOP – Traits

This post covers the Traits section of the OOP chapter when studying for the Zend PHP 7 Certification.

Traits, introduced in PHP 5.4.0, are a mechanism for code reuse and are intended to reduce some limitations of single inheritance by enabling a developer to reuse sets of methods freely in several independent classes living in different class hierarchies.

A Trait is similar to a class, but only intended to group functionality and as such, Traits cannot be instantiated on its own like a class can.

Traits are defined using the trait keyword.

<?php
trait T {
    protected function foo() {}
}

$trait = new T(); // Fatal error. Cannot be instantiated

To include Traits within classes, use the use keyword within the class declararion followed by the name of the Trait.

<?php
trait Hello
{
    public function sayHello()
    {
        echo "Hello!";
    }
}

class SomeClass
{
    use Hello;
}

$class = new SomeClass();
$class->sayHello();

An inherited member from a base class is overridden by a member inserted by a Trait. The precedence order is that methods from the current class override Trait methods, which in turn override methods from the base class.

This can be demonstrated in the two examples below.

<?php
class Base {
    public function sayHello() {
        echo 'Hello ';
    }
}

trait SayWorld {
    public function sayHello() {
        parent::sayHello();
        echo 'World!';
    }
}

class Child extends Base {
    use SayWorld;
}

$o = new Child();
$o->sayHello();

// Outputs:
Hello World!
<?php
trait HelloWorld {
    public function sayHello() {
        echo 'Hello World!';
    }
}

class TheWorldIsNotEnough {
    use HelloWorld;
    public function sayHello() {
        echo 'Hello Universe!';
    }
}

$o = new TheWorldIsNotEnough();
$o->sayHello();

// Outputs:
Hello Universe!

An example of Trait usage might include a logger class.

<?php
interface Logger
{
    public function log($message, $level);    
}

class DemoLogger implements Logger
{
    public function log($message, $level)
    {
        echo "Logged message: $message with level $level", PHP_EOL; 
    }
}

trait Loggable // implements Logger
{
    protected $logger;
    public function setLogger(Logger $logger)
    {
        $this->logger = $logger;
    }
    public function log($message, $level)
    {
        $this->logger->log($message, $level);
    }
}

class Foo implements Logger
{
    use Loggable;
}

$foo = new Foo;
$foo->setLogger(new DemoLogger);
$foo->log('It works', 1);

// Outputs:
Logged message: It works with level 1

The important thing to consider when using Traits is that they really are just pieces of code that get copied into the class, and therefore are needed if you have a situation where code from one class needs to be copied into another.

A couple of potential conflicts can arise when using Traits, one of which is when trying to change the visibility of a method within the Trait that exists in the class using the Trait.

<?php
trait T {
    protected function foo() {}
}
class A { 
    public function foo() {}
}
class B extends A
{
    use T;
}

// Outputs:
Fatal error: Access level to T::foo() must be public (as in class A)

The other potential conflict is when defining a property within a Trait. If the Trait’s property has a different visibility and value compared to the original class, a fatal error will occur.

trait T {
    public $foo = 'baz';
}

class A {
    public $foo = 'bar';
}

class B extends A
{
    use T; // Fatal error: A and T define the same property ($foo) in the composition of B. However, the definition differs and is considered incompatible.
}

In PHP 7.0, if the property has the same visibility and value as the original class, it is allowed whereas prior to this PHP version, an E_STRICT notice would be raised.

trait T {
    public $foo = 'bar';
}

class A {
    public $foo = 'bar';
}

class B extends A
{
    use T; // Allowed
}

View the other sections:

Note: This article is based on PHP version 7.0.