Оригинал: CQRS in PHP: The Ultimate Guide for Modern Developers
Перевод для канала Мы ж программист
Введение
Если вы давно создаете приложения на PHP, то наверняка видели, как сервисы или контроллеры делают всё – получают данные, преобразуют их, сохраняют и так далее. Это работает, конечно. Но по мере роста вашего приложения код становится хрупким, нечитаемым и сложным для поддержки.
Вы наверняка спрашивали себя: «Почему эта простая операция чтения запуталась в бизнес-логике и логике сохранения?».
Знакомьтесь с CQRS – Command Query Responsibility Segregation. Модное название для очень простой идеи.
CQRS (Command Query Responsibility Segregation) – это шаблон проектирования, который разделяет чтение данных (запросы) и запись/обновление данных (команды).
➡️ Команды изменяют состояние (например, создание пользователя).
➡️ Запросы считывают состояние (например, получение информации о пользователе).
Разделяя обязанности, вы получаете более чистую архитектуру, упрощаете тестирование и улучшаете масштабируемость.
Ключевые концепции CQRS
Давайте разберемся:
Command (Команда)
Command – это объект, который представляет собой намерение выполнить действие.
Примеры:
CreateUserCommand
UpdateProductStockCommand
DeleteOrderCommand
Команды не возвращают данных – только результат успеха/неудачи.
Query (Запрос)
Query – это объект, представляющий запрос данных.
Примеры:
GetUserByEmailQuery
GetOrdersByCustomerQuery
SearchProductsQuery
Запросы не могут менять состояние системы.
Handlers (Обработчики)
Каждая команда или запрос имеет соответствующий Handler.
CreateUserCommandHandler
GetUserByEmailQueryHandler
Обработчики содержат актуальную логику операции.
Зачем использовать CQRS в PHP?
Вот некоторые основные преимущества:
- Разделение ответственности — Логика чтения и записи не связаны.
 - Масштабируемость — Вы можете масштабировать чтение и запись независимо друг от друга..
 - Тестируемость — Каждое действие становится тестируемой единицей.
 - Безопасность — Легко реализовать контроль доступа на основе ролей для каждой команды/запроса.
 - Удобство обслуживания — Избегайте раздутых классов служб.
 
Настройка PHP-проекта к работе с CQRS
Давайте рассмотрим настройку CQRS в PHP-приложении. Для наглядности мы будем использовать обычный PHP (без фреймворков), но это можно легко адаптировать под Laravel, Symfony и т.д.
Структура каталогов:
/src
  /Command
    CreateUserCommand.php
    CreateUserHandler.php
  /Query
    GetUserByEmailQuery.php
    GetUserByEmailHandler.php
  /Bus
    CommandBus.php
    QueryBus.php
  /Handler
    HandlerInterface.php1. Создание команды и обработчика
CreateUserCommand.php
class CreateUserCommand
{
    public string $name;
    public string $email;
    public string $password;
    public function __construct(string $name, string $email, string $password)
    {
        $this->name = $name;
        $this->email = $email;
        $this->password = $password;
    }
}CreateUserHandler.php
class CreateUserHandler
{
    protected UserRepository $repository;
    public function __construct(UserRepository $repository)
    {
        $this->repository = $repository;
    }
    public function handle(CreateUserCommand $command): void
    {
        $user = new User(
            $command->name,
            $command->email,
            password_hash($command->password, PASSWORD_BCRYPT)
        );
        $this->repository->save($user);
    }
}2. Создание запроса и обработчика
GetUserByEmailQuery.php
class GetUserByEmailQuery
{
    public string $email;
    public function __construct(string $email)
    {
        $this->email = $email;
    }
}GetUserByEmailHandler.php
class GetUserByEmailHandler
{
    protected UserRepository $repository;
    public function __construct(UserRepository $repository)
    {
        $this->repository = $repository;
    }
    public function handle(GetUserByEmailQuery $query): ?User
    {
        return $this->repository->findByEmail($query->email);
    }
}3. Построение шины команд и запросов
Шина (bus) направляет команду/запрос к соответствующему обработчику.
CommandBus.php
class CommandBus
{
    private array $handlers = [];
    public function register(string $commandClass, object $handler): void
    {
        $this->handlers[$commandClass] = $handler;
    }
    public function dispatch(object $command): void
    {
        $class = get_class($command);
        if (!isset($this->handlers[$class])) {
            throw new Exception("No handler found for $class");
        }
        $this->handlers[$class]->handle($command);
    }
}QueryBus.php
class QueryBus
{
    private array $handlers = [];
    public function register(string $queryClass, object $handler): void
    {
        $this->handlers[$queryClass] = $handler;
    }
    public function dispatch(object $query): mixed
    {
        $class = get_class($query);
        if (!isset($this->handlers[$class])) {
            throw new Exception("No handler found for $class");
        }
        return $this->handlers[$class]->handle($query);
    }
}4. Использование системы
Пример команды:
$commandBus = new CommandBus();
$commandBus->register(CreateUserCommand::class, new CreateUserHandler(new UserRepository()));
$command = new CreateUserCommand("Alice", "alice@example.com", "securepassword");
$commandBus->dispatch($command);Пример запроса:
$queryBus = new QueryBus();
$queryBus->register(GetUserByEmailQuery::class, new GetUserByEmailHandler(new UserRepository()));
$query = new GetUserByEmailQuery("alice@example.com");
$user = $queryBus->dispatch($query);Сравнение CQRS и традиционного CRUD

Когда использовать CQRS в PHP?
Отличные кейсы использования:
- Крупные корпоративные PHP-приложения
 - Приложения с сильным дисбалансом чтения/записи
 - Приложения, требующие сложной бизнес-логики на каждое действие
 - Архитектура микросервисов
 - Системы с event sourcing
 
Возможно, не лучший выбор, если:
- Вы создаете небольшое CRUD-приложение
 - Вы хотите, чтобы все было просто
 - У вас нет команды для разработки сложных паттернов
 
Запомните: CQRS – это не серебряная пуля.
Бонус: CQRS + Event Sourcing = 💥
В паре с Event Sourcing CQRS становится еще более мощным.
Вместо того чтобы хранить текущее состояние, вы храните каждое изменение (событие) и восстанавливаете состояние из истории.
Пример:
UserCreatedEvent
UserEmailChangedEvent
Каждая команда приводит к одному или нескольким событиям в домене. Вы сохраняете их вместо прямого обновления строк.
Инструменты и библиотеки для CQRS в PHP
Если вы не хотите изобретать велосипед, то вот отличные варианты:
- 🔧 Broadway — CQRS + Event Sourcing (https://github.com/broadway/broadway)
 - 🧩 Prooph Components — Event-sourced и CQRS компоненты for PHP
 - 🧱 Tactician — Простая шина команд (https://github.com/thephpleague/tactician)
 - 🚀 Laravel-QueryBus — Для приложений на Laravel
 - 🔍 Symfony Messenger — Может быть адаптировано для шины в стиле CQRS
 
Заключительные мысли
CQRS в PHP – это переломный момент для растущих приложений. Хотя поначалу это может показаться пугающим, отдача в виде ясности, тестируемости и масштабируемости огромна.
Не стоит с первого дня полностью переходить на CQRS. Начните с малого. Добавьте разделение команд и запросов там, где это имеет смысл.
Совет профессионала: можно сочетать CQRS с традиционными подходами. Это не все или ничего!
