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.75Each 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
EasyLet'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 aPizzainterface that establishes the contract for all pizzas and toppings. It should have two methods:getDescription(): stringandgetPrice(): float.BasePizza.php— Include the Pizza interface and create aMargheritaPizzaclass that implements it. This is your base pizza with a description of"Margherita"and a price of8.00.Toppings.php— Include the Pizza interface and create three decorator classes that implement it. Each decorator wraps aPizzaobject (accept it in the constructor) and enhances both the description and price:CheeseTopping— Adds", Extra Cheese"to the description and1.50to the pricePepperoniTopping— Adds", Pepperoni"to the description and2.00to the priceMushroomTopping— Adds", Mushrooms"to the description and1.25to 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 withCheeseTopping"pepperoni"wraps withPepperoniTopping"mushroom"wraps withMushroomTopping
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()
?>This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of OOP
External FilesIntroduction to OOPClasses vs ObjectsThe $this KeywordMethodsPropertiesConstructor (__construct)Destructor (__destruct)Recap - Simple Calculator4Inheritance
Basic InheritanceThe parent:: KeywordMethod OverridingThe final KeywordAbstract ClassesRecap - Employee Hierarchy7Encapsulation
Public, Protected, PrivateAccess Modifiers in DepthGetters and SettersInformation HidingConstructor Promotion (8.0)Recap - Student Records System10Advanced OOP Concepts
Composition vs InheritanceDependency InjectionAnonymous ClassesEnums (PHP 8.1)Fibers (PHP 8.1)Object Cloning Deep DiveGenerators & Iterators13Design Patterns Part 2
Command PatternAdapter PatternDecorator PatternTemplate Method PatternState PatternComposite PatternRepository Pattern2Namespaces & Autoloading
Introduction to NamespacesThe use KeywordPSR-4 Autoloading StandardComposer AutoloaderRecap - Organized Project5Interfaces & Contracts
Introduction to InterfacesImplementing InterfacesMultiple Interface ImplementInterface vs Abstract ClassType Hinting with InterfacesRecap - Shape Calculator8Magic Methods
Magic Methods Introduction__toString & __debugInfo__get, __set, __isset, __unset__call & __callStatic__clone & Object Cloning__serialize & __unserializeRecap - Custom Collection11Type System & Error Handling
Type DeclarationsNullable TypesUnion & Intersection TypesException ClassesCustom Exception HierarchyTry, Catch, FinallyRecap - Form Validator14Project: Library Management
Project OverviewBook and User Classes3Class Properties
Instance vs Static PropertiesConstants in ClassesStatic Methods & PropertiesPrivate & Protected PropertiesReadonly Properties (PHP 8.1)Recap - Bank Account Manager6Polymorphism
Method Overriding RevisitedPolymorphism via InterfacesType Hinting & Union TypesLate Static BindingRecap - Payment Processor9Traits
Introduction to TraitsUsing Multiple TraitsTrait Conflict ResolutionAbstract Methods in TraitsTraits vs Inheritance12Design Patterns Part 1
Intro to Design PatternsSingleton PatternFactory PatternObserver PatternStrategy Pattern