PHP traits – Everything you need to know

PHP traits

PHP is one of the most popular programming languages for web development or rather I would say The most popular language. PHP is easy to learn and write. You can learn PHP within a few weeks and start building your own website. In this tutorial, I am going to talk about the lesser known feature of PHP i.e traits. It is included in the PHP 5.4.x version.

What is the need for PHP traits?

Suppose we have to develop an E-commerce Website which sells a variety of stuff. To manage various products data I have created one common class called Product. The Product now acts as a base class for all the product type we may need to create. The Product class handles two basic property of any product i.e name of the product and the second one is the price which will be common for all the products.

Now we create different types of products using the Product class as our template. Let’s create two more types of classes one is more Mobile Phones and one for Shoes.

<?php

class Product {

    private $name;

    private $price;

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

    public function getName()
    {
        return $this->name;
    }

    public function getPrice()
    {
        return $this->price;
    }
}


class Phone extends Product {

    private $taxrate = 20;
    
    private $cameraSpecs;

    public function __construct($name, $price, $cameraSpecs)
    {
        parent::__construct($name, $price);
        $this->cameraSpecs = $cameraSpecs;
    }
    
    public function getSpces()
    {
        return $this->specs;
    }

    public function calculateTax(float $price)
    {
        return ( $this->taxrate / 100 ) * $price;
    }

}

class Shoe extends Product {

    private $taxrate = 8;
    
    private $sizes;

    public function __construct($name, $price, array $sizes)
    {
        parent::__construct($name, $price);
        $this->sizes = $sizes;
    }
    
    public function getSizes()
    {
        return 'Availabe Sizes : ' . implode( ', ', $this->sizes );
    }

    public function calculateTax(float $price)
    {
        return ( $this->taxrate / 100 ) * $price;
    }

}

$phoneObj = new Phone('iPhone X', 1000, '12 pixel Dual Camera');

$shoeObj = new Shoe('Nike Revolution', 400, [8, 10, 12]);

As you can see in the code both the class extends the Product class so that you don’t have to write implementations of getName() and getPrice() methods again and again.

You can also notice that both the classes have one private property named $taxrate which holds the sells tax of that product. We have kept that property as private so that any calling should not be able to mess up with the $taxrate value.

The Shoes Phone classes have one common method named calculateTax(). The calculateTax() method accepts a $price argument and calculates a sales tax amount based on the private $taxrate property.

The Problem In The Above Code.

Now what I have done is that I have implemented the duplicate code. We have implemented the same method calculateTax() with the same code in two different classes.

This is not good practice and it is against the Object Oriented Programmings Principles. One of a prominent feature of OOPs concept is to remove the code duplication.

Now you can make an argument that I can include the calculateTax() method in the Product class so that both the classes will have access to that, But what if calculateTaxt() belongs to different class say PriceUtilities which can provide more features related to the prices of the products. Now the problem is that PHP does not support multiple inheritances. In simple words, you can’t extend more than one class in PHP.

Defining a Trait.

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.

What the Official Documentation says:

As of PHP 5.4.0, PHP implements a method of code reuse called Traits.

Traits are a mechanism for code reuse in single inheritance languages such as PHP. A Trait is 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. The semantics of the combination of Traits and classes is defined in a way which reduces complexity and avoids the typical problems associated with multiple inheritance and Mixins.

A Trait is similar to a class, but only intended to group functionality in a fine-grained and consistent way. It is not possible to instantiate a Trait on its own. It is an addition to traditional inheritance and enables horizontal composition of behavior; that is, the application of class members without requiring inheritance. – PHP Documentation

Using a Trait.

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 Phone and the Shoe classes.

trait PriceUtilities
{
    public function calculateTax(float $price, float $taxrate)
    {
        return (($taxrate / 100) * $price);
    }
    
    // other utilities
}

Let’s solve our initial problem using the Trait.

class Product
{
    private $name;

    private $price;

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

    public function getName()
    {
        return $this->name;
    }

    public function getPrice()
    {
        return $this->price;
    }
}



class Phone extends Product
{
    use PriceUtilities;

    private $taxrate = 20;

    private $cameraSpecs;

    public function __construct($name, $price, $cameraSpecs)
    {
        parent::__construct($name, $price);
        $this->cameraSpecs = $cameraSpecs;
    }

    public function getSpces()
    {
        return $this->specs;
    }

    public function getTaxRate()
    {
        return $this->taxrate;
    }
    
}

class Shoe extends Product
{
    use PriceUtilities;

    private $taxrate = 8;

    private $sizes;

    public function __construct($name, $price, array $sizes)
    {
        parent::__construct($name, $price);
        $this->sizes = $sizes;
    }

    public function getSizes()
    {
        return 'Availabe Sizes : ' . implode(', ', $this->sizes);
    }

    public function getTaxRate()
    {
        return $this->taxrate;
    }

}

$phoneObj = new Phone('iPhone X', 1000, '12 pixel Dual Camera');

echo "Tax For {$phoneObj->getName()} is : \${$phoneObj->calculateTax($phoneObj->getPrice(), $phoneObj->getTaxRate())}" ."\n";

$shoeObj = new Shoe('Nike Revolution', 400, [8, 10, 12]);

echo "Tax For {$shoeObj->getName()} is : \${$shoeObj->calculateTax($phoneObj->getPrice(), $shoeObj->getTaxRate())}" ."\n";

// Output
// Tax For iPhone X is : $200
// Tax For Nike Revolution is : $80

Using More Than One Trait.

It is possible to use the multiple trait in any classes you want You can specify trait names you want to use after the use keyword in comma separated manner.

Let’s define another trait called GeneratorTrait which will have one method generateID() to generate a unique ID for our products.

trait GeneratorTrait
{
    public function generateId()
    {
        return uniqid(); // PHP function to generate unique ID
    }
}

Let’s modify the Shoe Phone classes to incorporate the above trait,

class Phone extends Product
{
    use PriceUtilities, GeneratorTrait;

    private $taxrate = 20;

    private $cameraSpecs;

    public function __construct($name, $price, $cameraSpecs)
    {
        parent::__construct($name, $price);
        $this->cameraSpecs = $cameraSpecs;
    }

    public function getSpces()
    {
        return $this->specs;
    }

    public function getTaxRate()
    {
        return $this->taxrate;
    }
}

class Shoe extends Product
{
    use PriceUtilities, GeneratorTrait;

    private $taxrate = 8;

    private $sizes;

    public function __construct($name, $price, array $sizes)
    {
        parent::__construct($name, $price);
        $this->sizes = $sizes;
    }

    public function getSizes()
    {
        return 'Availabe Sizes : ' . implode(', ', $this->sizes);
    }

    public function getTaxRate()
    {
        return $this->taxrate;
    }
}

$phoneObj = new Phone('iPhone X', 1000, '12 pixel Dual Camera');

echo "Tax For {$phoneObj->getName()} (Product ID : {$phoneObj->generateId()}) is : \${$phoneObj->calculateTax($phoneObj->getPrice(), $phoneObj->getTaxRate())}" . "\n";

$shoeObj = new Shoe('Nike Revolution', 400, [8, 10, 12]);

echo "Tax For {$shoeObj->getName()} (Product ID : {$shoeObj->generateId()}) is : \${$shoeObj->calculateTax($phoneObj->getPrice(), $shoeObj->getTaxRate())}" . "\n";

// Output
// Tax For iPhone X (Product ID : 5caa1b4e313e2) is : $200
// Tax For Nike Revolution (Product ID : 5caa1b4e31422) is : $80

Methods Name Conflicts.

As you have seen in the previous section that we can use multiple traits in PHP which is a great feature but while using many traits either defined by you or the third party library you use in your project, there is a possibility that you end up using the same name for methods residing in the two different traits.

Consider the following trait we define,

trait TaxUtilities
{
    public function calculateTax(float $price)
    {
        return $price * 12;
    }
}

As you can see I have used the same method name for two methods of different traits which are calculateTax().

Let’s try to use the TaxUtilities trait in our Phone class,

class Phone extends Product
{
    use PriceUtilities, GeneratorTrait, TaxUtilities;

    private $taxrate = 20;

    private $cameraSpecs;

    public function __construct($name, $price, $cameraSpecs)
    {
        parent::__construct($name, $price);
        $this->cameraSpecs = $cameraSpecs;
    }

    public function getSpces()
    {
        return $this->specs;
    }

    public function getTaxRate()
    {
        return $this->taxrate;
    }
}

Now if we run our code I will get the following error,

Fatal error: Trait method calculateTax has not been applied, because there are collisions with other trait methods on Phone in R:\Blog Posts\PHP Traits\index.php on line 54

To fix this problem, I can use the insteadof keyword. Here’s how:

class Phone extends Product
{
    use PriceUtilities, GeneratorTrait, TaxUtilities{
        TaxUtilities::calculateTax insteadOf PriceUtilities;
    }

    private $taxrate = 20;

    private $cameraSpecs;

    public function __construct($name, $price, $cameraSpecs)
    {
        parent::__construct($name, $price);
        $this->cameraSpecs = $cameraSpecs;
    }

    public function getSpces()
    {
        return $this->specs;
    }

    public function getTaxRate()
    {
        return $this->taxrate;
    }
}

The preceding snippet means, “Use the calculateTax() method of TaxUtilities instead of the method of
the same name in PriceUtilities.”

Aliasing Overridden Trait Methods.

PHP allows you to alias an overridden methods in traits so you can now use the calculateTax method form both the traits, like this,

class Phone extends Product
{
    use PriceUtilities, GeneratorTrait, TaxUtilities{
        TaxUtilities::calculateTax insteadOf PriceUtilities;
        PriceUtilities::calculateTax as basicTax;
    }

    private $taxrate = 20;

    private $cameraSpecs;

    public function __construct($name, $price, $cameraSpecs)
    {
        parent::__construct($name, $price);
        $this->cameraSpecs = $cameraSpecs;
    }

    public function getSpces()
    {
        return $this->specs;
    }

    public function getTaxRate()
    {
        return $this->taxrate;
    }
}

If you have any suggestions or if you face any problem please write it down in the comment box…

Happy Coding…:)

Click Here To Read About Datatables Using Codeigniter

Ropali Munshi
Ropali Munshi
Ropali Munshi is fullstack PHP Developer. He is passionate developer who loves to learn and expirement with new programming languages , libraries and frameworks. Nowdays he is more into the JavaScript realm.

Leave a Reply

Your email address will not be published. Required fields are marked *