Реализация паттерна Chain of Responsibility на примере котиков в PHP

eb9224d8c8d9927b7b836c97b58dd6eb.png

Привет, Хабр!

Если вы когда‑нибудь пытались настроить бизнес‑логику в своём проекте так, чтобы она не выглядела как свалка if-else и работала хорошо, то этот материал для вас. Сегодня мы разберём один из самых приятных паттернов — Chain of Responsibility, или «Цепочка обязанностей».

Вместо кучи условий, которые как минимум трудно читать, а как максимум невозможно поддерживать, мы строим гибкую архитектуру. Каждый этап обработки запроса становится отдельным модулем. А благодаря возможности менять порядок этих модулей или добавлять новые, система легко масштабируется.

Используйте Chain of Responsibility, когда:

  1. Логика обработки запроса должна быть модульной.

  2. Нужно динамически менять последовательность обработки.

  3. Вы хотите облегчить добавление новых обработчиков.

Сразу перейдем к коду

Реализация паттерна на примере магазина котиков

Архитектура магазина котиков

Вот как будет выглядеть наш процесс:

  1. Проверка наличия товара.

  2. Проверка возраста покупателя.

  3. Проверка оплаты.

  4. Упаковка заказа.

Каждое из этих действий — это отдельный обработчик в цепочке.

Интерфейс обработчика

Начнём с базового интерфейса, который будут реализовывать наши обработчики.

nextHandler = $handler;
        return $handler;
    }

    public function handle(array $request): ?array
    {
        if ($this->nextHandler) {
            return $this->nextHandler->handle($request);
        }

        return $request;
    }
}

Интерфейс HandlerInterface определяет контракт для всех обработчиков, а базовый класс AbstractHandler реализует передачу запроса следующему обработчику.

Обработчики

Теперь создадим обработчики для проверки заказа. Начнем с проверки наличия заказа:

Теперь реализуем проверку возраста покупателя:

Проверка оплаты:

Упаковка и подготовка к доставке:

Сборка цепочки

Теперь объединим все обработчики в цепочку.

 5,
    'age' => 25,
    'payment' => true,
    'payment_id' => 'PAY12345',
];

$stockHandler = new StockHandler();
$ageHandler = new AgeVerificationHandler();
$paymentHandler = new PaymentHandler();
$packagingHandler = new PackagingHandler();

$stockHandler->setNext($ageHandler)
             ->setNext($paymentHandler)
             ->setNext($packagingHandler);

try {
    $result = $stockHandler->handle($request);
    echo "Заказ успешно обработан: " . json_encode($result, JSON_PRETTY_PRINT);
} catch (RuntimeException $e) {
    error_log("Ошибка обработки заказа: " . $e->getMessage());
    echo "Ошибка: " . $e->getMessage();
}

Если что‑то идёт не так, выбрасываем RuntimeException, а все важные этапы логируются через error_log (или можно заменить на тот же Monolog).

Не забываем покрыть код тестами:

expectException(RuntimeException::class);
        $this->expectExceptionMessage('Товара нет в наличии.');

        $handler->handle(['stock' => 0]);
    }

    public function testChainProcessesRequestSuccessfully()
    {
        $request = [
            'stock' => 5,
            'age' => 25,
            'payment' => true,
            'payment_id' => 'PAY12345',
        ];

        $stockHandler = new StockHandler();
        $ageHandler = new AgeVerificationHandler();
        $paymentHandler = new PaymentHandler();
        $packagingHandler = new PackagingHandler();

        $stockHandler->setNext($ageHandler)
                     ->setNext($paymentHandler)
                     ->setNext($packagingHandler);

        $result = $stockHandler->handle($request);

        $this->assertEquals('ready_for_delivery', $result['status']);
    }
}

Что ещё можно улучшить?

  1. Динамическая конфигурация цепочки.
    Например, настраивать последовательность обработчиков через тот же JSON или YAML.

  2. Производительность.
    Для больших цепочек можно добавить кэширование результатов, чтобы не проходить одни и те же проверки повторно.

  3. Логирование.
    Подключаем Monolog для более подробного логирования.

Как вы заметили, паттерн упрощает сложные процессы, разбивая их на независимые шаги. А какое применение паттерну находили вы? Делитесь в комментариях!

Всем PHP-разработчикам рекомендую посетить открытый урок «Вебсокеты на PHP, или как написать свой чат», который пройдет 22 января в Otus. Записаться можно по ссылке.

© Habrahabr.ru