ignaciocastro0713 / cqbus-mediator
A modern CQRS Mediator for Laravel using PHP 8 Attributes, auto-discovery, and routing pipelines.
Package info
github.com/IgnacioCastro0713/cqbus-mediator
pkg:composer/ignaciocastro0713/cqbus-mediator
Requires
- php: ^8.2
- illuminate/container: ^11.0 || ^12.0
- illuminate/contracts: ^11.0 || ^12.0
- illuminate/filesystem: ^11.0 || ^12.0
- illuminate/pipeline: ^11.0 || ^12.0
- illuminate/support: ^11.0 || ^12.0
- spatie/php-structure-discoverer: ^2.3
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.84
- mockery/mockery: ^1.6
- orchestra/testbench: ^9.0 || ^10.0
- pestphp/pest: ^2.11|^3.0|^3.8
- phpbench/phpbench: ^1.4
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^9.5|^10.0|^11.5.3
- dev-main
- v6.1.0
- v6.0.1
- v6.0.0
- v5.4.1
- v5.4.0
- v5.3.0
- v5.2.0
- v5.1.0
- v5.0.0
- v4.1.0
- v4.0.1
- v4.0.0
- v3.1.0
- v3.0.0
- v2.4.1
- v2.4.0
- v2.3.0
- v2.2.1
- v2.2.0
- v2.1.3
- v2.1.2
- v2.1.1
- v2.1.0
- v2.0.1
- v2.0.0
- v1.5.4
- v1.5.3
- v1.5.2
- v1.5.1
- v1.5.0
- v1.4.1
- v1.4.0
- v1.3.5
- v1.3.4
- v1.3.3
- v1.3.2
- v1.3.1
- v1.3.0
- v1.2.0
- v1.1.2
- v1.1.1
- v1.1.0
- v1.0.1
- v1.0.0
- dev-chore/add-gemini-skills
- dev-feature/6.1
This package is auto-updated.
Last update: 2026-03-29 03:14:42 UTC
README
CQBus Mediator is a modern, zero-configuration Command/Query Bus for Laravel. It simplifies your application architecture by decoupling controllers from business logic using the Mediator pattern (CQRS), PHP 8 Attributes, and elegant routing pipelines.
๐ช The Problem it Solves
โ Before (The Fat Controller)
Bloated, hard to test, and mixes HTTP logic with business logic and side effects.
class UserController extends Controller { public function register(Request $request) { $request->validate(['email' => 'required|email', 'password' => 'required']); DB::beginTransaction(); try { $user = User::create($request->all()); Mail::to($user)->send(new WelcomeEmail()); Log::info("User registered"); DB::commit(); return response()->json($user, 201); } catch (\Exception $e) { DB::rollBack(); throw $e; } } }
โ After (CQBus Mediator + Attributes)
Clean, modular, heavily decoupled, and 100% testable.
use Ignaciocastro0713\CqbusMediator\Attributes\Routing\Api; use Ignaciocastro0713\CqbusMediator\Attributes\Pipelines\Pipeline; #[Api] #[Pipeline(DatabaseTransactionPipeline::class)] // Handles DB Transactions automatically class RegisterUserAction { use AsAction; public function __construct(private readonly Mediator $mediator) {} public static function route(Router $router): void { $router->post('/register'); // Route lives with the action } public function handle(RegisterUserRequest $request): JsonResponse { // 1. Validation happens automatically in FormRequest // 2. Logic is executed by the decoupled Handler $user = $this->mediator->send($request); // 3. Side effects (Emails, Logs) are broadcasted to Notifications $this->mediator->publish(new UserRegisteredEvent($user)); return response()->json($user, 201); } }
๐ Official Documentation
For comprehensive guides, API references, and advanced usage examples, please visit our official documentation site.
๐ Read the CQBus Mediator Documentation
๐ Table of Contents
- โจ Why use this package?
- ๐ Installation
- ๐ง Core Concepts
- โก Quick Start (Command/Query)
- ๐ข Event Bus (Publish/Subscribe)
- ๐ฎ Routing & Actions
- ๐ Pipelines (Middleware)
- ๐งช Testing Fakes
- ๐ Console Commands
- ๐ Production & Performance
- ๐ ๏ธ Development
โจ Why use this package?
- โก Zero Config: Automatically discovers Handlers and Events using PHP Attributes (
#[RequestHandler],#[Notification]). - ๐ข Dual Pattern Support: Seamlessly handle both Command/Query (one-to-one) and Event Bus (one-to-many) patterns.
- ๐ ๏ธ Scaffolding: Artisan commands to generate Requests, Handlers, Events, and Actions instantly.
- ๐ Flexible Pipelines: Apply middleware-like logic globally or specifically to handlers using the
#[Pipeline]attribute. - ๐ฎ Attribute Routing: Manage routes, prefixes, and middleware directly in your Action classesโno more bloated route files.
- ๐ Production Ready: Includes a high-performance cache system that eliminates discovery and Reflection overhead in production.
- ๐ Container Native: Everything is resolved through the Laravel Container, supporting full Dependency Injection and Route Model Binding.
๐ Installation
Install via Composer:
composer require ignaciocastro0713/cqbus-mediator
The package is auto-discovered. You can optionally publish the config file:
php artisan vendor:publish --tag=mediator-config
Tip: If you use a custom architecture like DDD (e.g., a
src/orDomain/folder instead ofapp/), you can tell the Mediator where to discover your handlers by updating thehandler_pathsarray in the publishedconfig/mediator.php.
๐ง Core Concepts
This package supports two main architectural patterns out of the box.
1. Command / Query Pattern (1-to-1)
Use send() to dispatch a Request (Command or Query) to exactly one Handler.
graph LR
A[Action / Controller] -- "send($request)" --> B((Mediator))
B -- "runs through" --> C{Pipelines}
C -- "handled by" --> D[Handler]
D -- "returns data" --> A
Loading
2. Event Bus Pattern (1-to-N)
Use publish() to broadcast an Event to multiple Notifications.
graph LR
A[Action / Logic] -- "publish($event)" --> B((Mediator))
B --> C[Handler 1]
B --> D[Handler 2]
B --> E[Handler 3]
Loading
โก Quick Start (Command/Query)
1. Scaffold your Logic
Stop writing boilerplate. Generate a Request, Handler, and Action in one command:
php artisan make:mediator-handler RegisterUserHandler --action
This creates:
app/Http/Handlers/RegisterUser/RegisterUserRequest.phpapp/Http/Handlers/RegisterUser/RegisterUserHandler.phpapp/Http/Handlers/RegisterUser/RegisterUserAction.php
Note: If you only need an Action (without a separate Handler), you can use:
php artisan make:mediator-action RegisterUserAction
2. Define the Request
The Request class is a standard Laravel FormRequest or a simple DTO.
namespace App\Http\Handlers\RegisterUser; use Illuminate\Foundation\Http\FormRequest; class RegisterUserRequest extends FormRequest { public function rules(): array { return ['email' => 'required|email', 'password' => 'required|min:8']; } }
3. Write the Logic (Handler)
The handler contains your business logic. It's automatically linked to the Request via the #[RequestHandler] attribute.
namespace App\Http\Handlers\RegisterUser; use App\Models\User; use Ignaciocastro0713\CqbusMediator\Attributes\Handlers\RequestHandler; #[RequestHandler(RegisterUserRequest::class)] class RegisterUserHandler { public function handle(RegisterUserRequest $request): User { return User::create($request->validated()); } }
๐ข Event Bus (Publish/Subscribe)
Multiple handlers can respond to the same event.
1. Scaffold your Event Logic
php artisan make:mediator-notification UserRegisteredNotification
2. Create Notifications
Use priority to control execution order (higher = runs first). Priority defaults to 0.
use Ignaciocastro0713\CqbusMediator\Attributes\Handlers\Notification; use App\Http\Events\UserRegistered\UserRegisteredEvent; #[Notification(UserRegisteredEvent::class, priority: 3)] class SendWelcomeEmailNotification { public function handle(UserRegisteredEvent $event): void { Mail::to($event->email)->send(new WelcomeEmail()); } } #[Notification(UserRegisteredEvent::class)] class LogUserRegistrationNotification { public function handle(UserRegisteredEvent $event): void { Log::info("User registered: {$event->userId}"); } }
3. Publish and Get Results
publish() returns an array of return values keyed by the handler class name.
$results = $this->mediator->publish(new UserRegisteredEvent($userId, $email));
๐ฎ Routing & Actions
We highly recommend the Action Pattern with our attribute routing.
The "Action" Pattern (Recommended)
Use the generated Action class as a Single Action Controller. By using the AsAction trait and the #[Api] attribute, the package automatically handles routing and middleware.
use Ignaciocastro0713\CqbusMediator\Attributes\Routing\Api; use Ignaciocastro0713\CqbusMediator\Contracts\Mediator; use Ignaciocastro0713\CqbusMediator\Traits\AsAction; use Illuminate\Http\JsonResponse; use Illuminate\Routing\Router; #[Api] // โก Applies 'api' middleware group AND 'api/' prefix automatically class RegisterUserAction { use AsAction; public function __construct(private readonly Mediator $mediator) {} public static function route(Router $router): void { // Final route: POST /api/register $router->post('/register'); } public function handle(RegisterUserRequest $request): JsonResponse { $user = $this->mediator->send($request); return response()->json($user, 201); } }
Route Model Binding
The package fully supports Laravel's Implicit Route Model Binding in your Action's handle method.
#[Api] class UpdateUserAction { use AsAction; public static function route(Router $router): void { // Parameter {user} matches $user in handle() $router->put('/users/{user}'); } public function handle(UpdateUserRequest $request, User $user): JsonResponse { // $user is automatically resolved from the database $updatedUser = $this->mediator->send($request); return response()->json($updatedUser); } }
Available Routing Attributes
โ ๏ธ Important: Every Action class must have either the
#[Api]or#[Web]attribute to define its base routing context. If omitted, the action will not be discovered and its routes will not be registered.
#[Api]: Applies theapimiddleware group and prependsapi/to the URI.#[Web]: Applies thewebmiddleware group.#[Prefix('v1')]: Prefixes the route URI. Can be combined with#[Api].#[Name('route.name')]: Sets the route name or appends to a prefix when a route name is defined in theroutemethod.#[Middleware(['auth:sanctum'])]: Applies custom middleware.#[Priority(10, group: 'users')]: Sets the registration priority. Groups are registered alphabetically, and actions within each group are sorted by priority (higher = registered earlier).
Example combining attributes:
use Ignaciocastro0713\CqbusMediator\Attributes\Routing\Api; use Ignaciocastro0713\CqbusMediator\Attributes\Routing\Middleware; use Ignaciocastro0713\CqbusMediator\Attributes\Routing\Name; use Ignaciocastro0713\CqbusMediator\Attributes\Routing\Prefix; use Ignaciocastro0713\CqbusMediator\Traits\AsAction; use Illuminate\Routing\Router; #[Api] #[Prefix('v1/orders')] #[Name('orders.')] #[Middleware(['auth:sanctum'])] class CreateOrderAction { use AsAction; public static function route(Router $router): void { // Final Route: POST /api/v1/orders // Route Name: orders.create // Middleware: api, auth:sanctum $router->post('/')->name('create'); } // ... handle method ... }
Route Registration Order (Priority)
If you have conflicting routes (like /api/users/current and /api/users/{user}), you can control the registration order using the #[Priority] attribute. By default, routes are registered in descending order (highest priority first).
Priority Groups (Contextual Sorting)
For large projects, you can use the group argument to create isolated sorting contexts. Groups are ordered alphabetically first, and then the actions within that group are sorted by their priority integer.
// Actions without a group (globals) are always registered first. #[Priority(10)] class A {} // Grouped actions are registered after globals, sorted alphabetically by group name ('billing' before 'users'). #[Priority(10, group: 'billing')] class B {} #[Priority(20, group: 'users')] // Sorted higher within the 'users' group class C {} #[Priority(10, group: 'users')] class D {}
Note: If two actions share the exact same group and priority, the Mediator uses their class name as a deterministic tie-breaker.
Note: You can change the global sorting direction (asc/desc) in config/mediator.php using the route_priority_direction key.
๐ Pipelines (Middleware)
Pipelines allow you to wrap your Handlers in logic (Transactions, Logging, Caching).
1. Global, Request & Notification Pipelines
Configure pipelines in config/mediator.php. You can choose exactly when they run:
global_pipelines: Run for BOTH Requests and Notifications.request_pipelines: Run ONLY for Commands/Queries. (Ideal for DB Transactions).notification_pipelines: Run ONLY for Events.
// config/mediator.php return [ 'global_pipelines' => [ \App\Pipelines\LoggingPipeline::class, ], 'request_pipelines' => [ \App\Pipelines\DatabaseTransactionPipeline::class, ], 'notification_pipelines' => [], ];
A pipeline class is just an invokable class (like a Laravel Middleware):
namespace App\Pipelines; use Closure; use Illuminate\Support\Facades\Log; class LoggingPipeline { public function handle(mixed $request, Closure $next): mixed { Log::info('Handling request: ' . get_class($request)); $response = $next($request); Log::info('Request handled successfully'); return $response; } }
2. Handler-level Pipelines
Apply to specific handlers using the #[Pipeline] attribute.
use Ignaciocastro0713\CqbusMediator\Attributes\Pipelines\Pipeline; use Ignaciocastro0713\CqbusMediator\Attributes\Handlers\RequestHandler; #[RequestHandler(CreateOrderRequest::class)] #[Pipeline(TransactionPipeline::class)] class CreateOrderHandler { public function handle(CreateOrderRequest $request): Order { // Runs inside a database transaction return Order::create($request->validated()); } }
3. Skipping Global Pipelines
Use #[SkipGlobalPipelines] to bypass global middleware for specific handlers.
use Ignaciocastro0713\CqbusMediator\Attributes\Handlers\RequestHandler; use Ignaciocastro0713\CqbusMediator\Attributes\Pipelines\SkipGlobalPipelines; #[RequestHandler(HealthCheckRequest::class)] #[SkipGlobalPipelines] class HealthCheckHandler { public function handle(HealthCheckRequest $request): string { return 'OK'; // Bypasses global logging or transactions } }
๐งช Testing Fakes
CQBus Mediator provides a built-in fake via the Mediator facade to easily test your application's behavior without executing complex logic.
use Ignaciocastro0713\CqbusMediator\Facades\Mediator; it('dispatches the correct request', function () { Mediator::fake(); $this->postJson('/api/register', [...]); Mediator::assertSent(RegisterUserRequest::class); });
๐ Console Commands
The package provides several Artisan commands to speed up your workflow and manage the mediator.
๐ ๏ธ Generation Commands
Scaffold your classes instantly. All generation commands support a --root option to change the base directory (e.g., --root=Domain/Users).
| Command | Description | Options |
|---|---|---|
make:mediator-handler |
Creates a Request and Handler class. | --action (adds Action), --root=Dir |
make:mediator-action |
Creates an Action and Request class. | --root=Dir |
make:mediator-notification |
Creates an Event and its Handler class. | --root=Dir |
Examples:
# Uses default root folder (Handlers/) php artisan make:mediator-handler RegisterUserHandler --action # Changes root folder to Orders/ php artisan make:mediator-action CreateOrderAction --root=Orders # Changes root folder to Domain/Events/ php artisan make:mediator-notification UserRegisteredNotification --root=Domain/Events
๐ Information Commands
mediator:list
View all discovered or cached handlers, notifications, and actions.
php artisan mediator:list
Options:
--handlers: List only Request Handlers.--events: List only Notifications.--actions: List only Actions.
๐ Production Optimization
Cache discovery results in production to eliminate file-system and Reflection overhead.
php artisan mediator:cache # Creates the cache php artisan mediator:clear # Clears the cache
๐ Production & Performance
| Benchmark | Source / Dev Mode | Cached (Production) | Improvement |
|---|---|---|---|
| Discovery (Boot Phase) | ~157.00 ms | ~0.06 ms | ~2,500x Faster |
| Reflection / Attribute Reading | ~16.00 ฮผs | ~4.00 ฮผs | ~4x Faster |
Simple Dispatch (send) |
- | ~68.00 ฮผs | Near Zero Overhead |
๐ ๏ธ Development
Requirements
- PHP 8.2+
- Laravel 11.0+ (and above)
- Composer
Available Commands
| Command | Description |
|---|---|
composer test |
Run tests with Pest |
composer ci |
Run format check + static analysis + tests |
composer analyse |
Static analysis with PHPStan (level 10) |
composer format |
Fix code style with PHP CS Fixer |
composer benchmark |
Run performance benchmarks |
Project Structure
src/
โโโ Attributes/ # PHP Attributes (Subdivided by context)
โ โโโ Handlers/ # #[RequestHandler], #[Notification]
โ โโโ Pipelines/ # #[Pipeline], #[SkipGlobalPipelines]
โ โโโ Routing/ # #[Api], #[Web], #[Prefix], #[Name], #[Middleware]
โโโ Console/ # Artisan commands (Cache, Clear, List, Make)
โ โโโ stubs/ # Stub files for code generation
โโโ Contracts/ # Interfaces (Mediator, RouteModifier)
โโโ Discovery/ # Discovery logic for Handlers and Actions
โโโ Routing/ # ActionDecoratorManager and RouteOptions
โโโ Services/ # MediatorService implementation
โโโ Support/ # MediatorFake and helpers
โโโ Traits/ # AsAction trait
tests/
โโโ Architecture/ # Pest Architecture tests
โโโ Feature/ # Feature/Integration tests
โโโ Fixtures/ # Test fixtures
โโโ Unit/ # Unit tests
๐ค Contributing
Feel free to open issues or submit pull requests on the GitHub repository.
๐ License
This package is open-sourced software licensed under the MIT license.