itmedia/command-bus-bundle
A Symfony bundle that adds command bus support, letting you dispatch commands to dedicated handlers and keep business logic decoupled from controllers. It integrates with the service container for handler registration and execution.
Before anything else, note the mismatch in the brief: this is a Symfony bundle, not a Laravel package. If you are evaluating it from a Laravel codebase, the answer is "skip it" — Laravel's Illuminate\Bus / dispatch() already covers this ground, and pulling in a Symfony bundle is not viable. The notes below assume a Symfony (or Symfony-compatible) host application.
v0.5.0 is a small compatibility release: its release note only advertises PHP 7.4 and 8 support. There is no documented new feature, middleware, async transport, or API change in the release note, so treat it as a compatibility bump rather than a reason to re-architect anything.
Minimal steps:
composer require itmedia/command-bus-bundle and pin the exact tag you test against (v0.5.0 as of this review).config/bundles.php (new Itmedia\CommandBusBundle\CommandBusBundle()).src/ for the bus service class and the handler interface, then the bundle's DependencyInjection/ folder to see how handlers are collected (almost certainly via a service tag, e.g. command_bus.handler or similar).bin/console debug:container command_bus and bin/console debug:container --tag=<handler_tag>. This is faster than reading docs that may not exist.First use case: a single synchronous command with a handler, wired and dispatched from a controller. Do not start with middleware, async transport, or return-value semantics — get one command round-tripping first, because most of the uncertainty in a package this small is in how handlers are discovered, not in the dispatch call itself. The v0.5.0 release note does not change that ordering.
A realistic shape:
final class RegisterUser { public function __construct(public string $email) {} }
final class RegisterUserHandler {
public function __invoke(RegisterUser $command): void { /* ... */ }
}
Then tag/register the handler per the bundle's convention and $commandBus->dispatch(new RegisterUser($email)) from the controller.
Command as a dumb DTO. Keep commands immutable, scalar-only, serializable. The moment you stuff an entity or a service into a command you lose the ability to test it in isolation and to ever move it onto an async transport.
Handler-per-command, injected by the container. Handlers should be thin: validate preconditions, load aggregates via repositories, persist, return. Business logic belongs in domain services the handler calls, not in the handler body — otherwise the "one handler per command" symmetry breaks down and handlers become god classes.
Controller → bus, never controller → handler. The value of the bus is that the controller knows nothing about handler location. Enforce this in review: any use App\Handler\... inside a controller is a smell.
Middleware/decorator for cross-cutting concerns. Rather than baking logging, transactions, or auth checks into every handler, wrap the bus or register middleware (whatever extension mechanism the bundle exposes — check src/ for a middleware interface or a tagged-iterator of "middlewares"). Transaction-wrapping the whole dispatch is the single highest-value middleware: one DB transaction per command, and handlers stay clean.
Testing. Because commands are DTOs and handlers are plain services, unit-test handlers by instantiating them with mocked repositories — no container, no bus. Then have a small number of integration tests that go through the real bus to catch wiring/tagging regressions. That split catches ~all the failure modes and runs fast.
If you outgrow it: the bundle is small enough that replacing it with a hand-rolled CommandBus (a ContainerInterface + a command → handler map) is a day's work. Design your application so the bus is behind your own interface, so the swap is mechanical.
messenger setup covers the same ground with real maintenance.src/; don't assume dispatch() returns the handler's return value — many minimal buses return void. Verify explicitly, because returning a value from a handler and silently getting null is the classic first-day bug here. The v0.5.0 note gives no API-level clarification.autoconfigure in services.yaml may or may not cover it — a bundle-specific tag usually needs an explicit _instanceof rule or manual tagging). debug:container --tag=... is the fastest diagnostic.services.yaml — that works regardless of what the bundle offers and is a better long-term bet than editing vendor code.How can I help you explore Laravel packages today?