face/commander-bundle
A PHP bundle that integrates Commander-style command handling into your application, giving you a structured way to define, register, and run commands through the framework's service container and configuration.
First, verify that this can even be installed in a Laravel app. face/commander-bundle is a Symfony bundle, not a Laravel package. Run composer require face/commander-bundle on a throwaway branch and inspect the lockfile diff. Check its composer.json for symfony/framework-bundle, Symfony component version constraints, and PHP requirements. If it pulls in a full Symfony kernel dependency, expect friction alongside Laravel’s own pinned Symfony components.
Where to look first:
*Bundle.php and DependencyInjection/ — to see how handlers are registered and whether a compiler pass scans tags.Resources/config/ — for the command bus service definition and constructor arguments.Minimal Laravel-first use case: a RegisterUser command and a RegisterUserHandler. Because the bundle’s Symfony bootstrapping will not run in Laravel, wire the bus manually in a service provider:
// app/Providers/AppServiceProvider.php
public function register(): void
{
$this->app->singleton(CommandBus::class, function ($app) {
// Inspect the bundle source for the real constructor signature.
return new CommandBus($app);
});
}
Then dispatch from a controller:
public function store(StoreUserRequest $request, CommandBus $bus)
{
$bus->dispatch(new RegisterUser($request->validated()));
return redirect()->route('users.index');
}
If you are not prepared to read the bundle source and wire its services manually, stop here. Use Laravel’s native bus or a maintained command-bus package instead.
One command, one handler. Keep commands as immutable DTOs. Put orchestration in the handler. Controllers, form requests, console commands, and jobs should only dispatch.
Manual handler mapping in Laravel. The bundle’s Symfony compiler pass and tag scanning will not execute in a Laravel kernel. Instead, keep an explicit map and resolve handlers through Laravel’s container:
protected array $handlers = [
RegisterUser::class => RegisterUserHandler::class,
];
Wrap that map in your own CommandBus implementation. Do not depend directly on the bundle’s bus interface in application code unless you also bind it to your wrapper.
Middleware and decorators. The most useful day-to-day pattern is wrapping dispatch with cross-cutting concerns:
class TransactionalCommandBus implements CommandBusInterface
{
public function __construct(private CommandBusInterface $inner) {}
public function dispatch(object $command): mixed
{
return DB::transaction(fn () => $this->inner->dispatch($command));
}
}
Bind the decorator in a service provider. Use the same approach for logging, validation, and authorization.
Async work. Do not assume the bundle has queue support unless the README explicitly says so. The safe pattern is to dispatch the command inside a Laravel job:
class DispatchRegisterUser implements ShouldQueue
{
public function handle(CommandBus $bus): void
{
$bus->dispatch(new RegisterUser(...));
}
}
Testing. Test handlers directly with mocked dependencies. Test the bus separately by binding a spy or fake implementation. If you keep the bundle, write a contract test around your own CommandBus interface so you can swap the implementation later.
Events. Emit Laravel events from the handler after the command succeeds. Avoid mixing domain events into the command bus unless the package’s middleware supports it.
ServiceProvider, no package discovery, and no config/commander.php. Symfony YAML configuration under config/packages/ is inert in Laravel.composer why symfony/framework-bundle and review the lockfile diff before merging. Do not add this to a large application without testing the resolved dependency graph.Illuminate\Bus, Laravel Tactician, or Symfony Messenger via a bridge is a safer choice.app()->make(RegisterUserHandler::class) before dispatching to isolate container binding issues.App\Bus\CommandBus and bind it to the bundle-backed implementation. That gives you a one-line swap when the package becomes unusable or you migrate to a maintained alternative.How can I help you explore Laravel packages today?