Implement CQRS pattern in Symfony with separate Command and Query handlers using Messenger component
/plugin marketplace add MakFly/superpowers-symfony/plugin install makfly-superpowers-symfony@MakFly/superpowers-symfonyThis skill inherits all available tools. When active, it can use any tool Claude has access to.
CQRS (Command Query Responsibility Segregation) separates read and write operations:
src/
├── Application/
│ ├── Command/
│ │ ├── CreateOrder.php
│ │ └── CreateOrderHandler.php
│ └── Query/
│ ├── GetOrder.php
│ └── GetOrderHandler.php
├── Domain/
│ └── Order/
│ └── Entity/Order.php
└── Infrastructure/
└── Controller/
└── OrderController.php
<?php
// src/Application/Command/CreateOrder.php
namespace App\Application\Command;
final readonly class CreateOrder
{
public function __construct(
public int $customerId,
public array $items,
public ?string $couponCode = null,
) {}
}
<?php
// src/Application/Command/CreateOrderHandler.php
namespace App\Application\Command;
use App\Domain\Order\Entity\Order;
use App\Domain\Order\Repository\OrderRepositoryInterface;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
#[AsMessageHandler]
final readonly class CreateOrderHandler
{
public function __construct(
private OrderRepositoryInterface $orders,
private ProductService $products,
private CouponService $coupons,
) {}
public function __invoke(CreateOrder $command): Order
{
// Validate products exist
$items = $this->products->resolveItems($command->items);
// Create order
$order = Order::create(
$this->orders->nextId(),
$command->customerId,
);
foreach ($items as $item) {
$order->addItem($item);
}
// Apply coupon if provided
if ($command->couponCode) {
$discount = $this->coupons->apply($command->couponCode, $order);
$order->applyDiscount($discount);
}
$this->orders->save($order);
return $order;
}
}
<?php
// src/Application/Query/GetOrder.php
namespace App\Application\Query;
final readonly class GetOrder
{
public function __construct(
public string $orderId,
) {}
}
// src/Application/Query/GetOrdersByCustomer.php
final readonly class GetOrdersByCustomer
{
public function __construct(
public int $customerId,
public int $page = 1,
public int $limit = 20,
) {}
}
<?php
// src/Application/Query/GetOrderHandler.php
namespace App\Application\Query;
use App\Domain\Order\Repository\OrderRepositoryInterface;
use App\Dto\OrderView;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
#[AsMessageHandler]
final readonly class GetOrderHandler
{
public function __construct(
private OrderRepositoryInterface $orders,
) {}
public function __invoke(GetOrder $query): ?OrderView
{
$order = $this->orders->findById($query->orderId);
if (!$order) {
return null;
}
return OrderView::fromEntity($order);
}
}
// src/Application/Query/GetOrdersByCustomerHandler.php
#[AsMessageHandler]
final readonly class GetOrdersByCustomerHandler
{
public function __construct(
private OrderReadRepository $readRepository,
) {}
public function __invoke(GetOrdersByCustomer $query): PaginatedResult
{
return $this->readRepository->findByCustomer(
$query->customerId,
$query->page,
$query->limit,
);
}
}
# config/packages/messenger.yaml
framework:
messenger:
default_bus: command.bus
buses:
command.bus:
middleware:
- validation
- doctrine_transaction
query.bus:
middleware:
- validation
<?php
// src/Application/Bus/CommandBusInterface.php
namespace App\Application\Bus;
interface CommandBusInterface
{
public function dispatch(object $command): mixed;
}
// src/Application/Bus/QueryBusInterface.php
interface QueryBusInterface
{
public function ask(object $query): mixed;
}
<?php
// src/Infrastructure/Bus/MessengerCommandBus.php
namespace App\Infrastructure\Bus;
use App\Application\Bus\CommandBusInterface;
use Symfony\Component\Messenger\HandleTrait;
use Symfony\Component\Messenger\MessageBusInterface;
final class MessengerCommandBus implements CommandBusInterface
{
use HandleTrait;
public function __construct(MessageBusInterface $commandBus)
{
$this->messageBus = $commandBus;
}
public function dispatch(object $command): mixed
{
return $this->handle($command);
}
}
// src/Infrastructure/Bus/MessengerQueryBus.php
final class MessengerQueryBus implements QueryBusInterface
{
use HandleTrait;
public function __construct(MessageBusInterface $queryBus)
{
$this->messageBus = $queryBus;
}
public function ask(object $query): mixed
{
return $this->handle($query);
}
}
# config/services.yaml
services:
App\Application\Bus\CommandBusInterface:
class: App\Infrastructure\Bus\MessengerCommandBus
arguments: ['@command.bus']
App\Application\Bus\QueryBusInterface:
class: App\Infrastructure\Bus\MessengerQueryBus
arguments: ['@query.bus']
<?php
// src/Infrastructure/Controller/OrderController.php
namespace App\Infrastructure\Controller;
use App\Application\Bus\CommandBusInterface;
use App\Application\Bus\QueryBusInterface;
use App\Application\Command\CreateOrder;
use App\Application\Query\GetOrder;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Attribute\Route;
#[Route('/api/orders')]
class OrderController extends AbstractController
{
public function __construct(
private CommandBusInterface $commandBus,
private QueryBusInterface $queryBus,
) {}
#[Route('', methods: ['POST'])]
public function create(Request $request): JsonResponse
{
$data = json_decode($request->getContent(), true);
$order = $this->commandBus->dispatch(new CreateOrder(
customerId: $data['customerId'],
items: $data['items'],
couponCode: $data['couponCode'] ?? null,
));
return new JsonResponse(['id' => $order->getId()], 201);
}
#[Route('/{id}', methods: ['GET'])]
public function show(string $id): JsonResponse
{
$order = $this->queryBus->ask(new GetOrder($id));
if (!$order) {
throw $this->createNotFoundException();
}
return new JsonResponse($order);
}
}
For complex reads, use dedicated read models:
<?php
// src/Infrastructure/ReadModel/OrderReadRepository.php
namespace App\Infrastructure\ReadModel;
use Doctrine\DBAL\Connection;
class OrderReadRepository
{
public function __construct(
private Connection $connection,
) {}
public function findByCustomer(int $customerId, int $page, int $limit): PaginatedResult
{
// Direct SQL for optimized reads
$sql = <<<SQL
SELECT o.id, o.total, o.status, o.created_at,
COUNT(i.id) as item_count
FROM orders o
LEFT JOIN order_items i ON i.order_id = o.id
WHERE o.customer_id = :customerId
GROUP BY o.id
ORDER BY o.created_at DESC
LIMIT :limit OFFSET :offset
SQL;
$results = $this->connection->fetchAllAssociative($sql, [
'customerId' => $customerId,
'limit' => $limit,
'offset' => ($page - 1) * $limit,
]);
return new PaginatedResult($results, $this->countByCustomer($customerId));
}
}
Creating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists' work to avoid copyright violations.
Applies Anthropic's official brand colors and typography to any sort of artifact that may benefit from having Anthropic's look-and-feel. Use it when brand colors or style guidelines, visual formatting, or company design standards apply.
Create beautiful visual art in .png and .pdf documents using design philosophy. You should use this skill when the user asks to create a poster, piece of art, design, or other static piece. Create original visual designs, never copying existing artists' work to avoid copyright violations.