Паттерн Компоновщик в PHP
Короткий скринкаст: паттерн «Компоновщик» (Composite) — листья и контейнеры в одном дереве, единый вызов render() обходит всё.
Описание
Компоновщик строит деревья из двух видов узлов: листья (не имеют детей) и контейнеры (вложенные). И то и другое реализует один интерфейс Node, поэтому код работает с деревом единообразно. Мы моделируем файловую систему: File — лист, Folder — контейнер, а внутри Folder могут лежать и файлы, и другие папки. Вызов render() у корня печатает всё дерево рекурсивно.
UML-диаграмма
Код
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() и одинаково сериализуются, будь то один элемент или их коллекция.