Writing Reusable Code using Traits in PHP

Traits are way for code reuse which makes it easier to include the same code(trait) in different locations(classes). PHP is a single inheritance language, which means a class could have only one parent class. A trait contains methods and properties like classes, but a trait cannot be instantiated on its own. Let’s have a look at what traits have to offer in PHP.

Simple Trait

A trait declaration is similar to a class declaration, but instead of the class keyword we use the keyword trait.

To include a trait inside a class we use the use keyword.

Syntax

trait traitName {
  
  // method and properties of trait.
  
}

// how to use.
class usingTrait {
  
  use traitName;
  
}

Example

<?php

// simple trait
trait Uploader {
  
  public function upload($filename) {
    echo 'Uploading file ' . $filename . PHP_EOL;
  }
  
}

class ProfilePic {
  // include the trait
  use Uploader;
  
  public function uploadProfilePic($filename) {
    echo 'Adding profile pic' . PHP_EOL;
    // this will invoke trait's upload
    $this->upload($filename);
  }
  
}

class CoverPic {
  
  use Uploader;
    
  public function uploadCoverPic($filename) {
    echo 'Adding cover pic' . PHP_EOL;
    // this will invoke trait's upload
    $this->upload($filename);
  }
  
}

$profile = new ProfilePic();
$profile->uploadProfilePic('pic.jpg');
echo '-------------------------------' . PHP_EOL;
$cover = new CoverPic();
$cover->uploadCoverPic('cover.jpg');

Output

Adding profile pic
Uploading file pic.jpg
-------------------------------
Adding cover pic
Uploading file cover.jpg

This is how a simple trait declaration and usage would look like. In this example, we use a upload method in the Uploader trait inside our classes ProfilePic and CoverPic. This example will be used as a base for the remaining of the post, so it might not appear completely in some places.

Precedence

If a class including a trait declares a method with same name as the method inside of the trait, then the class method overrides and takes precedence over the method declared inside the trait. Declaration inside the class always takes precedence over the trait declarations.

Example

// trait precedence
trait Uploader {
  
  public function upload($filename) {
    echo 'Trait - Uploading file - ' . $filename . PHP_EOL;
  }
  
}

class ProfilePic {
  
  use Uploader;
  
  // this will override the trait's upload method.
  public function upload($filename) {
    echo 'Class - uploading pic - ' . $filename . PHP_EOL;
  }
  
  public function uploadProfilePic($filename) {
    echo 'Adding profile pic' . PHP_EOL;
    // this will invoke ProfilePic's upload method.
    $this->upload($filename);
  }
  
}

//......

Output

Adding profile pic
Class - uploading pic - pic.jpg
-------------------------------
Adding cover pic
Trait - Uploading file - cover.jpg

In the above example the ProfilePic upload takes precedence over the upload method declared in the trait.

Multiple Traits

It’s possible to include multiple traits inside a class using the use keyword and specifying the traits separated by comma.

Example

// trait multiple
trait Uploader {
  
  public function upload($filename) {
    echo 'Trait - Uploading file - ' . $filename . PHP_EOL;
  }
  
}

// let's say we have to resize the image.
trait Resize {
  
  public function resize($filename) {
    echo 'Trait - Resizing image with filename - ' . $filename . PHP_EOL;
  }
  
}

class ProfilePic {
  // include both the traits
  use Uploader, Resize;
  
  // this will override the trait's upload method.
  public function upload($filename) {
    echo 'Class - uploading pic - ' . $filename . PHP_EOL;
  }
  
  public function uploadProfilePic($filename) {
    echo 'Adding profile pic' . PHP_EOL;
    $this->upload($filename);
    // resize trait method
    $this->resize($filename);
  }
  
}

//......

Output

Adding profile pic
Class - uploading pic - pic.jpg
Trait - Resizing image with filename - pic.jpg
-------------------------------
Adding cover pic
Trait - Uploading file - cover.jpg
Trait - Resizing image with filename - cover.jpg

In this example a new trait Resize is introduced, which is included in both the ProfilePic and CoverPic class.

Conflict Resolution

When multiple traits included in a class has a method with same name a conflict occurs. To resolve the conflict it is possible to specify which method of a trait to use over the other. It can done using the insteadof keyword.

It is also possible to create alias for a trait method using the as keyword.

Example

// trait conflict resolution
trait Uploader {
  
  public function upload($filename) {
    echo 'Trait - Uploading file - ' . $filename . PHP_EOL;
  }
  
  // get's info on the uploaded file
  public function getFileInfo() {
    echo 'Uploaded image info' . PHP_EOL;
  }
  
}

trait Resize {
  
  public function resize($filename) {
    echo 'Trait - Resizing image with filename - ' . $filename . PHP_EOL;
  }
  
  // get's info on the resized file
  public function getFileInfo() {
    echo 'Resized image info' . PHP_EOL;
  }
}

class ProfilePic {
  
  use Uploader, Resize {
    // tell php to use getFileInfo of Uploader instead of Resize's getFileInfo
    Uploader::getFileInfo insteadof Resize;
    // tell php to use getFileInfo of Resize as getResizedFileInfo (alias)
    Resize::getFileInfo as getResizedFileInfo;
  }
  
  // this will override the trait's upload method.
  public function upload($filename) {
    echo 'Class - uploading pic - ' . $filename . PHP_EOL;
  }
  
  public function uploadProfilePic($filename) {
    echo 'Adding profile pic' . PHP_EOL;
    // get info of uploaded file - using Uploader getFileInfo method.
    $this->getFileInfo();
    $this->upload($filename);
    $this->resize($filename);
    // get info of resized file - uses Resize getResizedFileInfo aka getFileInfo
    $this->getResizedFileInfo();
  }
  
}

//......

Output

Adding profile pic
Uploaded image info
Class - uploading pic - pic.jpg
Trait - Resizing image with filename - pic.jpg
Resized image info
-------------------------------
Adding cover pic
Uploaded image info
Trait - Uploading file - cover.jpg
Trait - Resizing image with filename - cover.jpg
Resized image info

As shown, we tell the classes to use Uploader getFileInfo method instead of the one present in Resize class. It also shows how an alias is created for the Resize traits getFileInfo as getResizedFileInfo.

Visibility

Since the methods inside the traits are declared public it is possible to invoke that method using the instance of that class. To restrict that, the visibility of the method being used can be changed while include it.

Example

// trait visibility
trait Uploader {
  
  public function upload($filename) {
    echo 'Trait - Uploading file - ' . $filename . PHP_EOL;
  }
  
  public function getFileInfo() {
    echo 'Uploaded image info' . PHP_EOL;
  }
  
}

trait Resize {
  
  public function resize($filename) {
    echo 'Trait - Resizing image with filename - ' . $filename . PHP_EOL;
  }
  
  public function getFileInfo() {
    echo 'Resized image info' . PHP_EOL;
  }
}

class ProfilePic {

  //.......
  //.......
  
}

class CoverPic {
  
  /**
   * Since upload method is declared public inside the trait, it can be
   * accessed by the instance outside of the class. We can fix that by
   * changing the visibility of the method.
   */
  use Uploader, Resize {
    // change the visibilty of "upload" method to protected.
    upload as protected;
    Uploader::getFileInfo insteadof Resize;
    // make the alias created from getFileInfo a private method.
    Resize::getFileInfo as private getResizedFileInfo;
  }
    
  public function uploadCoverPic($filename) {
    echo 'Adding cover pic' . PHP_EOL;
    $this->getFileInfo();
    $this->upload($filename);
    $this->resize($filename);
    $this->getResizedFileInfo();
  }
  
}

//...

Output

Adding profile pic
Uploaded image info
Class - uploading pic - pic.jpg
Trait - Resizing image with filename - pic.jpg
Resized image info
-------------------------------
Adding cover pic
Trait - Uploading file - cover.jpg
Trait - Resizing image with filename - cover.jpg

Error Ouput

Adding profile pic
Uploaded image info
Class - uploading pic - pic.jpg
Trait - Resizing image with filename - pic.jpg
Resized image info
-------------------------------
Fatal error:  Call to protected method CoverPic::upload() from context '' in trait-visibility.php on line 71

The example shows how the upload method is changed to protected and how the getResizedFileInfo alias method is made private.

Trait from Traits

One of the coolest thing about traits is that it can be made from including other traits. Imagine the possibilities of code reuse because of this feature.

Example

// trait from traits
trait Uploader {
  
  public function upload($filename) {
    echo 'Trait - Uploading file - ' . $filename . PHP_EOL;
  }
  
  public function getFileInfo() {
    echo 'Uploaded image info' . PHP_EOL;
  }
  
}

trait Resize {
  
  public function resize($filename) {
    echo 'Trait - Resizing image with filename - ' . $filename . PHP_EOL;
  }
  
  public function getFileInfo() {
    echo 'Resized image info' . PHP_EOL;
  }
}

trait FileHandler {
  // Use both the traits declared before
  use Uploader, Resize {
    upload as protected;
    Uploader::getFileInfo insteadof Resize;
    Resize::getFileInfo as private getResizedFileInfo;
  }
  
}

class ProfilePic {
  // use the combined trait
  use FileHandler;
  
  // this will override the trait's upload method.
  public function upload($filename) {
    echo 'Class - uploading pic - ' . $filename . PHP_EOL;
  }
  
  public function uploadProfilePic($filename) {
    echo 'Adding profile pic' . PHP_EOL;
    $this->getFileInfo();
    $this->upload($filename);
    $this->resize($filename);
    $this->getResizedFileInfo();
  }
  
}

//.....

Output

Adding profile pic
Uploaded image info
Class - uploading pic - pic.jpg
Trait - Resizing image with filename - pic.jpg
Resized image info
-------------------------------
Adding cover pic
Uploaded image info
Trait - Uploading file - cover.jpg
Trait - Resizing image with filename - cover.jpg
Resized image info

Here a new FileHandler trait is introduced which is made from including the Uploader and Resize trait. Which is inturn used in the classes.

Abstract Methods

Traits like classes can also impose the classes using it to define a method by creating abstract methods.

Example

// ..... .... 
trait FileHandler {
  
  use Uploader, Resize {
    upload as protected;
    Uploader::getFileInfo insteadof Resize;
    Resize::getFileInfo as private getResizedFileInfo;
  }
  
  // Save the file info in db
  abstract function storeFileInfo($filename);
  
}

class ProfilePic {
  
  use FileHandler;
  
  // this will override the trait's upload method.
  public function upload($filename) {
    echo 'Class - uploading pic - ' . $filename . PHP_EOL;
  }
  
  // must be declared since the trait has declared it as abstract.
  public function storeFileInfo($filename) {
    echo 'Storing profile pic - "' . $filename . '" in db' . PHP_EOL;
  }
  
  public function uploadProfilePic($filename) {
    echo 'Adding profile pic' . PHP_EOL;
    $this->getFileInfo();
    $this->upload($filename);
    $this->resize($filename);
    $this->getResizedFileInfo();
    $this->storeFileInfo($filename);
  }
  
}

//....

Output

Adding profile pic
Uploaded image info
Class - uploading pic - pic.jpg
Trait - Resizing image with filename - pic.jpg
Resized image info
Storing profile pic - "pic.jpg" in db
-------------------------------
Adding cover pic
Uploaded image info
Trait - Uploading file - cover.jpg
Trait - Resizing image with filename - cover.jpg
Resized image info
Storing cover photo - "cover.jpg" in db

As shown in the example, the FileHandler trait has an storeFileInfo abstract method, which must be defined inside the classes that are including this trait.

Properties, Methods also Static

Traits can also declare properties inside them, but if the same property exists in the class using it, that could be a problem. There can also be static properties and methods.

Example

// ... ... ..

trait FileHandler {
  
  // trait's can declare properties and it can be static. 
  public static $imageLocation = '/path/to/image';
  
  use Uploader, Resize {
    upload as protected;
    Uploader::getFileInfo insteadof Resize;
    Resize::getFileInfo as private getResizedFileInfo;
  }
  
  // Save the file info in db
  abstract function storeFileInfo($filename);
  
  // trait's can also declare static methods.
  public static function displayImageLink($filename) {
    echo 'Link to image file "' . $filename . '" - ' . static::$imageLocation .
            $filename . PHP_EOL;
  }
}

class ProfilePic {
  
  use FileHandler;
  
  // this will override the trait's upload method.
  public function upload($filename) {
    echo 'Class - uploading pic - ' . $filename . PHP_EOL;
  }
  
  public function storeFileInfo($filename) {
    echo 'Storing profile pic - "' . $filename . '" in db' . PHP_EOL;
  }
  
  public function uploadProfilePic($filename) {
    echo 'Adding profile pic' . PHP_EOL;
    $this->getFileInfo();
    $this->upload($filename);
    $this->resize($filename);
    $this->getResizedFileInfo();
    $this->storeFileInfo($filename);
    // call to static method.
    static::displayImageLink($filename);
  }
  
}

// ... ... ... ..

Output

Adding profile pic
Uploaded image info
Class - uploading pic - pic.jpg
Trait - Resizing image with filename - pic.jpg
Resized image info
Storing profile pic - "pic.jpg" in db
Link to image file "pic.jpg" - /path/to/imagepic.jpg
-------------------------------
Adding cover pic
Uploaded image info
Trait - Uploading file - cover.jpg
Trait - Resizing image with filename - cover.jpg
Resized image info
Storing cover photo - "cover.jpg" in db
Link to image file "cover.jpg" - /path/to/imagecover.jpg


Take a look at the the official Traits documentation.