Example 1

Use case: We want to make a pizza with toppings. A pizza can have no toppings which can assume it will be the base pizza (after asking the pizza place owner who wants this feature). 

<?php


interface PizzaMaker
{
    public function getToppings(): array;
}


class BasePizza implements PizzaMaker
{
    private $toppings = [];

    public function getToppings(): array
    {
        return $this->toppings;
    }
}

Our base pizza class has no toppings as expected. Now it's time to enhance it with this kind of functionality.

<?php

abstract class BasePizzaDecorator implements PizzaMaker
{

    protected $maker;

    public function __construct(PizzaMaker $maker)
    {
        $this->maker = $maker;
    }

}


class CheeseTopping extends BasePizzaDecorator
{
    public function getToppings(): array
    {
        return array_merge($this->maker->getToppings(), ['Cheese']);
    }

}


class SalamiTopping extends BasePizzaDecorator
{
    public function getToppings(): array
    {
        return array_merge($this->maker->getToppings(), ['Salami']);
    }
}

Now our toppings classes are in place and we can composite our pizza.

<?php

$pizza = new BasePizza();
$pizza = new CheeseTopping($pizza);
$pizza = new SalamiTopping($pizza);

print_r($pizza->getToppings());

Toppings
// (
//     [0] => Cheese
//     [1] => Salami
// )