Современный PrestaShop постепенно уходит от подхода, когда вся логика находится прямо внутри контроллера.
Раньше изменение товара часто выглядело примерно так:
$product = new Product($idProduct);
$product->active = false;
$product->save();
Такой код понятен и продолжает работать, но в современных Symfony-разделах Back Office всё чаще используется другой подход — CQRS.
CQRS расшифровывается как Command Query Responsibility Segregation. Главная идея проста:
-
Command изменяет данные;
-
Query получает данные.
Что такое Command
Command описывает конкретное действие.
Например:
UpdateProductPriceCommand
ChangeOrderStatusCommand
DisableProductCommand
Сам Command ничего не сохраняет в базе данных. Он только содержит необходимые для операции данные.
final class DisableProductCommand
{
public function __construct(
private int $productId
) {
}
public function getProductId(): int
{
return $this->productId;
}
}
Команда передаётся обработчику через Command Bus.
$this->getCommandBus()->handle(
new DisableProductCommand($idProduct)
);
Что делает Command Handler
Вся реальная логика находится в Handler.
final class DisableProductHandler
{
public function handle(
DisableProductCommand $command
): void {
$product = new Product(
$command->getProductId()
);
if (!Validate::isLoadedObject($product)) {
throw new PrestaShopException(
'Product not found'
);
}
$product->active = false;
$product->save();
}
}
Получается следующая схема:
Controller
↓
Command
↓
Command Bus
↓
Handler
↓
Product / Repository / Database
Контроллер больше не обязан знать, каким способом изменяется товар.
Что такое Query
Query работает похожим образом, но используется для получения информации.
Например:
GetProductForEditing
GetOrderForViewing
GetCategoryData
Контроллер отправляет Query:
$productData = $this->getQueryBus()->handle(
new GetProductForEditing($idProduct)
);
Query Handler получает необходимые данные и возвращает результат.
Схема выглядит так:
Controller
↓
Query
↓
Query Bus
↓
Query Handler
↓
Data
Таким образом операции чтения и изменения данных не смешиваются.
Зачем PrestaShop использует CQRS
Одна из причин — постепенный переход Back Office на Symfony.
В старой архитектуре контроллер напрямую работал с ObjectModel:
Controller → Product → Database
В современной архитектуре появляется дополнительный слой:
Controller
↓
Command / Query
↓
Handler
↓
ObjectModel / Repository
Благодаря этому Symfony-контроллер меньше зависит от того, каким способом PrestaShop хранит или изменяет данные.
Это особенно удобно в большой платформе, где одновременно существуют legacy-код, ObjectModel, Symfony и новые Domain-компоненты.
CQRS не запрещает ObjectModel
Иногда возникает впечатление, что при использовании CQRS больше нельзя обращаться к:
new Product();
new Order();
new Category();
Это не так.
Handler вполне может использовать привычный ObjectModel.
Разница только в том, что работа с ним находится не внутри контроллера, а в отдельном слое.
Например:
Controller
↓
UpdateProductCommand
↓
UpdateProductHandler
↓
Product ObjectModel
Если в дальнейшем реализация изменится, контроллер переписывать не придётся.
Где CQRS особенно полезен в модулях
Не стоит использовать такую архитектуру в каждом небольшом модуле.
Если модуль просто выводит дополнительный блок через hook, CQRS может только усложнить проект.
Но подход становится полезным, если модуль содержит:
-
сложный Back Office;
-
несколько Symfony-контроллеров;
-
импорт товаров;
-
синхронизацию с API;
-
управление заказами;
-
CLI-команды;
-
cron;
-
AJAX;
-
большое количество бизнес-операций.
Например, одна операция синхронизации остатков может запускаться из нескольких мест:
Back Office
CLI
Cron
API
Вместо четырёх разных реализаций все они могут вызывать одну команду:
UpdateProductStockCommand
↓
UpdateProductStockHandler
Бизнес-логика остаётся в одном месте.
Как организовать структуру модуля
Для крупного модуля можно использовать примерно такую структуру:
src/
├── Controller/
├── Command/
├── CommandHandler/
├── Query/
├── QueryHandler/
├── DTO/
└── Exception/
Например:
Command/
UpdateProductPriceCommand.php
CommandHandler/
UpdateProductPriceHandler.php
Query/
GetProductData.php
QueryHandler/
GetProductDataHandler.php
Такой код легче читать и сопровождать.
Главная ошибка при использовании CQRS
Не нужно просто переносить огромный контроллер в огромный Handler.
Плохой вариант:
public function handle($command)
{
// 500 строк логики
}
Если операция сложная, Handler должен обращаться к отдельным сервисам:
UpdateProductHandler
↓
ProductValidator
PriceCalculator
StockService
ExternalApiService
CQRS помогает разделить ответственность, а не просто увеличить количество файлов.
Стоит ли использовать CQRS в собственном модуле
Для маленького модуля — необязательно.
Для большого модуля, который планируется поддерживать несколько лет, такой подход часто оправдан.
Особенно если одна бизнес-операция должна выполняться из разных мест.
Простой ориентир:
Простой hook → CQRS, скорее всего, не нужен.
Большой Symfony Back Office → стоит рассмотреть.
CLI + cron + API + Back Office → CQRS особенно полезен.
CQRS в PrestaShop можно свести к простой схеме.
Изменение данных:
Controller
↓
Command
↓
Handler
Получение данных:
Controller
↓
Query
↓
Handler
Для разработчика это даёт главное преимущество: бизнес-логика перестаёт зависеть от конкретного контроллера.
Это особенно важно для современных модулей PrestaShop с Back Office, импортом, API, CLI и сложной логикой.
Кроме того, понимание Commands, Queries и Handlers заметно упрощает чтение исходного кода самого PrestaShop 9.