An Intro to Decorator Design Pattern in PHP

The famous Gang of Four book states that the Decorator design pattern attaches additional responsibilities to an object dynamically and provides a flexible alternative to sub-classing for extending the functionality of parent classes. This pattern makes us able to develop flexible designs and in this tutorial, the implementation of this pattern in PHP programming language will be covered.

The Decorator (aka Wrapper) pattern makes it possible to add new features to objects by placing them inside other so-called wrapper objects which contain those features. To get more familiar with this pattern, let's take a look at an example:

<?php
interface Coffee
{
    public function cost();
}

class DarkCoffee implements Coffee
{
    public function cost()
    {
        echo "The original price is 8$\n";
        return 8;
    }
}

abstract class DarkCoffeeDecorator implements Coffee
{
    protected $coffee;

    public function __construct(Coffee $coffee)
    {
        $this->coffee = $coffee;
    }

    abstract public function cost();
}

class DarkCoffeeWithMilk extends DarkCoffeeDecorator
{
    public function cost()
    {
        echo "1$ for extra milk added\n";
        return $this->coffee->cost() + 1;
    }
}

class DarkCoffeeWithCream extends DarkCoffeeDecorator
{
    public function cost()
    {
        echo "1.5$ for cream added\n";
        return $this->coffee->cost() + 1.5;
    }
}

$coffee = new DarkCoffee();
$coffee = new DarkCoffeeWithMilk($coffee);
$coffee = new DarkCoffeeWithCream($coffee);
echo "Total price is: " . $coffee->cost() . "$";

We have an interface which both the DarkCoffee and DarkCoffeeDecorator classes implement it. The DarkCoffee has the only method defined it its interface called cost() which prints the original price of a dark coffee and returns the price as well.

Any class that contains at least one abstract method must also be abstract that's why the DarkCoffeeDecorator class is abstract (These classes cannot be instantiated.) When inheriting from an abstract class, all methods marked abstract in the parent class declaration must be defined by the child with the same name. In this class, we have a protected property called $coffee which is populated in the constructor and as you can see, the constructor parameter is an object of the Coffee class.

 There are two classes called DarkCoffeeWithMilk and DarkCoffeeWithCream that are extended from DarkCoffeeDecorator class and as we said earlier, the child classes of an abstract class need to define all abstract methods of their parent classes; that's why, the cost() method is defined in both sub-classes printing relative messages to the condiment plus returning the original price of a coffee added to the condiment price.

In lines 46 to 49, we have tested how this pattern works. First, we created an object from DarkCoffee class then assigned an object from DarkCoffeeWithMilk class to the $coffee variable in line 47 and again in line 48 assigned an object of DarkCoffeeWithCream to this variable and at the end of the day called the cost() method in line 49:

1.5$ for cream added
1$ for extra milk added
The original price is 8$
Total price is: 10.5$

The first line in the resultset is related to line 48 which the DarkCoffeeWithCream class added some cream to the original dark coffee and 1.5 $ was added to the original price as well. The reason why the second line in the output is produced is the same as the previous line but this time with the help of DarkCoffeeWithMilk class.

In a nutshell, we have a main class called DarkCoffee and by means of the Decorator design pattern, we wrapped it in other objects to add some new features dynamically without modifying the original class.

by Behzad Moradi on 2019-08-13