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.
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;
}
}
}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);
}
}For comprehensive guides, API references, and advanced usage examples, please visit our official documentation site.
👉 Read the CQBus Mediator Documentation
- ✨ 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
- ⚡ 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.
Install via Composer:
composer require ignaciocastro0713/cqbus-mediatorThe package is auto-discovered. You can optionally publish the config file:
php artisan vendor:publish --tag=mediator-configTip: 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.
This package supports two main architectural patterns out of the box.
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
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]
Stop writing boilerplate. Generate a Request, Handler, and Action in one command:
php artisan make:mediator-handler RegisterUserHandler --actionThis 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
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'];
}
}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());
}
}Multiple handlers can respond to the same event.
php artisan make:mediator-notification UserRegisteredNotificationUse 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}");
}
}publish() returns an array of return values keyed by the handler class name.
$results = $this->mediator->publish(new UserRegisteredEvent($userId, $email));We highly recommend the Action Pattern with our attribute routing.
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);
}
}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);
}
}
⚠️ 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 ...
}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).
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 allow you to wrap your Handlers in logic (Transactions, Logging, Caching).
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;
}
}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());
}
}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
}
}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);
});The package provides several Artisan commands to speed up your workflow and manage the mediator.
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/EventsView all discovered or cached handlers, notifications, and actions.
php artisan mediator:listOptions:
--handlers: List only Request Handlers.--events: List only Notifications.--actions: List only Actions.
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| 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 |
- PHP 8.2+
- Laravel 11.0+ (and above)
- Composer
| 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 |
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
Feel free to open issues or submit pull requests on the GitHub repository.
This package is open-sourced software licensed under the MIT license.
