Download the PHP package different-universe/laravel-cqbus-mediator without Composer
On this page you can find all versions of the php package different-universe/laravel-cqbus-mediator. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download different-universe/laravel-cqbus-mediator
More information about different-universe/laravel-cqbus-mediator
Files in different-universe/laravel-cqbus-mediator
Package laravel-cqbus-mediator
Short Description Lightweight, modern CQS Mediator for Laravel using PHP 8 attributes and auto-discovery.
License MIT
Homepage https://github.com/different-universe/laravel-cqbus-mediator
Informations about the package laravel-cqbus-mediator
Laravel CQBus Mediator
Lightweight CQS/CQRS-style mediator for Laravel applications. The package lets you describe application use cases as small command, query, and internal handler classes, route messages to handlers automatically via PHP attributes, and wrap execution with Laravel pipelines.
Instead of calling large service classes from controllers, you dispatch an explicit message:
Motivation
Controllers are usually a poor place for business logic. They should deal with the HTTP layer: request data, validation, authentication context, response shape, status codes, redirects, headers, and framework-specific concerns.
Business operations are easier to understand and test when they are modeled as separate application units. A CreateOrderHandler, RefundPaymentHandler, or LookupCustomerBalanceHandler represents one concrete use case that can be reused from controllers, console commands, queue jobs, event listeners, tests, or any other entry point.
The common "fat controller" problem appears when HTTP details, application flow, persistence, domain rules, authorization checks, logging, and side effects grow together in the same method. The result is hard to test, hard to reuse outside HTTP, and hard to change without pulling on hidden dependencies.
Classic service classes help, but they often drift into broad buckets such as OrderService, UserService, or PaymentService. Over time they collect many unrelated methods, their names stop describing a single operation, and cross-cutting behavior such as transactions, tracing, tenant context, or query caching becomes inconsistent.
This package pushes the code toward smaller use-case handlers:
- one message maps to one command/query handler;
- handlers are discovered automatically from configured paths;
- command and query flows can have different configured pipelines;
- local pipelines can be declared directly on handlers;
- internal handlers can build explicit post-processing chains after the main handler returns.
Features
- Command, query, and internal handlers via PHP attributes.
- Automatic handler discovery from configured paths.
- Message class routing and explicit string channels.
- Laravel container resolution for handlers and pipelines.
- Laravel
Illuminate\Pipeline\Pipelineintegration. - Global, command-specific, query-specific, and local handler pipelines.
- Internal post-processing handler chains through output channels.
- Cache mode for production.
- Artisan generators for handler classes.
- Laravel package auto-discovery and facade support.
- Laravel Octane friendly mediator singleton.
Requirements
- PHP
^8.2 - Laravel
^11.0,^12.0, or^13.0
Installation
Install the package:
Publish the config file:
Optionally publish stubs for generator customization:
The service provider is registered automatically through Laravel package discovery.
If package discovery is disabled, register the provider manually:
Configuration
After publishing, the config is available at config/mediator.php:
Basic Usage
Create a message:
Create a handler:
Dispatch through the contract:
Or dispatch through the facade:
The value returned by handle() is returned from the mediator. If an internal handler chain is configured through an output channel, the final internal handler result is returned.
Commands And Queries
#[CommandHandler] and #[QueryHandler] work the same way at the API level. Both mark a class as a discoverable handler, both support automatic or explicit input channels, both can define an output channel, and both can be wrapped with pipelines.
The difference is mostly business-level:
- a command expresses an intention to change state;
- a query expresses an intention to read or calculate data without changing state.
The technical benefit of separating them is that commands and queries can have different configured pipelines. For example, commands can run through transactions and retries, while queries can run through cache lookup.
Command example:
Query example:
Handlers are resolved through the Laravel container, so constructor injection works normally.
Handler requirements:
- the class must be instantiable;
- the class must have a
handle()method; handle()must be public and non-static;handle()must have no more than one parameter;- a class cannot have more than one handler attribute.
Input Channels
There are two channel styles.
Automatic typed channel:
When no channel is passed to the attribute, the channel is derived from the type of the single handle() parameter. In this example, the input channel is CreateOrder::class.
Union types are supported and register the same handler for each message class:
Restrictions for automatic typed channels:
handle()must have exactly one parameter;- the parameter must be typed;
- the parameter cannot be nullable;
- the parameter type must be a concrete message class, not an interface or abstract class;
- builtin types such as
array,string,int, ormixedare not allowed; - intersection types are not supported;
- every referenced class must exist.
The channel is the exact class name from the type declaration. Inheritance is not considered: a handler registered for BaseCommand::class will not be found by send(new CreateOrder(...)) unless the actual message class is also registered as an input channel.
Explicit named channel:
When an explicit channel is provided, it takes precedence over the handle() parameter type. This is useful for string channels, generic payloads, integration-style messages, or cases where a dedicated message class would be unnecessary.
For named channels, the parameter type does not affect routing. A handler may have no handle() parameter at all, or one parameter with any type you need for that channel. If a parameter is present, it must still be the only parameter.
Dispatching
Dispatch by message class:
send() accepts only an object and routes it by $message::class.
Dispatch by explicit channel:
sendToChannel() accepts any payload.
Both methods are available on the contract and the facade:
Internal Handlers
Internal handlers run after the main command/query handler. They receive the result of the previous handler and may transform it before passing it to the next internal handler.
They are useful for explicit post-processing chains: audit trail storage, normalization, enrichment, or a final response transformation.
Main handler with output channel:
Internal handler chain:
Important rules:
- internal handlers cannot be called directly through
send()orsendToChannel(); - the mediator resolves internal handlers only from an
outputChannelof the main handler or another internal handler; - internal handlers are resolved through the Laravel container;
#[Pipeline],#[WithoutGlobalPipeline], and#[WithoutAllGlobalPipelines]are intended for command/query handlers only;- configured and local pipelines wrap the main handler; internal handlers execute after the main handler inside that flow.
The chain stops when a handler has no outputChannel. In that case, the value returned by that handler becomes the final mediator result.
If an output channel points back to an already visited internal channel, the mediator throws RuntimeException with a circular output channel message.
Pipelines
The package uses Laravel Illuminate\Pipeline\Pipeline. Conceptually, pipelines work like middleware: a pipe receives the payload, calls $next($payload), and can run logic before or after the next pipe/handler.
Pipelines can be configured globally:
Or locally on a command/query handler:
Multiple local pipelines can be declared with repeatable attributes:
Or with several pipes in one attribute:
Pipeline order:
global_pipelinescommand_pipelinesorquery_pipelines- local pipelines from
#[Pipeline(...)]
Because Laravel pipelines wrap the next stage, "before" logic runs from first to last, while "after" logic unwinds from last to first.
Parameterized Pipelines
Laravel pipeline strings with parameters are supported.
Via attribute:
Via config:
Excluding Configured Pipelines
Use #[WithoutGlobalPipeline] to remove selected configured pipeline entries for a handler:
Several configured pipelines can be excluded with a repeatable attribute:
Or with several pipes in one attribute:
The exclusion is applied to the configured pipeline list resolved for the handler type: global_pipelines plus command_pipelines for commands, or global_pipelines plus query_pipelines for queries.
WithoutGlobalPipeline compares the complete pipeline entry. If config contains a parameterized entry, you must exclude the same string:
This will not remove it:
Use the full entry instead:
Use #[WithoutAllGlobalPipelines] to remove all configured pipelines for this handler and keep only local #[Pipeline] entries:
Pipeline classes should be valid handler-like classes: instantiable classes with public non-static handle() methods.
Pipelines are supported for command and query handlers only. Internal handlers are not individually wrapped with pipelines.
Cache And Production Mode
In development, runtime discovery is convenient: the mediator scans configured paths and builds handler maps automatically.
In production, cache mode is always preferred. It compiles discovered handlers, internal handlers, output channels, and pipeline lists into one PHP cache file, so the application can load the prepared map instead of running discovery and reflection during runtime.
Create the cache:
Clear the cache:
These commands are also wired into Laravel optimization:
optimize runs mediator cache generation, and optimize:clear removes the mediator cache.
Recommended production deploy flow:
If handlers, attributes, output channels, pipeline attributes, configured pipelines, or discovery paths change, rebuild the mediator cache.
Console Commands
Generator commands:
By default, generated handlers are placed under App\Mediator and receive the Handler suffix. Both behaviors can be changed in config/mediator.php.
Cache commands:
Published stubs can be customized in the application's stubs directory:
Laravel Octane
The package works with Laravel Octane. The mediator is registered as a singleton and can be warmed when an Octane worker starts.
To warm the mediator service, add it to warm in config/octane.php:
This makes the worker resolve the mediator up front instead of doing it on the request.
Facade And Contract
For application code, prefer injecting the contract:
The facade is convenient for examples, tests, seeders, small scripts, or places where facade style is already used:
Both expose:
Error Handling
Common exceptions:
DifferentUniverse\CqbusMediator\Exceptions\HandlerNotFoundException
Thrown when no command/query handler exists for an input channel or message class, or when an output channel references a missing internal handler.
Typical causes:
- handler is outside
discovery_paths; - cache is stale;
- message class does not match the handler input channel;
- string channel has a typo;
- output channel points to a missing internal handler.
DifferentUniverse\CqbusMediator\Exceptions\InvalidHandlerException
Thrown during discovery/cache generation when a handler or pipeline class is structurally invalid.
Typical causes:
- handler class is abstract or otherwise not instantiable;
- missing
handle()method; handle()is private, protected, or static;- command/query
handle()has more than one parameter; - automatic typed channel uses nullable, builtin, interface, missing, or intersection types;
- duplicate input channel;
- duplicate internal handler input channel;
- multiple handler attributes on one class;
- internal handler has an empty input channel.
RuntimeException
Thrown by the mediator when an internal output channel chain becomes circular.
Testing
Feature-test a full mediator flow:
Unit-test a handler directly when you want a narrow test:
Replace handler dependencies through the Laravel container:
Test a flow with pipelines by configuring discovery paths and pipeline config in the test:
For internal handler chains, assert the final result or observable side effects:
When tests modify mediator config or cache files, reset that state between tests. In particular, call MediatorConfig::flushCachedPipelines() after changing configured pipelines, clear facade/container instances when needed, and remove bootstrap/cache/mediator.php if a test creates it.
Best Practices
- Keep controllers thin and HTTP-focused.
- Model one handler as one application use case.
- Use commands for operations that change state.
- Use queries for read operations.
- Prefer typed message classes for main application flows.
- Use string channels for integration, generic payload, or special scenarios where a message class would add noise.
- Use pipelines for cross-cutting concerns: transactions, logging, retries, tenancy, metrics, and query caching.
- Keep handlers small enough to name after the use case.
- Avoid broad service classes that collect unrelated behavior.
- Do not overuse internal handler chains. They are useful for clear post-processing, but long hidden workflows become hard to read.
- Use mediator cache in production.
- In Octane applications, warm the mediator service when you want it resolved during worker startup.
License
Laravel CQBus Mediator is open-sourced software licensed under the MIT license.
All versions of laravel-cqbus-mediator with dependencies
illuminate/container Version ^11.0 || ^12.0 || ^13.0
illuminate/console Version ^11.0 || ^12.0 || ^13.0
illuminate/contracts Version ^11.0 || ^12.0 || ^13.0
illuminate/pipeline Version ^11.0 || ^12.0 || ^13.0
illuminate/support Version ^11.0 || ^12.0 || ^13.0
spatie/php-structure-discoverer Version ^2.3