PHP Chain of Responsibility Design Pattern Поведенческий паттерн

Паттерн Цепочка обязанностей (Chain of Responsibility) в PHP

Короткий скринкаст: паттерн «Цепочка обязанностей» (Chain of Responsibility) — передаём запрос по цепочке обработчиков, пока один из них его не обработает. Согласуем заявку у тимлида и менеджера.

Chain Of Responsibility Pattern — открыть страницу урока

Описание

Цепочка обязанностей позволяет передавать запрос последовательно по цепочке обработчиков, пока один из них его не обработает. Заявку на сумму сначала получает тимлид: до 1000 он согласует её сам, иначе передаёт менеджеру. Менеджер согласует до 5000, а более крупную сумму дальше передавать некому — вернётся null. Клиент вызывает только первый обработчик и не знает, кто именно ответит.

UML-диаграмма

classDiagram class Handler { <<interface>> +setNext(next: Handler): Handler +handle(amount: int): string } class AbstractHandler { -next: Handler +setNext(next: Handler): Handler +handle(amount: int): string } class TeamLead { +handle(amount: int): string } class Manager { +handle(amount: int): string } Handler <|-- AbstractHandler : implements AbstractHandler <|-- TeamLead : extends AbstractHandler <|-- Manager : extends AbstractHandler o-- Handler : next

Код

Handler.php

interface Handler
{
    public function setNext(Handler $next): Handler;

    public function handle(int $amount): ?string;
}

AbstractHandler.php

abstract class AbstractHandler implements Handler
{
    private ?Handler $next = null;

    public function setNext(Handler $next): Handler
    {
        $this->next = $next;

        return $next;
    }

    public function handle(int $amount): ?string
    {
        return $this->next?->handle($amount);
    }
}

TeamLead.php

class TeamLead extends AbstractHandler
{
    public function handle(int $amount): ?string
    {
        if ($amount <= 1000) {
            return 'Team lead approved '.$amount;
        }

        return parent::handle($amount);
    }
}

Manager.php

class Manager extends AbstractHandler
{
    public function handle(int $amount): ?string
    {
        if ($amount <= 5000) {
            return 'Manager approved '.$amount;
        }

        return parent::handle($amount);
    }
}

demo.php

require_once 'Handler.php';
require_once 'AbstractHandler.php';
require_once 'TeamLead.php';
require_once 'Manager.php';

$teamLead = new TeamLead;
$manager = new Manager;

$teamLead->setNext($manager);

echo $teamLead->handle(500).PHP_EOL;
echo $teamLead->handle(3000).PHP_EOL;
var_dump($teamLead->handle(99999));

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

После php demo.php видно, как запрос идёт по цепочке: 500 согласует тимлид, 3000 — уже менеджер, а для 99999 подходящего обработчика нет и возвращается NULL:

Team lead approved 500
Manager approved 3000
NULL

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

  • Отправитель не знает, какой обработчик в итоге обработает запрос.
  • Обработчики можно добавлять, убирать и переупорядочивать, не меняя клиента.
  • Каждый обработчик отвечает за одно условие (принцип единственной обязанности).
  • Цепочка собирается динамически и может отличаться в разных сценариях.

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

  • Когда есть несколько кандидатов на обработку запроса и заранее неизвестно, кто сработает.
  • Когда обработчики должны выстраиваться в цепочку и передавать запрос дальше.
  • Когда набор и порядок обработчиков меняется во время выполнения.
  • Когда важно разделить ответственность между отдельными обработчиками.

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

В Laravel Цепочка обязанностей — это HTTP-middleware: Illuminate\Pipeline\Pipeline прогоняет запрос через слой app/Http/Middleware/*, и каждое звено либо продолжает цепочку, либо прерывает её. Тот же конвейер используется в маршрутизаторах и обработке задач, а собранный стек middleware ядра (Illuminate\Foundation\Http\Kernel) — та самая цепочка, по которой проходит каждый запрос.