Цепочка обязанностей (chain of responsibility). Шаблоны проектирования.

Шаблон проектирования цепочка обязанностей используется для организации набора объектов последовательно обрабатывающих запрос. Например, в веб-разработке этот шаблон часто используется как для создания middleware, так и для разделения обработчиков на отдельные объекты.

Разница только в том, что каждый объект цепочки middleware’ов проверяет запрос, а каждый элемент цепочки обработчиков смотрит может ли он обработать запрос (если может пропускает, если не может, то вызывает следующий элемент цепочки). Под «запросом» понимается не только HTTP-запрос, но и любое другое сообщение или команда, которую нужно выполнить, например, логирование исключений, отрисовка изображений и т.д.

<?php
 
namespace ITReview;
 
/**
 * Промежуточный скрипт.
 *
 * @package ITReview
 */
abstract class Middleware
{
    /**
     * @var Middleware $next Следующий промежуточный скрипт.
     */
    private Middleware $next;
 
    /**
     * Связывание.
     *
     * @param  Middleware  $middleware  Промежуточный скрипт.
     */
    public function link(Middleware $middleware)
    {
        $this->next = $middleware;
    }
 
    /**
     * Обработка.
     *
     * @param  array  $request  Запрос с данными.
     *
     * @return bool
     */
    public function work(array $request): bool
    {
        if (!$this->check($request)) {
            return false;
        }
 
        if (!empty($this->next)) {
            return $this->next->work($request);
        }
 
        return true;
    }
 
    /**
     * Проверка.
     *
     * @param  array  $request  Запрос с данными.
     *
     * @return bool
     */
    abstract protected function check(array $request): bool;
}
 
/**
 * Проверка возраста.
 *
 * @package ITReview
 */
final class AgeMiddleware extends Middleware
{
    /**
     * {@inheritDoc}
     */
    protected function check(array $request): bool
    {
        if (isset($request['age']) && $request['age'] > 20) {
            echo 'Валидация возраста пройдена.' . PHP_EOL;
 
            return true;
        }
 
        echo 'Валидация возраста НЕ пройдена.' . PHP_EOL;
 
        return false;
    }
}
 
/**
 * Проверка страны.
 *
 * @package ITReview
 */
final class CountryMiddleware extends Middleware
{
    /**
     * {@inheritDoc}
     */
    protected function check(array $request): bool
    {
        if (isset($request['country']) && $request['country'] == 'Poland') {
            echo 'Валидация страны пройдена.' . PHP_EOL;
 
            return true;
        }
 
        echo 'Валидация страны НЕ пройдена.' . PHP_EOL;
 
        return false;
    }
}
 
/**
 * Проверка страны.
 *
 * @package ITReview
 */
final class NameMiddleware extends Middleware
{
    /**
     * {@inheritDoc}
     */
    protected function check(array $request): bool
    {
        if (isset($request['name']) && $request['name'] == 'John') {
            echo 'Валидация имени пройдена.' . PHP_EOL;
 
            return true;
        }
 
        echo 'Валидация имени НЕ пройдена.' . PHP_EOL;
 
        return false;
    }
}
 
/**
 * Обработчик.
 *
 * @package ITReview
 */
abstract class Handler
{
    /**
     * @var Handler $next Следующий обработчик.
     */
    private Handler $next;
 
    /**
     * Связывание.
     *
     * @param  Handler  $handler  Обработчик.
     */
    public function link(Handler $handler)
    {
        $this->next = $handler;
    }
 
    /**
     * Обработка.
     *
     * @param  array  $request  Запрос.
     */
    public function work(array $request): void
    {
        if ($this->check($request)) {
            $this->process($request);
        }
 
        if (!empty($this->next)) {
            $this->next->work($request);
        }
    }
 
    /**
     * Проверка возможности обработки.
     *
     * @param  array  $request  Запрос.
     *
     * @return bool
     */
    abstract protected function check(array $request): bool;
 
    /**
     * Обработка запроса.
     *
     * @param  array  $request  Запрос.
     */
    abstract protected function process(array $request): void;
}
 
/**
 * Обработчик платежей QIWI.
 *
 * @package ITReview
 */
final class QiwiHandler extends Handler
{
    /**
     * {@inheritDoc}
     */
    protected function check(array $request): bool
    {
        return isset($request['payment']) && $request['payment'] == 'QIWI';
    }
 
    /**
     * {@inheritDoc}
     */
    protected function process(array $request): void
    {
        echo 'Обработка запроса оплаты через QIWI.' . PHP_EOL;
 
        exit;
    }
}
 
/**
 * Обработчик платежей Sberbank.
 *
 * @package ITReview
 */
final class SberbankHandler extends Handler
{
    /**
     * {@inheritDoc}
     */
    protected function check(array $request): bool
    {
        return isset($request['payment']) && $request['payment'] == 'Sberbank';
    }
 
    /**
     * {@inheritDoc}
     */
    protected function process(array $request): void
    {
        echo 'Обработка запроса оплаты через Sberbank.' . PHP_EOL;
 
        exit;
    }
}
 
/**
 * Приложение.
 *
 * @package ITReview
 */
final class Application
{
    /**
     * @var array $request Запрос.
     */
    private array $request;
    /**
     * @var Middleware $middleware Промежуточный скрипт.
     */
    private Middleware $middleware;
    /**
     * @var Handler $handler Обработчик.
     */
    private Handler $handler;
 
    /**
     * Конструктор.
     *
     * @param  array  $request  Запрос.
     */
    public function __construct(array $request = [])
    {
        $this->request = $request;
    }
 
    /**
     * Добавление промежуточных скриптов.
     *
     * @param  mixed  ...$middleware  Промежуточные скрипты.
     */
    public function addMiddleware(...$middleware): void
    {
        if (empty($middleware)) {
            return;
        }
 
        foreach ($middleware as $currentMiddleware) {
            if (!($currentMiddleware instanceof Middleware)) {
                echo 'Ошибка создания цепочки Middleware.' . PHP_EOL;
                exit;
            }
        }
 
        $this->middleware = $middleware[0];
 
        for ($i = 0; $i < count($middleware); $i++) {
            if ($i == 0) {
                continue;
            }
 
            $middleware[$i - 1]->link($middleware[$i]);
        }
    }
 
    /**
     * Добавление обрабочтиков.
     *
     * @param  mixed  ...$handlers  Обработчики.
     */
    public function addHandlers(...$handlers): void
    {
        if (empty($handlers)) {
            return;
        }
 
        foreach ($handlers as $handler) {
            if (!($handler instanceof Handler)) {
                echo 'Ошибка создания цепочки Handlers.' . PHP_EOL;
                exit;
            }
        }
 
        $this->handler = $handlers[0];
 
        for ($i = 0; $i < count($handlers); $i++) {
            if ($i == 0) {
                continue;
            }
 
            $handlers[$i - 1]->link($handlers[$i]);
        }
    }
 
    /**
     * Обработка.
     */
    public function handle(): void
    {
        if (!empty($this->middleware) && !$this->middleware->work($this->request)) {
            echo 'Запрос не дошел до обработчика.' . PHP_EOL;
            exit;
        }
 
        if (!empty($this->handler)) {
            $this->handler->work($this->request);
        }
 
        echo 'Запрос не обработан, т.к. обработчика не нашлось.' . PHP_EOL;
    }
}
 
$result = [
    'name' => 'John',
    'country' => 'Poland',
    'age' => '25',
    'payment' => 'QIWI',
];
 
$application = new Application($result);
 
$application->addMiddleware(
    new AgeMiddleware(),
    new CountryMiddleware(),
    new NameMiddleware()
);
 
$application->addHandlers(
    new QiwiHandler(),
    new SberbankHandler()
);
 
$application->handle();

Полезные ссылки

Добавить комментарий