A clean and simple CQRS (Command Query Responsibility Segregation) pattern implementation for Laravel applications.
- ✅ 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
composer require samir-hussein/laravel-cqrsThe package will be automatically discovered by Laravel.
php artisan vendor:publish --tag=cqrs-configThis creates config/cqrs.php where you can configure namespaces, middleware, and handler mappings.
The package will create directories automatically when you use Artisan commands. Or create them manually:
app/
├── CQRS/
│ ├── Commands/
│ ├── Queries/
│ └── Handlers/
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=queryThat's it! You're ready to use the package.
This example shows how to create a command with validation and middleware support.
<?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.',
];
}
}Using Artisan (Recommended):
php artisan cqrs:handler User/CreateUserCommandHandler --type=commandOr 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);
}
}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,
],
],<?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
{
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);
}
}What happens:
- ✅ Validation: Command data is validated using
rules()method - ✅ Middleware: Global and command-specific middleware are executed
- ✅ Handler:
CreateUserCommandHandlerprocesses the command - ✅ Response: User is created and returned
This example shows how to create a query with validation and middleware support.
Using Artisan (Recommended):
php artisan cqrs:query User/GetUserQueryOr 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.',
];
}
}Using Artisan (Recommended):
php artisan cqrs:handler User/GetUserQueryHandler --type=queryOr 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());
}
}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
],
],<?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
$user = CQRS::dispatch(new GetUserQuery(['id' => $id]));
if (!$user) {
return response()->json(['error' => 'User not found'], 404);
}
return response()->json($user);
}
}What happens:
- ✅ Validation: Query parameters are validated using
rules()method - ✅ Middleware: Global and query-specific middleware are executed (e.g., caching)
- ✅ Handler:
GetUserQueryHandlerprocesses the query - ✅ Response: User data is returned (or 404 if not found)
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 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.
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
The configuration file (config/cqrs.php) includes:
-
handler_namespace: Base namespace for handlers (default:App\CQRS\Handlers) -
command_namespace: Base namespace for commands (default:App\CQRS\Commands) -
query_namespace: Base namespace for queries (default:App\CQRS\Queries) -
auto_resolve_handlers: Enable/disable auto-resolution (default:true) -
handler_mappings: Manual mappings for custom handler locations -
middleware: Global and command/query-specific middleware configuration
The package includes convenient Artisan commands to generate CQRS files:
php artisan cqrs:command User/CreateUserCommandThis creates: app/CQRS/Commands/User/CreateUserCommand.php
php artisan cqrs:query User/GetUserQueryThis creates: app/CQRS/Queries/User/GetUserQuery.php
# Create a command handler
php artisan cqrs:handler User/CreateUserCommandHandler --type=command
# Create a query handler
php artisan cqrs:handler User/GetUserQueryHandler --type=queryThis 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.
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);
}Route → Controller → CQRS::dispatch() → Validation → Middleware Pipeline → Handler → Repository
- PHP 8.2+
- Laravel 10.0+ | 11.0+ | 12.0+
MIT
For issues and questions, please open an issue on GitHub.