samir-hussein/laravel-cqrs

A clean CQRS (Command Query Responsibility Segregation) pattern implementation for Laravel applications

Installs: 6

Dependents: 0

Suggesters: 0

Security: 0

Stars: 0

Watchers: 0

Forks: 0

Open Issues: 0

pkg:composer/samir-hussein/laravel-cqrs

v1.0.0 2026-02-20 19:58 UTC

This package is auto-updated.

Last update: 2026-02-20 20:03:31 UTC


README

A clean and simple CQRS (Command Query Responsibility Segregation) pattern implementation for Laravel applications.

Features

  • Simple & Clean: Easy to understand and implement
  • Auto-resolution: Handlers are automatically resolved based on naming conventions
  • Laravel Integration: Seamless integration with Laravel's service container
  • Type-safe: Full type hints and interfaces
  • Flexible: Works with any Laravel project structure
  • Validation Support: Built-in validation methods for commands and queries
  • Middleware Pipeline: Wrap handlers with middleware for logging, transactions, authorization, etc.
  • Universal Dispatch: Single dispatch() method that works with both Commands and Queries

Installation

Step 1: Install via Composer

composer require samir-hussein/laravel-cqrs

The package will be automatically discovered by Laravel.

Step 2: Publish Configuration (Optional)

php artisan vendor:publish --tag=cqrs-config

This creates config/cqrs.php where you can configure namespaces, middleware, and handler mappings.

Step 3: Create Directory Structure (Optional)

The package will create directories automatically when you use Artisan commands. Or create them manually:

app/
├── CQRS/
│   ├── Commands/
│   ├── Queries/
│   └── Handlers/

Step 4: Use Artisan Commands (Recommended)

The package includes Artisan commands to generate CQRS files:

# Create a command
php artisan cqrs:command User/CreateUserCommand

# Create a query
php artisan cqrs:query User/GetUserQuery

# Create a command handler
php artisan cqrs:handler User/CreateUserCommandHandler --type=command

# Create a query handler
php artisan cqrs:handler User/GetUserQueryHandler --type=query

# Create a pipeline middleware
php artisan cqrs:middleware User/TransactionMiddleware

That's it! You're ready to use the package.

Example 1: Creating and Using a Command

This example shows how to create a command with validation and middleware support.

1. Create the Command

<?php

namespace App\CQRS\Commands\User;

use LaravelCQRS\Command;

class CreateUserCommand extends Command
{
    /**
     * Create a new command instance.
     *
     * @param array $data
     */
    public function __construct(array $data = [])
    {
        parent::__construct($data);
    }

    public function getName(): ?string
    {
        return $this->get('name');
    }

    public function getEmail(): ?string
    {
        return $this->get('email');
    }

    public function getPassword(): ?string
    {
        return $this->get('password');
    }

    /**
     * Define validation rules
     */
    public function rules(): array
    {
        return [
            'name' => ['required', 'string', 'max:255'],
            'email' => ['required', 'email', 'unique:users,email'],
            'password' => ['required', 'string', 'min:8'],
        ];
    }

    /**
     * Custom validation messages (optional)
     */
    public function messages(): array
    {
        return [
            'email.unique' => 'This email address is already registered.',
            'password.min' => 'Password must be at least 8 characters.',
        ];
    }
}

2. Create the Command Handler

Using Artisan (Recommended):

php artisan cqrs:handler User/CreateUserCommandHandler --type=command

Or manually create the file:

<?php

namespace App\CQRS\Handlers\User;

use App\Models\User;
use App\Repositories\UserRepository;
use LaravelCQRS\Command;
use LaravelCQRS\Commands\User\CreateUserCommand;
use LaravelCQRS\Contracts\CommandHandlerInterface;
use Illuminate\Support\Facades\Hash;

class CreateUserCommandHandler implements CommandHandlerInterface
{
    public function __construct(
        private UserRepository $userRepository
    ) {}

    public function handle(Command $command): User
    {
        /** @var CreateUserCommand $command */
        $data = $command->getData();
        
        // Hash password before creating user
        if (isset($data['password'])) {
            $data['password'] = Hash::make($data['password']);
        }

        return $this->userRepository->create($data);
    }
}

3. Configure Middleware (Optional)

Add middleware in config/cqrs.php:

'middleware' => [
    'global' => [
        \App\CQRS\Middleware\LoggingMiddleware::class,
    ],
    'App\CQRS\Commands\User\CreateUserCommand' => [
        \App\CQRS\Middleware\TransactionMiddleware::class,
        \App\CQRS\Middleware\AuthorizationMiddleware::class,
    ],
],

4. Use in Controller

<?php

namespace App\Http\Controllers;

use App\CQRS\Commands\User\CreateUserCommand;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use LaravelCQRS\CQRS;

class UserController extends Controller
{
    // Option 1: Manual instantiation (traditional way)
    public function store(Request $request): JsonResponse
    {
        // CQRS::dispatch() automatically:
        // 1. Validates the command (using rules() method)
        // 2. Applies middleware (if configured)
        // 3. Dispatches to the handler
        $user = CQRS::dispatch(new CreateUserCommand($request->all()));

        return response()->json($user, 201);
    }

    // Option 2: Automatic dependency injection (recommended)
    public function create(CreateUserCommand $command): JsonResponse
    {
        // The command is automatically instantiated with request data
        // No need to manually pass $request->all()
        $user = CQRS::dispatch($command);

        return response()->json($user, 201);
    }
}

What happens:

  1. Validation: Command data is validated using rules() method
  2. Middleware: Global and command-specific middleware are executed
  3. Handler: CreateUserCommandHandler processes the command
  4. Response: User is created and returned

Example 2: Creating and Using a Query

This example shows how to create a query with validation and middleware support.

1. Create the Query

Using Artisan (Recommended):

php artisan cqrs:query User/GetUserQuery

Or manually create the file:

<?php

namespace App\CQRS\Queries\User;

use LaravelCQRS\Query;

class GetUserQuery extends Query
{
    /**
     * Create a new query instance.
     *
     * @param array $data
     */
    public function __construct(array $data = [])
    {
        parent::__construct($data);
    }

    public function getId(): int|string|null
    {
        return $this->get('id');
    }

    /**
     * Define validation rules
     */
    public function rules(): array
    {
        return [
            'id' => ['required', 'integer', 'min:1'],
        ];
    }

    /**
     * Custom validation messages (optional)
     */
    public function messages(): array
    {
        return [
            'id.required' => 'User ID is required.',
            'id.integer' => 'User ID must be a valid number.',
        ];
    }
}

2. Create the Query Handler

Using Artisan (Recommended):

php artisan cqrs:handler User/GetUserQueryHandler --type=query

Or manually create the file:

<?php

namespace App\CQRS\Handlers\User;

use App\Models\User;
use App\Repositories\UserRepository;
use LaravelCQRS\Contracts\QueryHandlerInterface;
use LaravelCQRS\Query;
use LaravelCQRS\Queries\User\GetUserQuery;

class GetUserQueryHandler implements QueryHandlerInterface
{
    public function __construct(
        private UserRepository $userRepository
    ) {}

    public function handle(Query $query): ?User
    {
        /** @var GetUserQuery $query */
        return $this->userRepository->find($query->getId());
    }
}

3. Configure Middleware (Optional)

Add middleware in config/cqrs.php:

'middleware' => [
    'global' => [
        \App\CQRS\Middleware\LoggingMiddleware::class,
    ],
    'App\CQRS\Queries\User\GetUserQuery' => [
        \App\CQRS\Middleware\CacheMiddleware::class, // Cache query results
    ],
],

4. Use in Controller

<?php

namespace App\Http\Controllers;

use App\CQRS\Queries\User\GetUserQuery;
use Illuminate\Http\JsonResponse;
use LaravelCQRS\CQRS;

class UserController extends Controller
{
    public function show(string $id): JsonResponse
    {
        // CQRS::dispatch() automatically:
        // 1. Validates the query (using rules() method)
        // 2. Applies middleware (if configured - e.g., caching)
        // 3. Dispatches to the handler
        // Option 1: Manual instantiation
        $user = CQRS::dispatch(new GetUserQuery(['id' => $id]));

        // Option 2: Automatic dependency injection (recommended)
        // public function show(GetUserQuery $query): JsonResponse
        // {
        //     $user = CQRS::dispatch($query);
        //     return response()->json($user);
        // }

        if (!$user) {
            return response()->json(['error' => 'User not found'], 404);
        }

        return response()->json($user);
    }
}

What happens:

  1. Validation: Query parameters are validated using rules() method
  2. Middleware: Global and query-specific middleware are executed (e.g., caching)
  3. Handler: GetUserQueryHandler processes the query
  4. Response: User data is returned (or 404 if not found)

Validation

Commands and Queries support built-in validation. Override these methods in your command/query classes:

  • rules(): array - Define validation rules
  • messages(): array - Custom error messages (optional)
  • attributes(): array - Custom attribute names (optional)

The CQRS::dispatch() method automatically validates before dispatching. If validation fails, a ValidationException is thrown.

Manual validation:

$command = new CreateUserCommand($request->all());

// Check if valid
if ($command->isValid()) {
    // Get errors
    $errors = $command->errors();
}

// Or validate and get validated data
$validatedData = $command->validate();

See VALIDATION_USAGE.md for complete validation guide.

Middleware

Middleware allows you to wrap handler execution for cross-cutting concerns:

  • Logging: Log all command/query executions
  • Transactions: Wrap database operations in transactions
  • Authorization: Check permissions before execution
  • Caching: Cache query results
  • Performance Monitoring: Track execution time

Create middleware:

<?php

namespace App\CQRS\Middleware;

use LaravelCQRS\Command;
use LaravelCQRS\Contracts\MiddlewareInterface;
use LaravelCQRS\Query;
use Closure;
use Illuminate\Support\Facades\DB;

class TransactionMiddleware implements MiddlewareInterface
{
    public function handle(Command|Query $commandOrQuery, Closure $next): mixed
    {
        if ($commandOrQuery instanceof Command) {
            return DB::transaction(function () use ($commandOrQuery, $next) {
                return $next($commandOrQuery);
            });
        }
        
        return $next($commandOrQuery);
    }
}

Configure in config/cqrs.php:

'middleware' => [
    'global' => [
        \App\CQRS\Middleware\LoggingMiddleware::class,
    ],
    'App\CQRS\Commands\User\CreateUserCommand' => [
        \App\CQRS\Middleware\TransactionMiddleware::class,
    ],
],

See MIDDLEWARE_USAGE.md for complete middleware guide.

Naming Conventions

The package automatically resolves handlers based on naming:

  • Command: App\CQRS\Commands\User\CreateUserCommand

  • Handler: App\CQRS\Handlers\User\CreateUserCommandHandler

  • Query: App\CQRS\Queries\User\GetUserQuery

  • Handler: App\CQRS\Handlers\User\GetUserQueryHandler

Configuration

After installing the package, publish the configuration file:

php artisan vendor:publish --tag=cqrs-config

This creates config/cqrs.php with all available options. Here's a detailed explanation of each configuration option:

Handler Namespace

'handler_namespace' => env('CQRS_HANDLER_NAMESPACE', 'App\\CQRS\\Handlers'),

The base namespace where your handler classes are located. Used for auto-resolution when handlers follow the naming convention.

Example: If your handler is at App\CQRS\Handlers\User\CreateUserCommandHandler, set this to App\CQRS\Handlers.

Command Namespace

'command_namespace' => env('CQRS_COMMAND_NAMESPACE', 'App\\CQRS\\Commands'),

The base namespace where your command classes are located. Used to determine if a class is a command for handler resolution.

Example: If your commands are at App\CQRS\Commands\User\CreateUserCommand, set this to App\CQRS\Commands.

Query Namespace

'query_namespace' => env('CQRS_QUERY_NAMESPACE', 'App\\CQRS\\Queries'),

The base namespace where your query classes are located. Used to determine if a class is a query for handler resolution.

Example: If your queries are at App\CQRS\Queries\User\GetUserQuery, set this to App\CQRS\Queries.

Auto-resolve Handlers

'auto_resolve_handlers' => env('CQRS_AUTO_RESOLVE', true),

When true (default), handlers are automatically resolved based on naming conventions. When false, you must provide handler mappings for all commands/queries, otherwise an exception will be thrown.

Use cases:

  • Set to false if you want explicit control over handler resolution
  • Set to true for automatic resolution (recommended for most cases)

Handler Mappings

'handler_mappings' => [
    'App\CQRS\Commands\User\CreateUserCommand' => 'App\Custom\Handlers\CreateUserHandler',
    'App\CQRS\Queries\User\GetUserQuery' => 'App\Legacy\Handlers\GetUserHandler',
],

Manual mappings for commands/queries to handlers. These mappings always take precedence over auto-resolution, even when auto_resolve_handlers is true.

Use cases:

  • Custom handler locations that don't follow naming conventions
  • Override default handler resolution for specific commands/queries
  • Legacy handlers in different namespaces
  • Works alongside auto-resolution (checked first, then falls back to auto)

Important: If auto_resolve_handlers is false and no mapping exists for a command/query, a HandlerNotFoundException will be thrown.

Middleware

'middleware' => [
    'global' => [
        \App\CQRS\Middleware\LoggingMiddleware::class,
        \App\CQRS\Middleware\TransactionMiddleware::class,
    ],
    'App\CQRS\Commands\User\CreateUserCommand' => [
        \App\CQRS\Middleware\AuthorizationMiddleware::class,
    ],
    'App\CQRS\Queries\User\GetUserQuery' => [
        \App\CQRS\Middleware\CacheMiddleware::class,
    ],
],

Configure middleware for commands and queries:

  • global: Middleware that applies to all commands and queries
  • Command/Query specific: Middleware that applies only to a specific command or query class

Execution order: Global middleware runs first, then command/query-specific middleware.

Example middleware use cases:

  • Logging: Log all command/query executions
  • Transactions: Wrap database operations in transactions
  • Authorization: Check permissions before execution
  • Caching: Cache query results
  • Performance Monitoring: Track execution time

See MIDDLEWARE_USAGE.md for complete middleware guide.

Environment Variables

You can override configuration values using environment variables in your .env file:

CQRS_HANDLER_NAMESPACE=App\CQRS\Handlers
CQRS_COMMAND_NAMESPACE=App\CQRS\Commands
CQRS_QUERY_NAMESPACE=App\CQRS\Queries
CQRS_AUTO_RESOLVE=true

Artisan Commands

The package includes convenient Artisan commands to generate CQRS files:

Create a Command

php artisan cqrs:command User/CreateUserCommand

This creates: app/CQRS/Commands/User/CreateUserCommand.php

Create a Query

php artisan cqrs:query User/GetUserQuery

This creates: app/CQRS/Queries/User/GetUserQuery.php

Create a Handler

# Create a command handler
php artisan cqrs:handler User/CreateUserCommandHandler --type=command

# Create a query handler
php artisan cqrs:handler User/GetUserQueryHandler --type=query

This creates:

  • app/CQRS/Handlers/User/CreateUserCommandHandler.php (for commands)
  • app/CQRS/Handlers/User/GetUserQueryHandler.php (for queries)

Note: The --type option is required for handlers to determine which interface and base class to use.

Create a Middleware

php artisan cqrs:middleware User/TransactionMiddleware

This creates: app/CQRS/Middleware/User/TransactionMiddleware.php (implements LaravelCQRS\Contracts\MiddlewareInterface). Register it in config/cqrs.php under middleware.global or per command/query.

Using Buses Directly

You can also use the buses directly instead of the CQRS helper:

use LaravelCQRS\Bus\CommandBus;
use LaravelCQRS\Bus\QueryBus;

// In controller
public function __construct(
    private CommandBus $commandBus,
    private QueryBus $queryBus
) {}

public function store(Request $request): JsonResponse
{
    $command = new CreateUserCommand($request->all());
    $command->validate(); // Manual validation
    $user = $this->commandBus->dispatch($command);
    return response()->json($user, 201);
}

Architecture Flow

Route → Controller → CQRS::dispatch() → Validation → Middleware Pipeline → Handler → Repository

Requirements

  • PHP >= 8.0
  • Laravel >= 9.0

License

MIT

Support

For issues and questions, please open an issue on GitHub.