As we have seen, interfaces help you manage the fact that, like Java, PHP does not support multiple inheritance. In other words, a class in PHP can only extend a single parent. However, you can make a class promise to implement as many interfaces as you like; for each interface it implements, the class takes on the corresponding type.So interfaces provide types without implementation. But what if you want to share an implementation across inheritance hierarchies? PHP 5.4 introduced traits, and these let you do just that.

A trait is a class-like structure that cannot itself be instantiated but can be incorporated into classes. Any methods defined in a trait become available as part of any class that uses it. A trait changes the structure of a class, but doesn’t change its type. Think of traits as includes for classes.Let’s look at why a trait might be useful.

A Problem for Traits to Solve

Here is a version of the ShopProduct class with a calculateTax() method:

<?php
class ShopProduct
{    
    private $taxrate = 17;

public function calculateTax(float $price): float    
    {        
        return (($this->taxrate / 100) * $price);    
    }
}
<?php
$p = new ShopProduct("Fine Soap", "", "Bob's Bathroom", 1.33);
print $p->calculateTax(100) . "\n";

The calculateTax() method accepts a $price argument and calculates a sales tax amount based on the private $taxrate property. Of course, a subclass gains access to calculateTax(). But what about entirely different class hierarchies? Imagine a class named UtilityService, which inherits from another class, Service. If UtilityService needs to use an identical routine, I might find myself duplicating calculateTax() in its entirety:

<?php
abstract class Service
{    
    // service oriented stuff
}
<?php
class UtilityService extends Service
{    
    private $taxrate = 17;    
    function calculateTax(float $price): float    
    {        
        return ( ( $this->taxrate/100 ) * $price );    
    }
}

$u = new UtilityService();
print $u->calculateTax(100)."\n";
<?php
abstract class Service
{    
    // service oriented stuff
}

class UtilityService extends Service
{    
    private $taxrate = 17;
    function calculateTax(float $price): float    
    {        
        return ( ( $this->taxrate/100 ) * $price );    
    }
}

$u = new UtilityService();
print $u->calculateTax(100)."\n";

Defining and Using a Trait

One of the core object-oriented design goals is the removal of duplication. One solution to this kind of duplication is to factor it out into a reusable strategy class. Traits provide another approach less elegant, perhaps, but certainly effective. Here I declare a single trait that defines a calculateTax() method, and then I include it in both ShopProduct and UtilityService

<?php

trait PriceUtilities
{    
    private $taxrate = 17;
    
    public function calculateTax(float $price): float    
    {        
        return (($this->taxrate / 100) * $price);    
    }
}
<?php
class ShopProduct
{    
    use PriceUtilities;
}
<?php
abstract class Service
{    
    // service oriented stuff
}
<?php
class UtilityService extends Service
{    
    use PriceUtilities;
}
<?php
$p = new ShopProduct();
print $p->calculateTax(100) . "\n";
$u = new UtilityService();
print $u->calculateTax(100) . "\n";

I declare the PriceUtilities trait with the trait keyword. The body of a trait looks very similar to that of a class. It is simply a set of methods and properties collected within braces. Once I have declared it, I can access the PriceUtilities trait from within my classes. I do this with the use keyword followed by the name of the trait I wish to incorporate. So having declared and implemented the calculateTax() method in a single place, I go ahead and incorporate it into both the ShopProduct and the UtilityService classes.

Using More than One Trait

You can include multiple traits in a class by listing each one after the use keyword, separated by commas. In this example, I define and apply a new trait, IdentityTrait, keeping my original PriceUtilities trait:

<?php
// listing 04.17trait 
IdentityTrait
{    
    public function generateId(): string    
    {        
        return uniqid();    
    }
}
<?php
// listing 04.18
class ShopProduct{    
    use PriceUtilities, IdentityTrait;
}
<?php
$p = new ShopProduct();
print $p->calculateTax(100) . "\n";
print $p->generateId() . "\n";

By applying both PriceUtilities and IdentityTrait with the use keyword, I make the calculateTax() and the generateId() methods available to the ShopProduct class. This means the class offers both the calculateTax() and generateId() methods.

Combining Traits and Interfaces

Although traits are useful, they don’t change the type of the class to which they are applied. So when you apply the IdentityTrait trait to multiple classes, they won’t share a type that could be hinted for in a method signature.Luckily, traits play well with interfaces. I can define an interface that requires a generateId() method, and then declare that ShopProduct implements it:

<?php
interface IdentityObject
{    
    public function generateId(): string;
}
<?php
trait IdentityTrait
{    
    public function generateId(): string    
    {        
        return uniqid();    
    }
}
<?php
class ShopProduct implements IdentityObject
{ 
    use PriceUtilities, IdentityTrait;
}

As before, ShopProduct uses the IdentityTrait trait. However, the method this imports, generateId() now also fulfills a commitment to the IdentityObject interface. This means that we can pass ShopProduct objects to methods and functions that use type hinting to demand IdentityObject instances, like this:

<?php
public static function storeIdentityObject(IdentityObject $idobj)    
{        
    // do something with the IdentityObject    
}
<?php
$p = new ShopProduct();
self::storeIdentityObject($p);
print $p->calculateTax(100) . "\n";
print $p->generateId() . "\n";

Managing Method Name Conflicts with insteadof

The ability to combine traits is a nice feature, but sooner or later conflicts are inevitable. Consider what would happen, for example, if I were to use two traits that provide a calculateTax() method:

<?php
trait TaxTools
{    
    function calculateTax(float $price): float    
    {        
        return 222;    
    }
}
<?php
trait PriceUtilities
{    
    private $taxrate = 17;    
    public function calculateTax(float $price): float    
    {
    return (($this->taxrate / 100) * $price);    
    }    
    // other utilities
}
<?php
class UtilityService extends Service
{    
    use PriceUtilities, TaxTools;
}
<?php
$u = new UtilityService();
print $u->calculateTax(100) . "\n";

Because I have included two traits that contain calculateTax() methods, PHP is unable to work out which should override the other. The result is a fatal error:


PHP Fatal error: Trait method calculateTax has not been applied, because thereare collisions with other trait methods on...

<?php
class UtilityService extends Service
{    
    use PriceUtilities, TaxTools {        
        TaxTools::calculateTax insteadof PriceUtilities;    
    }
}
<?php
$u = new UtilityService();
print $u->calculateTax(100) . "\n";

In order to apply further directives to a use statement, I must first add a body. I do this with opening and closing braces. Within this block, I use the insteadof operator. This requires a fully qualified method reference (i.e., one that identifies both the trait and the method names, separated by a scope resolution operator) on the left-hand side. On the right-hand side, insteadof requires the name of the trait whose equivalent method should be overridden:

<?php
TaxTools::calculateTax insteadof PriceUtilities;

222

The preceding snippet means, “Use the calculateTax() method of TaxTools instead of the method of the same name in PriceUtilities.” So when I run this code, I get the dummy output I planted in TaxTools::calculateTax():

Aliasing overridden trait methods

What do you do, though, if you want to then access the overridden method? The as operator allows you to alias trait methods. Once again, the as operator requires a full reference to a method on its left-hand side. On the right-hand side of the operator, you should put the name of the alias. So here, for example, I reinstate the calculateTax() method of the PriceUtilities trait using the new name basicTax()

<?php
class UtilityService extends Service
{
    use PriceUtilities, TaxTools {
        TaxTools::calculateTax insteadof PriceUtilities;        PriceUtilities::calculateTax as basicTax;    
    }
}
<?php
$u = new UtilityService();
print $u->calculateTax(100) . "\n";
print $u->basicTax(100) . "\n";

222 17

So PriceUtilities::calculateTax() has been resurrected as part of the UtilityService class under the name basicTax().

Note Where a method name clashes between traits, it is not enough to alias one of the method names in the use block. You must first determine which method supersedes the other using the insteadof operator. then you can reassign the discarded method a new name with the as operator.

Incidentally, you can also use method name aliasing where there is no name clash. You might, for example, want to use a trait method to implement an abstract method signature declared in a parent class or in an interface.

Using static methods in traits

Most of the examples you have seen so far could use static methods because they do not store instance data. There’s nothing complicated about placing a static method in a trait. Here I change the PriceUtilities::$taxrate property and the PriceUtilities::calculateTax() methods so that they are static:

<?php
trait PriceUtilities
{
    private static $taxrate = 17;
    public static function calculateTax(float $price): float    
    {        
        return ((self::$taxrate / 100) * $price);    
    }    
    // other utilities  
}
<?php
class UtilityService extends Service
{    
    use PriceUtilities;
}
$u = new UtilityService();
print $u::calculateTax(100) . "\n";

So, static methods are declared in traits and accessed via the host class in the normal way.

Changing Access Rights to Trait Methods

You can, of course, declare a trait method public, private, or protected. However, you can also change this access from within the class that uses the trait. You have already seen that the as operator can be used to alias a method name. If you use an access modifier on the right-hand side of this operator, it will change the method’s access level rather than its name.Imagine, for example, you would like to use calculateTax() from within UtilityService, but not make it available to implementing code. Here’s how you would change the use statement:

<?php
trait PriceUtilities
{    
    public function calculateTax(float $price): float    
    {        
        return (($this->getTaxRate() / 100) * $price);    
    }    
    public abstract function getTaxRate(): float;    
    // other utilities
}
<?php
class UtilityService extends Service
{
    use PriceUtilities {        
        PriceUtilities::calculateTax as private;    
    }

    private $price;    

    
    public function __construct(float $price)    {        
        $this->price = $price;    
    }    

    public function getTaxRate(): float    
    {        
        return 17;    
    } 

    public function getFinalPrice(): float    
    {        
        return ($this->price + $this->calculateTax($this->price));    
    }
}
<?php
$u = new UtilityService(100);
print $u->getFinalPrice() . "\n";

I deploy the as operator in conjunction with the private keyword in order to set private access to calculateTax(). This means I can access the method from getFinalPrice(). Here’s an external attempt to access calculateTax()


Error: Call to private method popp\ch04\batch06_9\UtilityService::calculateTax() from context ...


This post is part of series:

1 - Object Oriented Programming Concept2 - Classes3 - Objects4 - Methods5 - Constructors6 - Arguments and Types7 - Static Methods and Properties8 - Constant Properties9 - Abstract Classes10 - Interfaces11 - Traits
OOP#OOPPHP#PHP

Comments (0)