ignaciocastro0713 / cqbus-mediator
A Mediator for Laravel
Installs: 21
Dependents: 0
Suggesters: 0
Security: 0
Stars: 2
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/ignaciocastro0713/cqbus-mediator
Requires
- php: ^8.1
- illuminate/container: ^10.0 || ^11.0 || ^12.0
- illuminate/contracts: ^10.0 || ^11.0 || ^12.0
- illuminate/filesystem: ^10.0 || ^11.0 || ^12.0
- illuminate/pipeline: ^10.0 || ^11.0 || ^12.0
- illuminate/support: ^10.0 || ^11.0 || ^12.0
- spatie/php-structure-discoverer: ^2.3
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.84
- mockery/mockery: ^1.6
- orchestra/testbench: ^8.0|^9.0|^10.0
- pestphp/pest: ^2.11|^3.0|^3.8
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^9.5|^10.0|^11.5.3
README
A simple, extensible, and configurable Command/Query Bus (Mediator) implementation for Laravel applications. This package helps you implement the Command/Query Responsibility Segregation (CQRS) with Mediator pattern, promoting cleaner, more maintainable code by separating concerns and decoupling components.
โจ Features
-
Attribute-Based Handler Discovery: Define your command/query handlers using a simple PHP 8 attribute (
#[RequestHandler]). -
Configuration-Driven Scanning: Easily configure the directories where your handlers are located via a dedicated configuration file.
-
Automatic Dependency Injection: Handlers are resolved from the Laravel service container, allowing for seamless dependency injection.
-
Clear Separation of Concerns: Decouples the sender from the handlers, improving testability and code organization.
-
Global pipelines (middleware) support: that will apply to every request sent through the Mediator
๐ Installation
You can install this package via Composer.
Require the Package:
composer require ignaciocastro0713/cqbus-mediator
The Service Provider will be automatically registered. If you wish, you can publish the configuration file:
php artisan vendor:publish --provider="Ignaciocastro0713\CqbusMediator\MediatorServiceProvider"
Publish the Configuration File: The package comes with a configurable file that allows you to define handler discovery paths. Publish it using the Artisan command:
php artisan vendor:publish --tag=mediator-config
This will create config/mediator.php in your Laravel application.
โ๏ธ Configuration (config/mediator.php)
After publishing, you'll find a mediator.php file in your config directory. This file is crucial for discovers your
handlers.
<?php return [ /* |-------------------------------------------------------------------------- | Handler Discovery Paths |-------------------------------------------------------------------------- | | This handler_paths defines the directories where the MediatorService will scan | for command/request handlers. These paths are relative to your Laravel | application's base directory (typically the 'app' directory). | | Example: 'handler_paths' => app_path('Features'), would scan 'app/Features/Commands'. or | 'handler_paths' => [app_path('Features'), app_path('UseCases')] | */ 'handler_paths' => app_path(), /* |-------------------------------------------------------------------------- | Global Pipelines |-------------------------------------------------------------------------- | | The global pipelines that will be applied to every request | sent through the Mediator. Each class should have a handle($request, Closure $next) method. | | Example Pipeline definition: | class LoggingPipeline | { | public function handle($request, Closure $next) | { | Log::info('Handling Request pipeline', ['request' => $request]); | | $response = $next($request); | | Log::info('Handled Request pipeline', ['request' => $request]); | | return $response; | } | } | | Example configuration: | 'pipelines' => [ | App\Pipelines\LoggingMiddleware::class, | ] | | for more information: https://laravel.com/docs/helpers#pipeline */ 'pipelines' => [], ];
Important: Adjust handler_paths to include all directories where your command/query handlers are located.
๐ ๏ธ Generating Handlers and Requests
To simplify the creation of new handler and request classes, the package includes an Artisan command that generates both files and organizes them into a clean folder structure.
The command is called make:mediator-handler and has the following signature:
php artisan make:mediator-handler {name} {--root=Handlers} {--action}
Arguments and Options
-
name: The name of the handler you want to create. It must end with the word Handler. -
--root(optional): Defines the main folder path inside app/Http/. By default, it is Handlers folder. -
--action(optional): Defines an additional action class.
๐ Usage
- A Request that encapsulates the data needed for an operation.
<?php namespace App\Http\UseCases\User\GetUsers; use Illuminate\Foundation\Http\FormRequest; class GetUsersRequest extends FormRequest { /** * Determine if the user is authorized to make this request. */ public function authorize(): bool { return true; } /** * Get the validation rules that apply to the request. * * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string> */ public function rules(): array { return [ // ]; } }
- A handler class that will process your command/query. This class must have a public
handlemethod and be decorated with the#[RequestHandler]attribute.
<?php namespace App\Http\UseCases\User\GetUsers; use App\Repositories\UserRepositoryInterface; use Ignaciocastro0713\CqbusMediator\Attributes\RequestHandler; #[RequestHandler(GetUsersRequest::class)] class GetUsersHandler { function __construct(private readonly UserRepositoryInterface $userRepository) { } public function handle(GetUsersRequest $request): array { $users = $this->userRepository->getUsers(); if ($request->has("name")) { $users = array_filter($users, fn($user) => $user === $request->query("name")); } return array_values($users); } }
- send the Request
You can inject the
Mediatorinterface into your controllers, services, or anywhere you need to send a command or query.
<?php namespace App\Http\Controllers; use App\Handlers\Users\Queries\GetUsers\GetUsersQuery; use Ignaciocastro0713\CqbusMediator\Contracts\Mediator; // Import the Mediator interface use Illuminate\Http\Request; use Illuminate\Routing\Controller; class UserController extends Controller { public function __construct(private readonly Mediator $mediator) { } public function index(GetUsersRequest $request): JsonResponse { $users = $this->mediator->send($request); return response()->json($users); } public function store(CreateUserRequest $request) { $result = $this->mediator->send($request); return response()->json($result, 201); } }
๐ Usage Handler With Action Controller
- You can create your own Action class using the
AsActiontrait:
namespace App\Http\UseCases\User\GetUsersAction; use Ignaciocastro0713\CqbusMediator\Traits\AsAction; class GetUsersAction { use AsAction; public function __construct(private readonly Mediator $mediator) { } public function handle(GetUsersRequest $request): JsonResponse { $users = $this->mediator->send($request); return response()->json($users); } }
- Register the action class as a controller in your routes file:
Route::get('/users', GetUsersAction::class);
- Or Registering routes directly in the action:
class GetUsersAction { use AsAction; public static function route(Router $router): void { $router->get('api/users', static::class); } /*..*/ }
Now, all routes using actions with the AsAction trait can be registered as route
Note:
- The service provider will only decorate controllers that use the
AsActiontrait.
How to use global pipelines
-
Define your pipelines class:
The pipeline should implement ahandle($request, \Closure $next)method.namespace App\Pipelines; class LoggingPipeline { public function handle($request, \Closure $next) { \Log::info('Mediator request received', ['request' => $request]); return $next($request); } }
-
Register your pipelines in
config/mediator.php:return [ 'handler_paths' => app_path(), 'pipelines' => [ App\Pipelines\LoggingPipeline::class, // Add more pipelines classes here ], ];
โ๏ธ Configuration and Optimization for Production
To get the most out of the package, it is highly recommended to use the new caching commands in production.
Artisan Commands
The package provides two commands:
php artisan mediator:cache: Scans your handler directories and creates an optimized cache file in
bootstrap/cache/mediator_handlers.php.
php artisan mediator:clear: Deletes the cache file.
Recommended Deployment Workflow
To ensure your application in production always runs with maximum performance, integrate the following steps into your deployment script:
# Clear any existing handler cache (if any) php artisan mediator:clear # Generate the new cache file with the latest handlers php artisan mediator:cache
This way, the expensive file scanning is performed only once during the build process, and the runtime in production is optimized for every request.
๐งช Running Tests
To run the tests for this package, use the following command in your project root:
vendor/bin/pest
๐จ Fixing Code Style
To fix the code style (including risky rules), run:
./vendor/bin/php-cs-fixer fix --allow-risky=yes
๐ค 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.