Installation
composer require dmp/cqrs-sf-bundle
Add to config/bundles.php:
return [
// ...
Dmp\CqrsBundle\DmpCqrsBundle::class => ['all' => true],
];
First Use Case: Command Handling
Define a command class (e.g., src/Command/CreateUserCommand.php):
namespace App\Command;
class CreateUserCommand
{
public function __construct(
public string $name,
public string $email
) {}
}
Register the command in config/packages/dmp_cqrs.yaml:
dmp_cqrs:
commands:
App\Command\CreateUserCommand: App\CommandHandler\CreateUserCommandHandler
Dispatching a Command
use Dmp\CqrsBundle\CommandBus;
class UserController
{
public function __construct(private CommandBus $commandBus) {}
public function createUser(string $name, string $email)
{
$command = new CreateUserCommand($name, $email);
$this->commandBus->dispatch($command);
}
}
Query Handling (Optional)
Define a query (e.g., src/Query/GetUserQuery.php):
namespace App\Query;
class GetUserQuery
{
public function __construct(public int $id) {}
}
Register in dmp_cqrs.yaml:
dmp_cqrs:
queries:
App\Query\GetUserQuery: App\QueryHandler\GetUserQueryHandler
CreateUserCommand, UpdateOrderCommand).GetUserByEmailQuery, ListActiveOrdersQuery).CommandBus; resolve queries via QueryBus (if implemented).namespace App\CommandHandler;
use App\Command\CreateUserCommand;
use App\Repository\UserRepository;
class CreateUserCommandHandler
{
public function __construct(private UserRepository $userRepository) {}
public function __invoke(CreateUserCommand $command)
{
$this->userRepository->save(
new User($command->name, $command->email)
);
}
}
dmp_cqrs.yaml:
dmp_cqrs:
command_middleware:
- App\Middleware\LoggingMiddleware
- App\Middleware\ValidationMiddleware
namespace App\Middleware;
use Dmp\CqrsBundle\Middleware\CommandMiddlewareInterface;
class LoggingMiddleware implements CommandMiddlewareInterface
{
public function handle($command, callable $next)
{
\Log::info("Handling command: " . get_class($command));
return $next($command);
}
}
public function __invoke(CreateUserCommand $command)
{
$user = $this->userRepository->save(new User(...));
$this->eventDispatcher->dispatch(new UserCreatedEvent($user));
}
EventSubscriber or dmp_cqrs.yaml (if supported).public function testCreateUserCommand()
{
$handler = new CreateUserCommandHandler($this->createMock(UserRepository::class));
$command = new CreateUserCommand('John', 'john@example.com');
$handler($command);
// Assert repository interactions
}
$commandBus = $this->container->get(CommandBus::class);
$commandBus->dispatch(new CreateUserCommand(...));
$this->assertDatabaseHas('users', [...]);
YAML Overrides:
!import in config/packages/dmp_cqrs.yaml to override defaults:
imports:
- { resource: "@AppConfig/config/cqrs.yaml" }
config/packages/ by default).Autowiring Conflicts:
services.yaml:
services:
App\CommandHandler\CreateUserCommandHandler:
tags: ['dmp_cqrs.command_handler']
Missing Handlers:
dmp_cqrs.yaml for typos in command/query mappings.$this->commandBus->setDebug(true);
Middleware Order:
dmp_cqrs.yaml. Reorder as needed:
command_middleware:
- App\Middleware\ValidationMiddleware # Runs first
- App\Middleware\LoggingMiddleware # Runs second
Circular Dependencies:
CommandBus into handlers (leads to circular references). Use services directly.Custom Bus:
Dmp\CqrsBundle\CommandBus to add features (e.g., retry logic):
class RetryCommandBus extends CommandBus
{
public function dispatch($command, int $retries = 3)
{
try {
parent::dispatch($command);
} catch (Exception $e) {
if ($retries > 0) {
sleep(1);
$this->dispatch($command, $retries - 1);
}
throw $e;
}
}
}
services.yaml:
services:
Dmp\CqrsBundle\CommandBus: '@App\Service\RetryCommandBus'
Async Commands:
dmp_cqrs:
commands:
App\Command\SendEmailCommand: ~ # Mark as async
framework/messenger.yaml.Query Support:
QueryBus by extending the bundle or using a separate package like symfony/messenger with query commands.Batch Commands:
BatchCommand pattern:
class BatchCreateUserCommand
{
public function __construct(public array $users) {}
}
Caching Queries:
public function __invoke(GetUserQuery $query)
{
return Cache::remember(
"user_{$query->id}",
3600,
fn() => $this->userRepository->find($query->id)
);
}
Overusing Commands:
PlaceOrderCommand with validation/transactions).Tight Coupling:
// Bad: Command knows about UserRepository
class CreateUserCommand { public function __construct(public UserRepository $repo) {} }
// Good: Command is a DTO; handler uses repository
class CreateUserCommand { public function __construct(public string $name) {} }
Error Handling:
try {
$this->commandBus->dispatch($command);
} catch (CommandException $e) {
$this->addFlash('error', $e->getMessage());
}
How can I help you explore Laravel packages today?