Writing Reusable Code using Traits in PHP
Reading Time:
Reading Time:
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.
trait traitName {
// method and properties of trait.
}
// how to use.
class usingTrait {
use traitName;
}
<?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');
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.
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.
// 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);
}
}
//......
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.
It’s possible to include multiple traits inside a class using the use keyword and specifying the traits separated by comma.
// 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);
}
}
//......
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.
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.
// 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();
}
}
//......
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
.
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.
// 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();
}
}
//...
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
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.
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.
// 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();
}
}
//.....
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.
Traits like classes can also impose the classes using it to define a method by creating abstract methods.
// ..... ....
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);
}
}
//....
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.
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.
// ... ... ..
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);
}
}
// ... ... ... ..
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.