PHP Decorator Design Pattern Structural Pattern

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.

Decorator Pattern — open the lesson page

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

classDiagram class NotifierInterface { <<interface>> +send(message: string): string } class EmailNotifier { +send(message: string): string } class NotifierDecorator { #wrapped: NotifierInterface +__construct(wrapped: NotifierInterface) +send(message: string): string } class SmsDecorator { +send(message: string): string } class SlackDecorator { +send(message: string): string } NotifierInterface <|-- EmailNotifier : implements NotifierInterface <|-- NotifierDecorator : implements NotifierDecorator <|-- SmsDecorator : extends NotifierDecorator <|-- SlackDecorator : extends NotifierDecorator o-- NotifierInterface : wrapped

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.