PHP Composite Design Pattern Структурный паттерн

Паттерн Компоновщик в PHP

Короткий скринкаст: паттерн «Компоновщик» (Composite) — листья и контейнеры в одном дереве, единый вызов render() обходит всё.

Composite Pattern — открыть страницу урока

Описание

Компоновщик строит деревья из двух видов узлов: листья (не имеют детей) и контейнеры (вложенные). И то и другое реализует один интерфейс Node, поэтому код работает с деревом единообразно. Мы моделируем файловую систему: File — лист, Folder — контейнер, а внутри Folder могут лежать и файлы, и другие папки. Вызов render() у корня печатает всё дерево рекурсивно.

UML-диаграмма

classDiagram class Node { <<interface>> +name() string +render(depth) string } class File { -string name -int size +render(depth) string } class Folder { -Node[] children +add(Node) void +render(depth) string } Node <|-- File : implements Node <|-- Folder : implements Folder o-- Node : children

Код

Node.php

interface Node
{
    public function name(): string;

    public function render(int $depth = 0): string;
}

File.php

class File implements Node
{
    public function __construct(
        private string $name,
        private int $size,
    ) {}

    public function name(): string
    {
        return $this->name;
    }

    public function render(int $depth = 0): string
    {
        return $this->name.' ['.$this->size.']';
    }
}

Folder.php

class Folder implements Node
{
    private array $children = [];

    public function __construct(private string $name) {}

    public function name(): string
    {
        return $this->name;
    }

    public function add(Node $child): void
    {
        $this->children[] = $child;
    }

    public function render(int $depth = 0): string
    {
        $out = $this->name.'/';

        foreach ($this->children as $child) {
            $out .= "\n".str_repeat('  ', $depth + 1).$child->render($depth + 1);
        }

        return $out;
    }
}

demo.php

require_once 'Node.php';
require_once 'File.php';
require_once 'Folder.php';

$root = new Folder('src');
$root->add(new File('index.php', 1024));
$root->add(new File('README.md', 512));

$utils = new Folder('Utils');
$utils->add(new File('Helper.php', 2048));
$utils->add(new Folder('Templates'));
$utils->add(new File('mail.php', 32));

$root->add($utils);

echo $root->render().PHP_EOL;

Пример результата

После php demo.php в терминале появится дерево:

src/
  index.php [1024]
  README.md [512]
  Utils/
    Helper.php [2048]
    Templates/
    mail.php [32]

Преимущества

  • Позволяет единообразно работать с отдельным объектом и их группой.
  • Рекурсивная композиция: дерево собирается из листьев и контейнеров.
  • Новые виды компонентов добавляются без правок клиента.
  • Клиентский код прост: один вызов обходит всё дерево.

Когда использовать

  • Когда есть иерархия «часть — целое».
  • Когда клиент должен одинаково трактовать листья и контейнеры.
  • Когда структура вложенности заранее неизвестна.
  • Когда нужно рекурсивно обходить составные структуры.

Использование в Laravel

В Laravel Компоновщик — это Blade-компоненты и слоты: Illuminate\View\Component и Illuminate\View\ComponentSlot позволяют вкладывать компоненты друг в друга, а рендерятся они единообразно. Другой пример — ресурсы API: Illuminate\Http\Resources\Json\JsonResource и ResourceCollection имеют общий метод toArray() и одинаково сериализуются, будь то один элемент или их коллекция.