PHP Classes
elePHPant
Icontem

How to Make Better Reuse of PHP Code using Traits Part 2: Advanced Traits Usage Explained

Recommend this page to a friend!
  Blog PHP Classes blog   RSS 1.0 feed RSS 2.0 feed   Blog How to Make Better Re...   Post a comment Post a comment   See comments See comments (1)   Trackbacks (0)  

Author: Dave Smith

Posted on:

Categories: PHP Tutorials

In the first part of this article we learned how to use PHP traits to organize better functionality to be reused by multiple unrelated classes.

Read this article to learn more about advanced features of traits like solving conflicts when using traits that define the same properties, changing the visibility of a trait method in the class that uses it, reusing traits within traits, examples of packages that provide general purpose traits for use by many other packages.




Contents

Conflict Resolution

Changing Method Visibility

Traits using Traits

Back to the Basics

Examples of Packages that Provide General Purpose Traits


Introduction

In the first part of this article we learned that since PHP 5.4 developers can use traits to define code functionality that can be reused by many distinct classes even when their core functionality is very different.

In this part we are going to cover more advanced topics that deal with aspects you may encounter.

Conflict Resolution

We ended the first part with a simple example of ambiguity based on property naming.

We now know that ambiguity with properties creates problems, but what if two traits being used happen to have the same methods implemented? In order to avoid the script failing with a fatal error, the class that uses the traits must resolve the conflict.

trait screwDrivers {

public $driverType;

public function returnTool() {

return $this->driverType;

}

public function slottedDriver() {

$this->driverType = 'slotted';

}

public function phillipsDriver() {

$this->driverType = 'phillips';

}

public function starDriver() {

$this->driverType = 'star';

}

}

trait wrenches {

public $wrenchType;

public function returnTool() {

return $this->wrenchType;

}

public function openWrench() {

$this->wrenchType = 'open';

}

public function boxWrench() {

$this->wrenchType = 'box';

}

public function allenWrench() {

$this->wrenchType = 'allen';

}

}

class myTools {

use screwDrivers, wrenches {

screwDrivers::returnTool insteadof wrenches;
wrenches::returnTool as returnWrench;

}

}

In the above example, both traits have implemented a returnTool method which creates ambiguity. To resolve this conflict, myTools uses the insteadof operator to explicitly define which method the class will use.

In this case I have said to use the screwDrivers returnTool method instead of the one in wrenches. This also means that we no longer have a way to access the wrenches returnTool method, so we can rename it with an alias using the as operator. As you can see I have said that the wrenches returnTool method can be accessed as returnWrench.

Here is how it would be used in the code:

$tools = new myTools();
$tools->slottedDriver();
$tools->allenWrench();
echo $tools->returnTool().'<br>';
echo $tools->returnWrench();

Note that the returnTool method is using the method implemented in the screwDrivers trait and the returnWrench method is using the returnTool method from the wrenches trait.

Changing Method Visibility

The as property can also be used to change the visibility of a method.

class myTools {

use screwDrivers, wrenches {

screwDrivers::returnTool insteadof wrenches;
wrenches::returnTool as returnWrench;

openWrench as protected;
boxWrench as protected protectBoxWrench;

}

}

In the above example, the openWrench method is now protected, so it can only be accessed within the class. The boxWrench method has an alias defined as protectBoxWrench, boxWrench still has its original visibility, public in this case, where protectBoxWrench can only be used in the class since it is protected.

Traits using Traits

Just like a class, traits can also use traits, however they still need a class to instantiate them.

trait commonTools {

use screwDrivers, wrenches {

screwDrivers::returnTool insteadof wrenches;
wrenches::returnTool as returnWrench;

openWrench as protected;
boxWrench as protected protectedBoxWrench;

}

}
class myTools {

use commonTools;

}

To conserve space, the above example does not include the screwDrivers or wrenches traits used in the other examples, so just assume they have already been defined.

The trait commonTools defines which other traits to use and handles the conflict resolution so myTools just has to use commonTools to access the methods.

It is important to note that conflict resolution has to be handled where a trait is being used, so in this case the commonTools trait is using the screwDrivers and wrenches traits, which means that any conflicts must be resolved here, in commonTools.

Back to the Basics

So far we have systematically worked our way up to some advanced topics on using traits. While it is important to understand how to resolve conflicts and use special operators, it is also important not to get lost in the advanced trait usage.

As the introduction stated, traits are primarily developed to be used by multiple classes in a single inheritance language like PHP. The set of tools does not need to be over complicated, it can be a set of methods which are common to unrelated classes in a development project.

trait commonMethods {

public function cleanUpString($string){

$string = trim($string);

return $string;

}

public function createStringArray($string){

$stringArray = str_split($string);

return $stringArray;

}

}

class firstClass {

use commonMethods;

}

class secondClass {

use commonMethods;

}

The above example has 2 unrelated classes using the same trait so that each class now has access to the commonMethods. The code could look something like this.

$first = new firstClass();
$second = new secondClass();
$cleanString = $first->cleanUpString(' I need to be cleaned up ');
$stringArray = $second->createStringArray('Hello');

Here we have instantiated each class as their own object and each class can access the methods defined in the commonMethods trait.

A class can also define its own implementation of a method defined in a trait.

class secondClass {

use commonMethods;

public function createStringArray($string){

$stringArray = str_split($string,2);

return $stringArray;

}

}

Based on the secondClass implementing its own method which splits the string into an array containing 2 characters per key, refer to the following code.

$first = new firstClass();
$second = new secondClass();
$stringArray1 = $first->createStringArray('Hello');
$stringArray2 = $second->createStringArray('Hello');

$stringArray1 will be an array where each key contains one character from the string and $stringArray2 will be an array where each key contains two characters from the string since it is using the method implemented in the class which has precedence over the method implemented in the trait.

Traits can also provide abstract methods which the using class must provide the concrete implementation.

trait commonMethods {

abstract public function printString($string);

public function cleanUpString($string) {

$string = trim($string);

return $string;

}

public function createStringArray($string) {

$stringArray = str_split($string);

return $stringArray;

}

}

class firstClass {

use commonMethods;

public function printString($string) {

echo $string;

}

}

class secondClass {

use commonMethods;

public function printString( $string ) {

$string = $this->cleanUpString($string);
echo $string;

}

public function createStringArray( $string ) {

$stringArray = str_split($string,2);

return $stringArray;

}

}

As we can see here, the trait is requiring the classes which use it to implement the printString method. The firstClass will output the string as supplied while the secondClass will clean up the string and then output it.

Traits can also implement static methods

trait commonMethods {

abstract public function printString($string);

static public function cleanUpString($string){

$string = trim($string);

return $string;

}

public function createStringArray($string){

$stringArray = str_split($string);

return $stringArray;

}

}

class firstClass {

use commonMethods;

public function printString($string){

echo $string;

}

}

Since we made the method cleanUpString static, it can be accessed without instantiating a class.

$cleanString = firstClass::cleanUpString(' Clean me up  ');

Examples of Packages that Provide General Purpose Traits

In the PHP Classes site there are already several nice examples of packages that provide traits which can be reused by other packages and reduce their development effort. Here are some examples of trait packages:

Conclusion

Traits are well worth the time it takes to understand them and implementing into your own projects. Single inheritance languages like PHP have previously had a limitation on code reuse. This limitation no longer exists with the inclusion of Traits.

Once you fully understand the power that traits put in your hands, you may start organizing better your packages that reuse code that you previously have written for different purposes.

If you liked this article, or you have a question, post a comment here.




You need to be a registered user or login to post a comment

1,409,710 PHP developers registered to the PHP Classes site.
Be One of Us!

Login Immediately with your account on:

FacebookGmail
HotmailStackOverflow
GitHubYahoo


Comments:

1. this is going to be useful - David Mintz (2015-08-19 01:35)
there have been times I have needed something just like this... - 0 replies
Read the whole comment and replies



  Blog PHP Classes blog   RSS 1.0 feed RSS 2.0 feed   Blog How to Make Better Re...   Post a comment Post a comment   See comments See comments (1)   Trackbacks (0)