The Decorator Design Pattern in PHP (with easy to follow example)

The decorator pattern can be used like this (scroll below it for a short description):

The following example is a simplified example. For a slightly more complicated, but still simple example, please see the 2nd snippet near the bottom of this page. But read through this one first...

<?php
interface WebsiteInterface
{
    public function getPrice();
    public function getDescription();
}
/** This is the base website 'service', it does not accept a WebsiteInterface object in it's constructor */
class BaseWebsite implements WebsiteInterface
{
    public function getPrice()
    {
        return 1000;
    }
    public function getDescription()
    {
        return "    A barebones website";
    }
}

/** But this (and all others below it) expect a WebsiteInterface object in it's constructor */
class CustomDesign implements WebsiteInterface
{
    protected $website;
    public function __construct(WebsiteInterface $website)
    {
        $this->website = $website;
    }
    public function getPrice()
    {
        return 2000 + $this->website->getPrice();
    }
    public function getDescription()
    {
        return $this->website->getDescription() . ",\n    and a completely custom design, designed by an in-house designer";
    }
}

class WordPressBlog implements WebsiteInterface
{
    protected $website;
    public function __construct(WebsiteInterface $website)
    {
        $this->website = $website;
    }
    public function getPrice()
    {
        return 750 + $this->website->getPrice();
    }
    public function getDescription()
    {
        return $this->website->getDescription() . ",\n    and setting everything up with a WordPress blog";
    }
}

class ContactForm implements WebsiteInterface
{
    protected $website;
    public function __construct(WebsiteInterface $website)
    {
        $this->website = $website;
    }
    public function getPrice()
    {
        return 150 + $this->website->getPrice();
    }
    public function getDescription()
    {
        return $this->website->getDescription() . ",\n    and adding a contact form";
    }
}

class YearHosting implements WebsiteInterface
{
    protected $website;
    public function __construct(WebsiteInterface $website)
    {
        $this->website = $website;
    }
    public function getPrice()
    {
        return (12 * 30) + $this->website->getPrice();
    }
    public function getDescription()
    {
        return $this->website->getDescription() . ",\n    including a year's hosting";
    }
}
// basic website:
// Let's see the price / desc of the basic website. This is just like a normal bit of code:
echo "=== BASIC WEBSITE ===\n";
$basic_website = new BaseWebsite();
echo "Cost: " . $basic_website->getPrice() . "\n";
echo "Description of all included services: \n" . $basic_website->getDescription() . "\n";
// but now, let's say we want to get the price of a website with a custom design. Here is how we can get it all working:
echo "=== BASIC WEBSITE + CUSTOM DESIGN  ===\n";
$basic_and_custom_design = new CustomDesign(new BaseWebsite());
echo "Cost: " . $basic_and_custom_design->getPrice() . "\n";
echo "Description of all included services: \n" . $basic_and_custom_design->getDescription() . "\n";
echo "=== BASIC WEBSITE + CUSTOM DESIGN + WP + CONTACT FORM + HOSTING FOR A YEAR ===\n";
$basic_and_custom_design = new YearHosting(new ContactForm(new WordPressBlog(new CustomDesign(new BaseWebsite()))));
echo "Cost: " . $basic_and_custom_design->getPrice() . "\n";
echo "Description of all included services: \n" . $basic_and_custom_design->getDescription() . "\n";

The output:

=== BASIC WEBSITE ===
Cost: 1000
Description of all included services: 
    A barebones website
=== BASIC WEBSITE + CUSTOM DESIGN  ===
Cost: 3000
Description of all included services: 
    A barebones website,
    and a completely custom design, designed by an in-house designer
=== BASIC WEBSITE + CUSTOM DESIGN + WP + CONTACT FORM + HOSTING FOR A YEAR ===
Cost: 4260
Description of all included services: 
    A barebones website,
    and a completely custom design, designed by an in-house designer,
    and setting everything up with a WordPress blog,
    and adding a contact form,
    including a year's hosting

A description of the decorator design pattern in PHP: The decorator pattern is a good way to be able to easy extend your application with if you need to change how a script will handle things at runtime. I could have achieved the same output by making a bunch of methods on the main class with things like public function add_hosting() { $this->price += 200; $this->description .= " Add hosting..."; }. But that is not a good idea. Using the decorator pattern is much more extendable, cleaner code, follows SOLID principles.

Using the decorator pattern means that you can easily add new features, without having the change the main or existing class(es). In the example above, it would be very trivial to add a new service called "TransferSiteFromOldHost", and add that to an order.

I simplified the example above, but it is actually a good idea to actually replace the interface with an abstract class, which includes abstract functions. The main base service class would only implement the interface - the others would extend the abstract class (which itself does implement the interface). Here is an example (the output would be the exact same):

<?php
interface WebsiteInterface
{
    public function getPrice();
    public function getDescription();
}

abstract class WebsiteFeature implements WebsiteInterface
{
    protected $website;
    public function __construct(WebsiteInterface $website)
    {
        $this->website = $website;
    }
    abstract public function getPrice(); // must be implemented by child class
    abstract public function getDescription(); // must be implemented by child class
}
/** This only implements the interface (does not extend the abstract function, so does not have the constructor) */
class BaseWebsite implements WebsiteInterface
{
    public function getPrice()
    {
        return 1000;
    }
    public function getDescription()
    {
        return "    A barebones website";
    }
}

/** But this (and all others below it) extends the abstract class, so the constructor is there. They must implement the abstract methods too.*/

class CustomDesign extends WebsiteFeature
{
    public function getPrice()
    {
        return 2000 + $this->website->getPrice();
    }
    public function getDescription()
    {
        return $this->website->getDescription() . ",\n    and a completely custom design, designed by an in-house designer";
    }
}

class WordPressBlog extends WebsiteFeature
{
    protected $website;
    public function __construct(WebsiteInterface $website)
    {
        $this->website = $website;
    }
    public function getPrice()
    {
        return 750 + $this->website->getPrice();
    }
    public function getDescription()
    {
        return $this->website->getDescription() . ",\n    and setting everything up with a WordPress blog";
    }
}

class ContactForm extends WebsiteFeature
{
    protected $website;
    public function __construct(WebsiteInterface $website)
    {
        $this->website = $website;
    }
    public function getPrice()
    {
        return 150 + $this->website->getPrice();
    }
    public function getDescription()
    {
        return $this->website->getDescription() . ",\n    and adding a contact form";
    }
}

class YearHosting extends WebsiteFeature
{
    protected $website;
    public function __construct(WebsiteInterface $website)
    {
        $this->website = $website;
    }
    public function getPrice()
    {
        return (12 * 30) + $this->website->getPrice();
    }
    public function getDescription()
    {
        return $this->website->getDescription() . ",\n    including a year's hosting";
    }
}

Again, this example was simplified. In the real world you wouldn't be just returning strings like this. But hopefully it gets the idea across.

Design pattern type: Structural design pattern

Comments The Decorator Design Pattern in PHP (with easy to follow example)