Skip to content
Home » Decorator Pattern in PHP | Using Design Patterns in 2023

Decorator Pattern in PHP | Using Design Patterns in 2023

Using the Decorator Design Pattern in PHP

The Decorator pattern in PHP is a structural design pattern that adds additional responsibilities to an object dynamically. It uses composition rather than inheritance. This pattern introduces decorator objects which wrap a class and extend or alter its behaviour using the power of composition and polymorphism. 

Decorator Pattern in PHP
Decorator Pattern in PHP

Seems tricky? No worries as we will understand it piece by piece. By the end of this article, you will be able to understand the decorator pattern in PHP, hopefully.

Definition | Decorator Design Pattern in PHP

“Decorator design pattern is a structural design pattern that attaches additional responsibilities to a class dynamically using wrapper objects called decorators.”

Decorator Pattern in PHP
Decorator Pattern

The objective of this pattern is to extend an object’s behaviour dynamically using composition.

Burger Queen | A Real-World Example

So, we are assigned a project to develop an application for “Burger Queen”, a new burger joint downtown. They have quite a minimalistic menu at the moment offering three varieties: Chicken, Beef & Fish. Much to the ease of the developers, they have a straightforward software design with inheritance in action.

Decorator Pattern in PHP
Burger class

That seems right unless we have complete requirements. The joint agrees to add extras for a more customized experience for burger lovers. These extras include fry sauce, cheese sauce, tartar sauce & extra patty at the moment, and the joint plans to add in more with the passage of time.

Now that’s quite some change. So how does that affect the design?  Let’s do a quick math. Take a beef burger and the extras,  how many different combinations can we have? That’s 15 different combinations. Now, add 15 two times more for chicken and fish burgers. The total number of sub-classes (adding the three simple burger classes with no extras) comes to about 48. That’s INSANE. 

Problem 🙁

The problem is quite apparent now. Who needs 48 sub-classes for three burgers and four extras? Suppose they add stuff in the long run, and we are supposed to make that change in the application. Can you anticipate the pain of extending this application? We are bound to add lots of bugs because the application is nothing more than a total mess.

Inheritance & class explosion

The most evident problem is the class explosion. We currently have 48 classes, and it is already a horrendous design. Undoubtedly, the class explosion is certainly a negative design factor as it makes an application extremely rigid, difficult, time-consuming, and costly to change.

The cause of it is using inheritance, a popular and powerful concept in OOP but not a silver bullet to every problem. Inheritance is static in nature, you have to define and declare everything before runtime, and there’s no chance you can dynamically add or remove behaviours and properties (which is the need here).

Inheritance & redundancy

Besides, inheritance can add redundancy to an application. Consider we have methods for extras in the base class. Suppose hasTartarSauce() that returns a boolean. Suppose Tartar sauce is added to the fish burger only. The other two burger classes still have to provide some implementation for it because they are inheriting it from the same base class as fish.

That’s a bad idea and may induce unintended effects in your application. Not all sub-classes are supposed to exhibit the same properties or behaviour. Sometimes it is just about establishing a hierarchy or a relation based on a subset of features. For example, humans and chimps. They have some similarities, but their characteristics and behaviours are not exactly the same. 

Open-closed principle

The Open-closed principle states that “a class should be open for extension but closed for modification.”  The design here blatantly violates this principle. 

In the aftermath of a price change of any item, we have to commence a disastrous adventure to change the price() method in all the sub-classes. It is a pain and a gate wide open for bugs to creep in.

Solution 🙂

Adding dynamic behaviours

The system itself is customizable and dynamic. There are extras that the customers can add dynamically to their orders. We need a more dynamic design for such systems that could bind properties or behaviours at runtime. Having a static design means covering every combination beforehand, and that’s when we need to define everything before the runtime.

The consequence of such a static design for a dynamic application of this scale is a bloated class hierarchy with possibly hundreds and thousands of sub-classes.

Composition over inheritance

Inheritance is not a universal solution for flexible systems. Rather, it can be the opposite sometimes, just as we have seen here. The choice of a design varies from case to case. So, the key here is to bind or inherit the behaviours or properties at runtime.

So, the answer is composition. Composition is when you add a reference to an object in your class. The class is supposed to delegate relevant work to this object. Not only delegate but add or remove the object itself. That’s what we need here. 

Besides, thanks to polymorphism in OOP, makes our application more extensible because we can add the classes to the system without changing the class that refers to it. Suppose you have a Dog class. Then you can have as many breeds of dogs as its sub-classes. In polymorphism, if your application refers to a Dog object and doesn’t bother about what type or breed it is.

Using the Decorator Design Pattern

A decorator or a wrapper is an object that wraps over the base object extending or modifying its behaviours and properties. That sound cool but let’s understand this abstract idea by making ourselves a burger.

Let’s make a beef burger with cheese sauce & extra patty

1. Start with a beef burger.

beef burger

2. Decorate/wrap it with an extra beef patty object.

beef burger with extra patty

3. Decorate/wrap it with a cheese sauce object.

beef burger with extra patty and cheese sauce

4. Call the price() method, let the objects delegate these calls, and calculate the price of the beef burger with an extra beef patty and cheese sauce.

calculating price of the burger

What do we know about the decorator class so far?

So a decorator class acts as a wrapper over an object. The wrapper’s object can be a decorator itself or some other class of the same supertype, meaning that they inherit from the same parent class.

We can use the class and decorators polymorphically because they have the same parent type. Thus, decorators can be applied in any combination. For example, A burger with an extra patty and cheese sauce is still a burger. 

We can add decorators at runtime, and they can call methods (delegate) on the objects they wrap. Think of it as dominos’ block. Calling drop() on the outermost makes all the subsequent drop() calls consecutively. That’s what we did above when we calculated the burger’s price with the extra stuff added.

Decorator Pattern in PHP

A common parent class

Just as we have already mentioned, decorators and any other classes that it decorates should inherit from the same parent class. In our application, it has to be the Burger class.

Decorator Pattern in PHP

It has an abstract method price() that the sub-classes will implement.

Beef burger class example

Let’s see the example of the BeefBurger class extending the Burger class.

Decorator Pattern in PHP

Burger decorator

Time for a super decorator class BurgerDecorator. All the decorators inherit it and it in turn inherits the Burger class so as to have a common parent with the concrete burger classes that the decorators are supposed to wrap.

Decorator Pattern in PHP

You might be wondering about abstract getDescription() why do we need to implement it in decorator sub-classes again? You will have your answer just now.

Cheese sauce decorator

So, the CheeseSauce class extends the BurgerDecorator. It also implements the price() function – obviously. But it also implements the abstract getDescription() function, adding the description of the wrapped object to its ‘cheese sauce’ string.

cheese sauce decorator pseudocode

Benefits of the Decorator Design Pattern

  • Adds many decorator classes as you want without modifying the existing code.
  • Uses compositional recursion to call a method on a layer of wrapped objects.
  • Uses composition to form different combinations of objects at runtime.
  • Dynamic and efficient design than if you use inheritance.

Complete Architecture | Decorator Design Pattern in PHP

Decorator Pattern in PHP
Decorator Pattern in PHP

Decorator Design Pattern in PHP

The following is a complete example of the decorator pattern in PHP.

Code | Decorator Design Pattern in PHP

<?php
 
abstract class Burger {
    private $description = "Not applicable";
 
    function getDescription() {
        return $this->description;
    }
 
    function setDescription($description) {
        $this->description = $description;
    }
 
    abstract function price();
}
 
class BeefBurger extends Burger {
 
    function __construct() {
        $this->setDescription('Beef Burger ');
    }
 
    function price() {
        return 7;
    }
}
 
class FishBurger extends Burger {
 
    function __construct() {
        $this->setDescription('Fish Burger ');
    }
 
    function price() {
        return 10;
    }
}
 
class ChickenBurger extends Burger {
 
    function __construct() {
        $this->setDescription('Chicken Burger ');
    }
 
    function price() {
        return 5;
    }
}
 
abstract class BurgerDecorator extends Burger{
    private $burger;
 
    function setBurger($burger) {
        $this->burger = $burger;
    }
 
    function getBurger() {
        return $this->burger;
    }
}
 
class ExtraPatty extends BurgerDecorator {
 
    function __construct($burger) {
        $this->setBurger($burger);
    }
 
    function getDescription() {
        return $this->getBurger()->getDescription().' +Extra Patty ';
    }
 
    function price() {
        return 3 + $this->getBurger()->price();
    }
}
 
class TarTarSauce extends BurgerDecorator {
 
    function __construct($burger) {
        $this->setBurger($burger);
    }
 
    function getDescription() {
        return $this->getBurger()->getDescription().' +TarTar Sauce ';
    }
 
    function price() {
        return 2 + $this->getBurger()->price();
    }
}
 
class CheeseSauce extends BurgerDecorator {
 
    function __construct($burger) {
        $this->setBurger($burger);
    }
 
    function getDescription() {
        return $this->getBurger()->getDescription().' +Cheese Sauce ';
    }
 
    function price() {
        return 1 + $this->getBurger()->price();
    }
}
 
class FrySauce extends BurgerDecorator {
 
    function __construct($burger) {
        $this->setBurger($burger);
    }
 
    function getDescription() {
        return $this->getBurger()->getDescription().' +Fry Sauce ';
    }
 
    function price() {
        return 1.5 + $this->getBurger()->price();
    }
}
 
function main() {
    $burger = new BeefBurger();
 
    $extraPatty = new ExtraPatty($burger);
 
    $cheeseSauce = new CheeseSauce($extraPatty);
 
    echo $cheeseSauce->getDescription(). ' costs $'.$cheeseSauce->price();
}
 
main();
?>

Output | Decorator Design Pattern in PHP

Beef Burger  +Extra Patty  +Cheese Sauce  costs $11

Pros and Cons of the Decorator Pattern in PHP

ProsCons
Satisfies the open-closed principle as classes can be added without modifying existing codeIt is hard to remove a decorator if it is not the outermost in the stack.
Add or remove behaviours dynamically at runtime.The order of decorators can affect the runtime behaviour
Add as many decorators as desired.Needs a lot of boilerplate code for defining a complex combination of decorators.
Uses composition recursion to perform similar operations on all the objects in the stack

Where is the Decorator Pattern Used?

  • When your class can exhibit different behaviours or properties dynamically.
  • When you want to extend your class without using inheritance.

Frequently Asked Questions on the Decorator Design Pattern

What is the decorator pattern?

The decorator design pattern is a structural design pattern that attaches additional responsibilities to a class dynamically using wrapper objects called decorators.

What is the purpose of the decorator pattern?

The purpose of the decorator pattern is to bind new behaviours to a class by wrapping it in special objects called decorators. Decorators extend or alter a class’s behaviour without having to sub-class it.

When to use the decorator pattern?

When you want to add additional behaviours to an existing class dynamically. Suppose you have an HTTP class and want to add middleware functions to process request/response objects. Think of the middleware functions as decorators. You can dynamically add or remove middleware functions before your request hits the base HTTP object.

Using the Decorator Pattern in PHP Today

Voila! That’s the end of the decorator pattern. Let’s revisit what we have seen thus far. We have learned that the decorator pattern is a structural pattern that uses wrapper objects called decorators. These decorators extend or alter a class behaviour by binding to it via composition. Any layer or combination of complexity is possible with the decorators.

The article explains a real-life example of a burger joint, Burger Queen. They have a few burgers and some extras to add to them. Though it seems inheritance is the de facto design choice for many OOP problems, upon further analysis, we conclude that inheritance does more harm than good.

The article then introduces the concept of decorators. The decorator uses composition to refer to the objects it is supposed to wrap. All these objects and decorators have to have the same common parent if they are to leverage composition recursion such that calling a method on the outermost decorator starts a chain reaction that stops when it reaches the last class in the stack.

Based on this understanding, we were able to design an application using the decorator design pattern in PHP where the burger can dynamically get the add-on or the extras. The extras or the adds-on are the decorators. All of them inherit from the Burger class.

So, that’s all about it. We hope you are now well aware of this design pattern and can implement it whenever possible.

See you in another design pattern article. Stay tuned at FuelingPHP.

Books on Design Patterns

Want to learn more about Design Patterns? There are many great resources online. We recommend the following books for your collection as they both can teach you the theoretical and the application of using design patterns in your day-to-day programming. Feel free to use the following Amazon affiliate links if you’d like to purchase them and a way to support our efforts.

Design Patterns: Elements of Reusable Object-Oriented Software

Design Patterns: Elements of Reusable Object-Oriented Software book

This is the book that started it all. I believe that every programmer should have a referenced copy to this book at some point in their career. There have been many updates and excellent newer content through the years, but this is a classic. It still stands the test of time and is just as relevant for today as it was in the original printing in the 90s.

Check it out on amazon

Learning PHP Design Patterns

This is an excellent book to go beyond the theory and apply it to writing good PHP code. Learning PHP Design Patterns is published by the popular O’Reilly media company. O’Reilly consistently publishes some of the most useful reference material related to software development. They are known to provide materials that thoroughly cover a topic in a way that is simple to understand. I recommend this book to every PHP developer.

Check it out on amazon

Design Patterns in PHP Learning Series.

This article is part of our series of design patterns in PHP. We are going through all of the patterns and showing how they can help you build better applications. Browse through our full list of patterns below.