Design Pattern: Decorator Pattern in PHP

Decorator pattern dynamically attaches additional responsibility to an object. This pattern is used to extend functionality without affecting the existing implementation.

If we just use inheritance, then we can extend the functionality of a class. But by using a decorator we can enhance/extend and/or change the functionality of an object at run time.

This article demonstrates Decorator pattern implementation in PHP. Check the implementation below.

Implementation

Here are the steps for Decorator pattern implementation-

  • Create an interface for the subject/item classes.
  • Create subject/item classes and implement the interface.
  • Create an abstract class as decorator class and implement the same subject interface. Define a property of type of the subject interface and initialize it in the constructor. In the method implementation call the methods from the subject.
  • Create decorator class and extend the abstract decorator. In the method implementation call the method from the parent(which in turn uses the methods from the subject). Then add additional functionality in the method implementation.

Here is a simple example of Decorator pattern implementation in PHP-

<?php
// PHP Decorator Pattern

// Subject
interface Subject {
    function operationOne(): void;
    function operationTwo(): void;
}

// Concrete subject
class ConcreteSubject implements Subject {
    public function operationOne(): void {
        echo "Performing Operation One(1) in Subject\n";
    }

    public function operationTwo(): void {
        echo "Performing Operation Two(2) in Subject\n";
    }
}

// Decorator
abstract class Decorator implements Subject {
    public function __construct(protected Subject $subject) {
    }

    public function operationOne(): void {
        $this->subject->operationOne();
    }

    public function operationTwo(): void {
        $this->subject->operationTwo();
    }
}

// Concrete decorator
class ConcreteDecorator extends Decorator {
    public function __construct(Subject $subject) {
        parent::__construct($subject);
    }

    public function operationOne(): void {
        $this->subject->operationOne();
        echo "Performing additional operation in Concrete Decorator\n";
    }
}


// Demo
$someDecorator = new ConcreteDecorator(new ConcreteSubject());
$someDecorator->operationOne();

Output of the above code will be as below-

Performing Operation One(1) in Subject
Performing additional operation in Concrete Decorator

Examples

Here are some examples of Decorator pattern.

Example #1: Data Export

In this example we have a simple class for data export. We want to add some additional functionality to that.

So, we are creating some decorators for CSV, JSON, and Excel. These will wrap the existing exporter implementation and will add some extra functionality on top of that.

DataExport Interface [Existing]

  • Create file “DataExport.php”.
  • Define interface “DataExport”.
  • Declare method “processData”. There can be other method declaration as per the requirement.
<?php
// DataExport.php

namespace BigBoxCode\DesignPattern\Decorator\DataExport;

interface DataExport {
    function processData(): void;
}

SimpleExport Class [Existing]

  • Create file “SimpleDataExport.php”.
  • Define class “SimpleDataExport”.
  • Implement “DataExport” interface for the class.
<?php
// SimpleDataExport.php

namespace BigBoxCode\DesignPattern\Decorator\DataExport;

class SimpleDataExport implements DataExport {
    public function processData(): void {
        echo "Processing Data\n";
    }
}

Main Decorator Class [Decorator]

  • Create file “DataExportDecorator.php”.
  • Define class “DataExportDecorator”.
  • Implement “DataExport” interface for the class.
  • Define a property “$dataExport”. Accept param in the constructor and set the value.
  • In the “processData” method implementation call the “processData” method from “$dataExport”.
<?php
// DataExportDecorator.php

namespace BigBoxCode\DesignPattern\Decorator\DataExport;


class DataExportDecorator implements DataExport {
    public function __construct(protected DataExport $dataExporter) {
    }

    public function processData(): void {
        $this->dataExporter->processData();
    }
}

CSV Decorator Class [Decorator]

  • Create file “CsvDataExportDecorator.php”.
  • Create class “CsvDataExportDecorator”.
  • Extend the “DataExportDecorator” for the class.
  • In the constructor call the parent constructor and set the “$dataExport” value.
  • In the “processData” method implementation call the “processData” from the parent “$dataExport”. Call additional processing steps in the same method for extra operations. Here we have created a separated method named “processCsv” and called it from “processData”.
<?php
// CsvDataExportDecorator.php

namespace BigBoxCode\DesignPattern\Decorator\DataExport;

class CsvDataExportDecorator extends DataExportDecorator {
    public function __construct(DataExport $dataExporter) {
        parent::__construct($dataExporter);
    }

    public function processData(): void {
        $this->dataExporter->processData();
        $this->processCsv();
    }

    public function processCsv(): void {
        echo "Processed data to CSV\n";
    }
}

Excel Decorator Class [Decorator]

  • Create file “ExcelDataExportDecorator.php”.
  • Create class “ExcelDataExportDecorator” and extend “DataExportDecorator”.
  • In the “processData” method implementation call the “processData” from “$dataExport” and also call additional processing steps for excel processing.
<?php
// ExcelDataExportDecorator.php

namespace BigBoxCode\DesignPattern\Decorator\DataExport;

class ExcelDataExportDecorator extends DataExportDecorator {
    public function __construct(DataExport $dataExporter) {
        parent::__construct($dataExporter);
    }

    public function processData(): void {
        $this->dataExporter->processData();
        $this->processExcel();
    }

    public function processExcel() {
        echo "Processed data to Excel\n";
    }
}

JSON Decorator Class [Decorator]

  • Create file “JsonDataExportDecorator.php”.
  • Create class “JsonDataExportDecorator” and extend “DataExportDecorator” for the class. In the “processData” implementation add additional steps for JSON data processing.
<?php
// JsonDataExportDecorator.php

namespace BigBoxCode\DesignPattern\Decorator\DataExport;

class JsonDataExportDecorator extends DataExportDecorator {
    public function __construct(DataExport $dataExporter) {
        parent::__construct($dataExporter);
    }

    public function processData(): void {
        $this->dataExporter->processData();
        $this->processJson();
    }

    public function processJson(): void {
        echo "Processed data to JSON\n";
    }
}

Demo

To use any specific exporter pass an object of “SimpleDataExport” to the decorator.

<?php
// demo.php

require __DIR__ . '/../../vendor/autoload.php';

use BigBoxCode\DesignPattern\Decorator\DataExport\CsvDataExportDecorator;
use BigBoxCode\DesignPattern\Decorator\DataExport\ExcelDataExportDecorator;
use BigBoxCode\DesignPattern\Decorator\DataExport\JsonDataExportDecorator;
use BigBoxCode\DesignPattern\Decorator\DataExport\SimpleDataExport;

$csvDataExport = new CsvDataExportDecorator(new SimpleDataExport());
$csvDataExport->processData();

$excelDataExport = new ExcelDataExportDecorator(new SimpleDataExport());
$excelDataExport->processData();

$jsonDataExport = new JsonDataExportDecorator(new SimpleDataExport());
$jsonDataExport->processData();

Output

The output of the demo above will be like below.

Processing Data
Processed data to CSV

Processing Data
Processed data to Excel

Processing Data
Processed data to JSON

Example #2: UI Elements

In this example we have some UI elements, like – Button, Input Box, and Table. We want to implement some decorators, like – margin, border, and background.

UI Element Interface

  • Create file “UiElement.php”.
  • Create interface “UIElement”.
  • Declare methods as per requirement. Here we have method – “draw”.
<?php
// UiElement.php

namespace BigBoxCode\DesignPattern\Decorator\UiElement;

interface UIElement {
    function draw(): void;
}

Button UI Element Class

  • Create file “Button.php”.
  • Create class “Button”.
  • Implement “UIElement” interface for the class. Define the “draw” method as part of the implementation.
<?php
// Button.php

namespace BigBoxCode\DesignPattern\Decorator\UiElement;

class Button implements UIElement {
    public function draw(): void {
        echo "Drawing Button\n";
    }
}

Input Box UI Element Class

  • Create file “InputBox.php”.
  • Create class “InputBox” and implement “UIElement” interface for the class.
<?php
// InputBox.php

namespace BigBoxCode\DesignPattern\Decorator\UiElement;

class InputBox implements UIElement {
    public function draw(): void {
        echo "Drawing Input Box\n";
    }
}

Table UI Element Class

  • Create file “Table.php”.
  • Create class “Table”. Implement “UIElement” for the interface.
<?php
// Table.php

namespace BigBoxCode\DesignPattern\Decorator\UiElement;

class Table implements UIElement {
    public function draw(): void {
        echo "Drawing Table\n";
    }
}

Abstract UI Decorator [Decorator]

  • Create file “UiDecorator.php”.
  • Create class “UIDecorator”.
  • Implement “UIElement” interface for the class.
  • Define a protected and readony property of type “UIElement” in the class. Here we have named it “$uiElement”. This will be used to store a reference to a UI element class object.
  • In the constructor accept param and set “$uiElement”.
  • In the “draw” method implementation call the “draw” method from “$uiElement”.
<?php
// UiDecorator.php

namespace BigBoxCode\DesignPattern\Decorator\UiElement;

abstract class UIDecorator implements UIElement {
    protected function __construct(protected readonly UIElement $uiElement) {
    }

    public function draw(): void {
        $this->uiElement->draw();
    }
}

Border Decorator Class [Decorator]

  • Create file “BorderDecorator.php”.
  • Create class “BorderDecorator”.
  • Extend the abstract decorator “UIDecorator”.
  • In the constructor accept “$uiElement” and pass that to the parent constructor.
  • In the “draw” method implementation call the “draw” from the parent(which calls the “draw” method from $uiElement”). Then add additional functionality as per the new requirement.
<?php
// BorderDecorator.php

namespace BigBoxCode\DesignPattern\Decorator\UiElement;

class BorderDecorator extends UIDecorator {
    public function __construct(UIElement $uiElement) {
        parent::__construct($uiElement);
    }

    public function draw(): void {
        parent::draw();

        echo "Adding Border to the element\n";
    }
}

Background Decorator Class [Decorator]

  • Create file “BackgroundDecorator.php”.
  • Create class “BackgroundDecorator” and extend the “UIDecorator”.
<?php
// BackgroundDecorator.php

namespace BigBoxCode\DesignPattern\Decorator\UiElement;

class BackgroundDecorator extends UIDecorator {
    public function __construct(UIElement $uiElement) {
        parent::__construct($uiElement);
    }

    public function draw(): void {
        parent::draw();
        
        echo "Adding Background to the element\n";
    }
}

Margin Decorator Class [Decorator]

  • Create file “MarginDecorator.php”.
  • Define class “BackgroundDecorator” and extend the “UIDecorator”.
<?php
// MarginDecorator.php

namespace BigBoxCode\DesignPattern\Decorator\UiElement;

class MarginDecorator extends UIDecorator {
    public function __construct(UIElement $uiElement) {
        parent::__construct($uiElement);
    }

    public function draw(): void {
        parent::draw();
        
        echo "Adding margin to the element\n";
    }
}

Demo

In the client, we can pass the UI element object to any decorator to implement the decorator for the element.

Multiple decorators can be used for the same element by chaining the call of the decorators.

<?php
// demo.php

require __DIR__ . '/../../vendor/autoload.php';

use BigBoxCode\DesignPattern\Decorator\UiElement\BackgroundDecorator;
use BigBoxCode\DesignPattern\Decorator\UiElement\BorderDecorator;
use BigBoxCode\DesignPattern\Decorator\UiElement\Button;
use BigBoxCode\DesignPattern\Decorator\UiElement\InputBox;
use BigBoxCode\DesignPattern\Decorator\UiElement\MarginDecorator;
use BigBoxCode\DesignPattern\Decorator\UiElement\Table;


$tableWithBorder = new BorderDecorator(new Table());
$tableWithBorder->draw();

$inputWithBorderAndBackground = new BackgroundDecorator(new BorderDecorator(new InputBox()));
$inputWithBorderAndBackground->draw();

$buttonWithAllDecorator = new MarginDecorator(new BackgroundDecorator(new BorderDecorator(new Button())));
$buttonWithAllDecorator->draw();

Output

Output of the demo code will be as below-

Drawing Table
Adding Border to the element


Drawing Input Box
Adding Border to the element
Adding Background to the element


Drawing Button
Adding Border to the element
Adding Background to the element
Adding margin to the element

Source Code

Use the following link to get the source code:

Other Code Implementations

Use the following links to check Decorator pattern implementation in other programming languages.

Leave a Comment


The reCAPTCHA verification period has expired. Please reload the page.