Building a PHP Pizza with the Decorator Pattern

Published:
Updated:
Introduction
This article shows how to exploit the object-oriented design pattern called Decorator Pattern to solve a common problem in application design.

To illustrate the problem, we will try to make a pizza kitchen ordering system.  There will be a-la-carte ingredients for our pizzas, and our customers can choose any combination of ingredients they want.  A pizza can be plain, or have any combination of toppings.  How many PHP classes would we need to describe the possible orders?  Let's say we have two toppings - pepperoni and mushroom.  Here are the pizzas our customers can order:
 
  • Plain
  • with Pepperoni
  • with Mushroom
  • with Pepperoni and Mushroom
That seems manageable.  We can quickly write 4 classes, one to describe each of our pizzas.
 
<?php
                      Class Plain { /* Plain Pizza Definition: $10 */ }
                      Class Pepperoni { /* Pepperoni Pizza Definition: $13 */ }
                      Class Mushrooms { /* Mushrooms Pizza Definition: $12 */ }
                      Class PepperoniAndMushrooms { /* Pepperoni + Mushrooms Pizza Definition: $15 */ }
                      /* Use the classes here */

Open in new window


With Growth, We Encounter Troubles
But what if we add Mozzarella to the list?  Our problem gets a bit bigger!
 
  • Plain
  • with Pepperoni
  • with Mushroom
  • with Mozzarella
  • with Pepperoni, Mushroom
  • with Pepperoni, Mozzarella
  • with Mushroom, Mozzarella
  • with Pepperoni, Mushroom, Mozzarella
That still seems manageable.  We can just write 8 classes to describe the pizzas.

But what if we add Green Pepper to the list?  Our problem is getting out of hand!
 
  • Plain
  • with Pepperoni
  • with Mushroom
  • with Mozzarella
  • with Green Pepper
  • with Pepperoni, Mushroom
  • with Pepperoni, Mozzarella
  • with Pepperoni, Green Pepper
  • with Mushroom, Mozzarella
  • with Mushroom, Green Pepper
  • with Mozzarella, Green Pepper
  • with Pepperoni, Mushroom, Mozzarella
  • with Pepperoni, Mushroom, Green Pepper
  • with Pepperoni, Mozzarella, Green Pepper
  • with Mushroom, Mozzarella, Green Pepper
  • with Pepperoni, Mushroom, Mozzarella, Green Pepper
I'm not even sure I found all of the combinations, but we can see the problem developing - adding even one ingredient to the list grows the combinations by a 2^n rate!

In reality, the problem is much more severe than these simple examples.  Pizzas can have lots of different toppings.

If you only serve the top-10 pizza items, your clients can order more than 5,000 different combinations, and that's when they choose only four toppings.  Move up to the top-50 pizza items, and you're looking at a huge programming problem -- more than 5,500,000 different pizzas can be made with just four toppings from that list.

You just can't write that many PHP classes, so it's clear that a different solution is needed.  What may not be immediately clear, however, is the impact of a price change on our programming.  If just one of the 50 ingredients changed price, it has the potential to affect more than 100,000 different pizza classes.  If you could change one class per minute, you would be looking at nearly 70 days of non-stop coding just to change the price of an ingredient.  We cannot have one PHP class for every pizza.

Can We Use Subclasses and Inheritance?
Could we handle this problem with inheritance or with multiple methods on a parent class?  Perhaps, and it's an instructive exercise to try that.  You will probably find yourself writing a great many if() statements.  And there is the question of inheritance - what method would you override to adjust the price?  It may be difficult to test the code.  Add in an order for a coke or a beer and see what happens to the inheritance design.  Change a price and you have to change a method on the parent class or a method on a child class, and that implies a new round of integration testing.  And these kinds of changes must happen in the source code at compile time.

If you're into the formal principles of object-oriented design, you may see that this violates the Single Responsibility Principle, since every object (pizza) created from a master class must carry with it all of the methods to create all of the possible combinations of toppings - whether they are needed on this pizza or not.  There is no Separation of Concerns, thus the resulting software contains unnecessary parts.

Decorator Pattern to the Rescue
The classic definition of the Decorator Pattern is that the pattern attaches responsibilities to an object dynamically.  This means that the interface to the object is stable and unchanged, and software that uses the undecorated object can remain intact.  But by adding the decoration (just like the pizza topping) we get a different object.  In the case of our code example, we will decorate the pizza orders by adding a topping and a cost to the pizza as it is built in our object-oriented kitchen.

To start with, we will need an interface that defines the way the our Pizza will look to the rest of the code.  Appropriately we will call this interface, "Pizza."  The interface defines two methods that any implementing class must define.
 
Interface Pizza
                      {
                          public function topping();
                          public function price();
                      }

Open in new window


Next we will define an abstract class that implements the topping() method, but leaves the cost() method to the subclasses that extend it.  We are playing a bit of a trick with this software to cut down on the number of variables and lines of code we will need.  Take a look at the return from the concrete topping() method.  It returns an array that contains whatever was in the topping array before it was called, and adds its own class name to the array.  This lets us name our subclasses with a name that identifies the topping.  That may sound strange at first, but it will make much more sense as you see how we use the interface and the abstract class to build pizzas.  You will soon see that this scheme means that we write less code and therefore have less chance for error!
 
Abstract Class Topping
                      {
                          public function topping(){
                              return array_merge($this->Pizza->topping(), [get_class($this)]);
                          }
                      
                          abstract public function price();
                      }

Open in new window


Armed with this code set, we are ready to make a pizza.  Our BasicPizza class will do that for us, laying down a delicious crust.  Note that it implements the Pizza interface, which means that it must produce both of the methods called for in the interface definition.  So in addition to the delicious crust, it also establishes the base price for our pizza, in this case, $10.  If we wanted to, we could sell the BasicPizza as-is.  Just "new up" a BasicPizza object and you can see that it is fully formed, but also full of potential for us to add toppings.
 
Class BasicPizza implements Pizza
                      {
                          public function topping(){
                              return ['Delicious Crust'];
                          }
                      
                          public function price(){
                              return [10];
                          }
                      }

Open in new window


Class Inheritance and Implementation
Now comes the fun part -- we add toppings to our pizza by calling each topping class and injecting the previously built pizza object into its constructor.  Each of the concrete "topping" classes extends the abstract class Topping and implements the Pizza interface.  Because these concrete classes (the classes that extend the abstract class) conform to the Pizza interface, they must have two methods, topping() and price().  Because they provide a concrete implementation of the abstract class Topping, they inherit the topping() method from that class, and must provide their own concrete method for the abstract method price().  It is through the use of this concrete price() method that we are able to set the price for each topping individually, and exactly once in the entire code set.

As you can see, our RedSauce Class looks similar to the BasicPizza class, but it does not implement the topping() method -- it inherits the method from the abstract class Topping that it extends.  The other classes that add toppings to the BasicPizza all follow the same general design.  After RedSauce, we can include more classes of the same design.  Note also, that red sauce is free (the price is zero), whereas the basic pizza and the other toppings have an associated price.
 
Class RedSauce extends Topping implements Pizza
                      {
                          public function __construct(Pizza $pie){
                              $this->Pizza = $pie;
                          }
                          public function price(){
                              return array_merge($this->Pizza->price(), [0]);
                          }
                      }

Open in new window


To wrap everything up and complete our order, we have the CheckOut class.  It collapses the array of toppings we have built and adds up the total price for the pizza.  These data points are available in two method calls on the object.
 
Class Checkout
                      {
                          public function __construct(Pizza $pie){
                              $this->Pizza = $pie;
                          }
                          public function totalPrice(){
                              return array_sum($this->Pizza->price());
                          }
                          public function allToppings(){
                              return implode(', ', $this->Pizza->topping());
                          }
                      }

Open in new window


Using our Decorator Pattern in PHP Code
Now that we have all of the essential parts defined, we can call for a pizza with the single line of code shown below.  You read the classes from right to left, since this is how they are encapsulated in parentheses, and how the references will be resolved by PHP.
 
$pizza = new Checkout(new Mushrooms(new Pepperoni(new RedSauce(new BasicPizza))));
                      // OUTPUT: For 15.00, you can get a pizza with Delicious Crust, RedSauce, Pepperoni, Mushrooms

Open in new window


If you're like me, that expression feels a little awkward, so I prefer to write this code out on separate lines, like this.  The result is going to be correct either way.
 
$pizza = new BasicPizza;
                      $pizza = new RedSauce($pizza);
                      $pizza = new Pepperoni($pizza);
                      $pizza = new Mushrooms($pizza);
                      $pizza = new Mozzarella($pizza);
                      $pizza = new Checkout($pizza);
                      // OUTPUT: For 16.50, you can get a pizza with Delicious Crust, RedSauce, etc...

Open in new window


Using the Decorator Pattern with HTML Forms and PHP Variables
There is another advantage of writing the class instantiations on separate lines.  We can use a variable name to identify the classes.  This means that our design can play well with an HTML form, and our clients can order pizza over the web!  Here is the complete script, allowing for us to receive a pizza order with the client's choice of toppings.  Obviously, you would add security measures to something like this before you released it into the wild, but even without that, it demonstrates the design pattern.  You can see it in action here:
http://iconoun.com/demo/formdecorator.php
 
<?php // formdecorator.php
                      
                      /**
                       * Demonstrate a run-time decorator pattern
                       * See https://laracasts.com/lessons/the-decorator-pattern
                       */
                      error_reporting(E_ALL);
                      echo '<pre>';
                      
                      
                      // OUR BASIC INTERFACE
                      Interface Pizza
                      {
                          public function topping();
                          public function price();
                      }
                      
                      
                      // OUR ABSTRACT CLASS
                      Abstract Class Topping
                      {
                          public function topping(){
                              return array_merge($this->Pizza->topping(), [get_class($this)]);
                          }
                      
                          abstract public function price();
                      }
                      
                      
                      // OUR BASIC CLASS
                      Class BasicPizza implements Pizza
                      {
                          public function topping(){
                              return ['Delicious Crust'];
                          }
                      
                          public function price(){
                              return [10];
                          }
                      }
                      
                      
                      // OUR TOPPING CLASSES
                      Class RedSauce extends Topping implements Pizza
                      {
                          public function __construct(Pizza $pie){
                              $this->Pizza = $pie;
                          }
                          public function price(){
                              return array_merge($this->Pizza->price(), [0]);
                          }
                      }
                      
                      Class Pepperoni extends Topping implements Pizza
                      {
                          public function __construct(Pizza $pie){
                              $this->Pizza = $pie;
                          }
                          public function price(){
                              return array_merge($this->Pizza->price(), [3]);
                          }
                      }
                      
                      Class Mushrooms extends Topping implements Pizza
                      {
                          public function __construct(Pizza $pie){
                              $this->Pizza = $pie;
                          }
                          public function price(){
                              return array_merge($this->Pizza->price(), [2]);
                          }
                      }
                      
                      Class Mozzarella extends Topping implements Pizza
                      {
                          public function __construct(Pizza $pie){
                              $this->Pizza = $pie;
                          }
                          public function price(){
                              return array_merge($this->Pizza->price(), [1.5]);
                          }
                      }
                      
                      
                      // OUR FINAL CLASS
                      Class Checkout
                      {
                          public function __construct(Pizza $pie){
                              $this->Pizza = $pie;
                          }
                          public function totalPrice(){
                              return array_sum($this->Pizza->price());
                          }
                          public function allToppings(){
                              return implode(', ', $this->Pizza->topping());
                          }
                      }
                      
                      
                      // DO WE HAVE A POST-METHOD REQUEST?
                      if (!empty($_POST['toppings']))
                      {
                          // START OUR PIZZA
                          $pizza = new BasicPizza;
                      
                          // ADD OUR TOPPINGS
                          foreach ($_POST['toppings'] as $topping => $nothing)
                          {
                              $pizza = new $topping($pizza);
                          }
                          // FINISH OUR PIZZA AND REPORT
                          $pizza = new Checkout($pizza);
                      
                          $p = number_format( $pizza->totalPrice(), 2 );
                          $t = $pizza->allToppings();
                          echo "For $p, you can get a pizza with $t" . PHP_EOL;
                      }
                      
                      
                      // CREATE THE FORM TO ORDER A PIZZA
                      $form = <<<EOD
                      <form method="post">
                      Our Pizzas all have delicious crust!  Choose your toppings here:
                      <input type="checkbox" name="toppings[RedSauce]"   />Red Sauce
                      <input type="checkbox" name="toppings[Pepperoni]"  />Pepperoni
                      <input type="checkbox" name="toppings[Mushrooms]"  />Mushrooms
                      <input type="checkbox" name="toppings[Mozzarella]" />Mozzarella
                      <input type="submit" value="Order Now!" />
                      </form>
                      EOD;
                      
                      echo $form;

Open in new window


Summary
In this article we have shown the risks that arise when a great many configuration options exist in a system, and we have shown how the Decorator Pattern can be used in PHP to give us a clean, maintainable code set for dealing with what would otherwise be great complexity.

Please give us your feedback!
If you found this article helpful, please click the "thumb's up" button below. Doing so lets the E-E community know what is valuable for E-E members and helps provide direction for future articles.  If you have questions or comments, please add them.  Thanks!
 
5
4,567 Views

Comments (4)

Glen GibbOwner

Commented:
Genius!
CERTIFIED EXPERT
Most Valuable Expert 2012

Commented:
Ray, excellent article (of course from you, I would expect nothing less). However, I do have a question: typically, if I were to solve this problem I would only have to use two classes: a pizza class, and a topping class. In the pizza class, I would have a property which was an array that would have a list of toppings for each pizza. Obviously, each topping class would have things like price, cost of good sold, name, etc...

 thus, in order to accommodate the 5.5 million possible combinations of the pricing and data associated with age, all I have to do is add a top into each pizza (much like you do in real life). What is the advantage of using the decorator pattern over this model? Or, is there another problem model which the decorator pattern will consistently fulfill, but my model will not?
Most Valuable Expert 2011
Author of the Year 2014

Author

Commented:
Hi, Michael.  I think one advantage of separate topping classes goes to automated testability.  Another goes to the Single Responsibility Principle, which says that an object should have one and only one reason to change, and to the Interface Segregation Principle which says that an object should not be forced to depend on information or methods they do not use.  As I spend more and more time in OOP design patterns, I find myself writing more classes and smaller classes.  If I want to change the price of a topping, I change exactly one class, rather than a method of a larger class.  The isolation of the change reduces the risk of unwanted side effects.

That's not to say there is a right or wrong answer to how we structure a pizza; just that the SOLID principles have merit that can and should be considered in application design. :-)
CERTIFIED EXPERT
Most Valuable Expert 2012

Commented:
So, what would this code look like in terms of the decorator pattern?

class topping {
	var $name = '';
	var $price = '';

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

class pizza {
	var $toppings = '';
	var $size = ''
	var $base_price = 0;

	function __construct($size,$price) {
		$this->size = $size;
		$this->base_price = $price
		$this->toppings = array();
	}

	function add_toping($topping) {
		array_push($this->toppings,$topping);
	}

	function calculate_price() {
		$total = 0;
		foreach($this->toppings as $topping) {
			$total += $topping->price;
		}

		return $this->base_price + $total;
	}
}

$p = new pizza('L',20);

$t = new topping('pepperoni',0.50);
$p->add_topping($t);

$p->add_topping($t)
$t = new topping('ham',0.50);

echo $p->calculate_price();

Open in new window

Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.