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

v2.1.3 2025-08-13 04:54 UTC

README

run-tests Check & fix styling

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

  1. 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 [
             //
         ];
     }
 }
  1. A handler class that will process your command/query. This class must have a public handle method 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);
    }
}
  1. send the Request You can inject the Mediator interface 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 AsAction trait:
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 AsAction trait.

How to use global pipelines

  1. Define your pipelines class:
    The pipeline should implement a handle($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);
        }
    }
  2. 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.