Оригинал: 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.php
1. Создание команды и обработчика
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 с традиционными подходами. Это не все или ничего!