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.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
This package is auto-updated.
Last update: 2026-02-28 23:52:00 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.
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 ... }
๐ Pipelines (Middleware)
Pipelines allow you to wrap your Handlers in logic (Transactions, Logging, Caching).
1. Global Pipelines
Run before every handler dispatched via send(). Register in config/mediator.php.
// config/mediator.php return [ 'global_pipelines' => [ \App\Pipelines\LoggingPipeline::class, ], ];
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.