Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Bus Laravel Package

alt-three/bus

Alt Three Bus is a lightweight command bus for Laravel that dispatches commands to handlers, supports middleware, and integrates with the container to keep business logic clean and decoupled.

View on GitHub
Deep Wiki
Context7

Getting Started

composer require alt-three/bus

Before you install anything, open the repo's composer.json and read the illuminate/* constraints. The package is archived and its last release was July 2020, so treat Laravel 8+ as unsupported until you've proven otherwise (the internals it wraps — dispatchNow, the old BusServiceProvider — moved on).

The one required step is swapping Laravel's bus provider for the package's in config/app.php:

// Illuminate\Bus\BusServiceProvider::class,
AltThree\Bus\BusServiceProvider::class,

Then php artisan config:clear (a cached config will silently keep the old provider).

Where to look first, in this order:

  1. src/Dispatcher.php — the actual behavior. It's small; read it before trusting the README, which is thin and predates the archive.
  2. src/CommandHandler.php — the hooks you opt into (handle, and the validation/transaction methods).
  3. README.md — installation only, basically.

First use case: a write command you'd otherwise put in a controller.

final class RegisterUser
{
    public function __construct(
        public string $name,
        public string $email,
    ) {}
}

final class RegisterUserHandler extends CommandHandler
{
    public function handle(RegisterUser $command): User
    {
        return User::create(['name' => $command->name, 'email' => $command->email]);
    }

    public function validate(RegisterUser $command): array
    {
        return [
            'name'  => 'required|string|max:255',
            'email' => 'required|email|unique:users,email',
        ];
    }

    public function transaction(RegisterUser $command): bool
    {
        return true;
    }
}

Register the command → handler mapping from a dedicated provider (the dispatcher exposes a map/commands style registration — confirm the exact method name in src/Dispatcher.php for your tag):

$this->app->make(Dispatcher::class)->map([
    RegisterUser::class => RegisterUserHandler::class,
]);

Dispatch normally: dispatch(new RegisterUser($name, $email));. You get validation-before-handle and a DB transaction around handle() for free.

Implementation Patterns

Keep commands dumb, handlers fat. Commands are DTOs with public promoted properties and no logic. Handlers own the work. This is the pattern the package is built to encourage and the one that survives if you later drop the package for Laravel's native pipeline.

Validation belongs in the handler, not the controller. Return a plain rules array from validate(); the dispatcher runs it against the command's public properties before handle() is called. Combine with messages() / attributes() overrides if you need custom copy. This is how you keep FormRequests out of non-HTTP entry points (artisan commands, queue jobs, other commands).

Transactions are declarative. Return true from transaction() for the default connection, or return a closure/connection name if the package's version supports it. Anything in handle() — including nested repository calls — rolls back together.

Container injection works in both places. Handlers are resolved from the container, so constructor injection and handle(RegisterUser $command, Mailer $mailer) both work. Depend on interfaces there, bind them in a normal provider.

Test at the dispatch boundary. Assert the handler's effects, and assert validation failures explicitly:

$this->expectException(ValidationException::class);
dispatch(new RegisterUser('', 'not-an-email'));

Use DatabaseTransactions on the test class so transaction-wrapped handlers don't leak state.

Compose commands, don't nest transactions. If one command needs another's behavior, call the handler directly or use dispatchNow() to force synchronous execution and avoid transaction-in-transaction surprises.

Group the mapping by module. One BusServiceProvider per domain beats a single 200-line map in AppServiceProvider — the map is your routing table and it should read like one.

Gotchas and Tips

  • The provider swap is global. Every package resolving Illuminate\Contracts\Bus\Dispatcher now gets the alt-three dispatcher. If it doesn't implement every method the framework's does (chain, dispatchAfterResponse, batches), you get runtime BadMethodCallExceptions far from the cause. Check what your other packages dispatch before shipping.
  • Queued commands change the transaction story. Validation happens at dispatch time in the web request; handle() runs in a worker. A transaction() declaration is not a guarantee that the queued execution is wrapped — read src/Dispatcher.php for your version, and when in doubt wrap the writes in DB::transaction() inside the handler.
  • Serialize only scalars and models. Command properties go through Laravel's job serialization when queued. Closures, resources, and injected services in the command will blow up in the worker. Use SerializesModels for Eloquent.
  • Rule evaluation is array-shaped. Validation runs against the command cast to an array, so nested objects and collections don't validate the way you expect. Flatten the command or use dot notation.
  • CommandNotRegisteredException almost always means boot order or config cache. The map is registered in boot(); a deferred provider or a stale bootstrap/cache/config.php will produce a "not registered" error for a command you clearly registered. php artisan config:clear first, then app(Dispatcher::class) in tinker and dd() the map.
  • Bus::fake() may not intercept anything. If the facade's fake swaps the contract binding, but you've rebound to the alt-three dispatcher elsewhere, your "assertDispatched" tests pass vacuously or fail confusingly. Fake at the boundary you actually use, or just assert DB/queue side effects.
  • Extension points, in order of usefulness: subclass Dispatcher and rebind it in the container (override dispatch for logging/metrics); swap or decorate CommandValidator for custom rule resolution; wrap handlers in a base class for cross-cutting concerns (audit columns, tenant scoping) since there's no middleware pipeline.
  • Migration exit ramp. If you outgrow it, the pieces map cleanly onto Laravel's native Illuminate\Bus pipeline plus a validation job middleware — the handler/command split you've already written is the hard part, and it ports over unchanged.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core