Menu
Coddy logo textTech

Decorator Pattern

Part of the Object Oriented Programming section of Coddy's PHP journey — lesson 77 of 91.

The Decorator Pattern is a structural design pattern that lets you dynamically add new behaviors to objects by wrapping them in special wrapper objects called decorators. Unlike inheritance, which adds behavior at compile time, decorators add behavior at runtime.

Imagine a coffee shop. You start with a basic coffee, then add extras like milk, sugar, or whipped cream.

Each addition wraps the original coffee and adds to its description and cost. The key insight is that both the base coffee and the decorators share the same interface.

<?php
interface Coffee {
    public function getDescription(): string;
    public function getCost(): float;
}

class SimpleCoffee implements Coffee {
    public function getDescription(): string {
        return "Simple Coffee";
    }
    
    public function getCost(): float {
        return 2.00;
    }
}

Decorators implement the same interface and wrap another Coffee object:

<?php
class MilkDecorator implements Coffee {
    public function __construct(private Coffee $coffee) {}
    
    public function getDescription(): string {
        return $this->coffee->getDescription() . ", Milk";
    }
    
    public function getCost(): float {
        return $this->coffee->getCost() + 0.50;
    }
}

class SugarDecorator implements Coffee {
    public function __construct(private Coffee $coffee) {}
    
    public function getDescription(): string {
        return $this->coffee->getDescription() . ", Sugar";
    }
    
    public function getCost(): float {
        return $this->coffee->getCost() + 0.25;
    }
}

$coffee = new SimpleCoffee();
$coffee = new MilkDecorator($coffee);
$coffee = new SugarDecorator($coffee);

echo $coffee->getDescription() . " = $" . $coffee->getCost();

Output:

Simple Coffee, Milk, Sugar = $2.75

Each decorator wraps the previous object, forming a chain. The pattern allows you to combine behaviors in any order and quantity without creating a class for every possible combination. Need double milk? Just wrap it twice.

challenge icon

Challenge

Easy

Let's build a pizza ordering system using the Decorator Pattern. You'll start with a basic pizza and dynamically add toppings — each topping wraps the pizza and adds to its description and price, just like the coffee example from the lesson.

You'll organize your code across four files:

  • Pizza.php — Define a Pizza interface that establishes the contract for all pizzas and toppings. It should have two methods: getDescription(): string and getPrice(): float.
  • BasePizza.php — Include the Pizza interface and create a MargheritaPizza class that implements it. This is your base pizza with a description of "Margherita" and a price of 8.00.
  • Toppings.php — Include the Pizza interface and create three decorator classes that implement it. Each decorator wraps a Pizza object (accept it in the constructor) and enhances both the description and price:
    • CheeseTopping — Adds ", Extra Cheese" to the description and 1.50 to the price
    • PepperoniTopping — Adds ", Pepperoni" to the description and 2.00 to the price
    • MushroomTopping — Adds ", Mushrooms" to the description and 1.25 to the price
  • main.php — Include the BasePizza and Toppings files. You'll receive one input: a comma-separated list of toppings to add (e.g., "cheese,pepperoni" or "mushroom,cheese,cheese").

    Start with a MargheritaPizza. For each topping in the input, wrap your pizza with the appropriate decorator:

    • "cheese" wraps with CheeseTopping
    • "pepperoni" wraps with PepperoniTopping
    • "mushroom" wraps with MushroomTopping

    After applying all toppings, print the final pizza's description and price in this format:

    [description] = $[price]

    Format the price to two decimal places.

Notice how the same topping can be applied multiple times — ordering double cheese simply means wrapping with CheeseTopping twice. This flexibility is the power of the Decorator Pattern!

Cheat sheet

The Decorator Pattern is a structural design pattern that dynamically adds behaviors to objects by wrapping them in decorator objects at runtime.

Both the base object and decorators implement the same interface:

<?php
interface Coffee {
    public function getDescription(): string;
    public function getCost(): float;
}

class SimpleCoffee implements Coffee {
    public function getDescription(): string {
        return "Simple Coffee";
    }
    
    public function getCost(): float {
        return 2.00;
    }
}

Decorators wrap another object of the same interface type:

<?php
class MilkDecorator implements Coffee {
    public function __construct(private Coffee $coffee) {}
    
    public function getDescription(): string {
        return $this->coffee->getDescription() . ", Milk";
    }
    
    public function getCost(): float {
        return $this->coffee->getCost() + 0.50;
    }
}

class SugarDecorator implements Coffee {
    public function __construct(private Coffee $coffee) {}
    
    public function getDescription(): string {
        return $this->coffee->getDescription() . ", Sugar";
    }
    
    public function getCost(): float {
        return $this->coffee->getCost() + 0.25;
    }
}

Chain decorators to combine behaviors:

$coffee = new SimpleCoffee();
$coffee = new MilkDecorator($coffee);
$coffee = new SugarDecorator($coffee);

echo $coffee->getDescription() . " = $" . $coffee->getCost();
// Output: Simple Coffee, Milk, Sugar = $2.75

Decorators can be applied multiple times and in any order without creating classes for every combination.

Try it yourself

<?php
require_once 'BasePizza.php';
require_once 'Toppings.php';

// Read input - comma-separated list of toppings
$input = trim(fgets(STDIN));
$toppings = explode(',', $input);

// Start with a base MargheritaPizza
$pizza = new MargheritaPizza();

// TODO: Loop through each topping and wrap the pizza with the appropriate decorator
// - "cheese" -> wrap with CheeseTopping
// - "pepperoni" -> wrap with PepperoniTopping
// - "mushroom" -> wrap with MushroomTopping

// TODO: Print the result in format: [description] = $[price]
// Format price to 2 decimal places using number_format()

?>
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming