PHP Decorator Pattern
A short screencast: the Decorator pattern in PHP — wrap an object and add behavior on the fly. A notification chain: email → sms → slack.
Overview
The Decorator lets you dynamically add responsibilities to an object without modifying its class. We define a common NotifierInterface, a base EmailNotifier, and an abstract NotifierDecorator that holds the wrapped object. Concrete decorators SmsDecorator and SlackDecorator extend the behavior by wrapping each other — forming a call chain.
UML diagram
Code
NotifierInterface.php
interface NotifierInterface
{
public function send(string $message): string;
}
EmailNotifier.php
class EmailNotifier implements NotifierInterface
{
public function send(string $message): string
{
return "email: $message";
}
}
NotifierDecorator.php
class NotifierDecorator implements NotifierInterface
{
protected NotifierInterface $wrapped;
public function __construct(NotifierInterface $wrapped)
{
$this->wrapped = $wrapped;
}
public function send(string $message): string
{
return $this->wrapped->send($message);
}
}
SmsDecorator.php
class SmsDecorator extends NotifierDecorator
{
public function send(string $message): string
{
return $this->wrapped->send($message).' | sms: '.$message;
}
}
SlackDecorator.php
class SlackDecorator extends NotifierDecorator
{
public function send(string $message): string
{
return $this->wrapped->send($message).' | slack: '.$message;
}
}
demo.php
require_once 'NotifierInterface.php';
require_once 'EmailNotifier.php';
require_once 'NotifierDecorator.php';
require_once 'SmsDecorator.php';
require_once 'SlackDecorator.php';
$notifier = new SlackDecorator(
new SmsDecorator(
new EmailNotifier()
)
);
echo $notifier->send('order ready').PHP_EOL;
Example output
Running php demo.php in the terminal prints the notification chain:
email: order ready | sms: order ready | slack: order ready
Advantages
- Adds behaviour without subclassing or editing the original class.
- Decorators combine freely and stack in any order.
- Behaviour can be turned on and off at runtime.
- Each decorator is responsible for a single concern.
When to use
- When responsibilities must be added to an object dynamically.
- When inheritance would lead to an explosion of subclasses.
- When the behaviours are independent and combine.
- When the original class cannot be changed (third-party or final).
Usage in Laravel
In Laravel the decorator is the cache repository: Illuminate\Cache\Repository wraps a store (Illuminate\Contracts\Cache\Store) and adds remember(), tags and events on top without touching the driver. Log channels in Illuminate\Log\LogManager and the wrappers around the HTTP client are built the same way: behaviour is layered on.