Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Cqrs Laravel Package

digital-craftsman/cqrs

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation

    composer require digital-craftsman/cqs-routing
    

    Add the config file to config/packages/cqs-routing.php (see README for template).

  2. First Use Case: Command/Query Handling Define a command/query class (e.g., CreateUserCommand):

    namespace App\Application\Commands;
    
    class CreateUserCommand
    {
        public function __construct(
            public string $name,
            public string $email
        ) {}
    }
    

    Register a handler (implement CommandHandlerInterface):

    namespace App\Application\Handlers;
    
    use App\Application\Commands\CreateUserCommand;
    use DigitalCraftsman\CQSRouting\CommandHandlerInterface;
    
    class CreateUserCommandHandler implements CommandHandlerInterface
    {
        public function __invoke(CreateUserCommand $command): void
        {
            // Business logic here
        }
    }
    
  3. Route Configuration Add a route in routes/api.php:

    use DigitalCraftsman\CQSRouting\CQSRouting;
    use App\Application\Commands\CreateUserCommand;
    
    Route::post('/users', CreateUserCommand::class)
        ->middleware(CQSRouting::class);
    
  4. Request Payload Send a JSON payload matching the command structure:

    {
        "name": "John Doe",
        "email": "john@example.com"
    }
    

Implementation Patterns

Workflows

  1. Command/Query Separation

    • Use commands for write operations (e.g., CreateUserCommand, UpdateOrderCommand).
    • Use queries for read operations (e.g., GetUserQuery, ListOrdersQuery).
    • Example:
      // Command (write)
      Route::post('/orders/{id}/cancel', CancelOrderCommand::class);
      
      // Query (read)
      Route::get('/orders/{id}', GetOrderQuery::class);
      
  2. DTO Validation Leverage the DTOValidator to enforce request structure:

    // In config/cqs-routing.php
    'dto_validator' => [
        'class' => AccessValidator::class,
        'parameters' => AccessValidatorParameters::class,
    ],
    

    Customize validation rules in AccessValidatorParameters:

    namespace App\Application\DTOValidator\DTO;
    
    use Symfony\Component\Validator\Constraints as Assert;
    
    class AccessValidatorParameters
    {
        #[Assert\NotBlank]
        public string $name;
    
        #[Assert\Email]
        public string $email;
    }
    
  3. Response Handling Configure responses via ResponseConstructor:

    // For JSON APIs
    'response_constructor' => SerializerJsonResponseConstructor::class,
    
    // For empty responses (e.g., commands)
    'response_constructor' => EmptyJsonResponseConstructor::class,
    
  4. Transaction Management Wrap handlers in ConnectionTransactionWrapper for database transactions:

    // In config/cqs-routing.php
    'handler_wrapper' => ConnectionTransactionWrapper::class,
    
  5. Middleware Integration Chain CQS middleware with Laravel’s middleware stack:

    Route::middleware([CQSRouting::class, 'auth:sanctum'])->post('/users', CreateUserCommand::class);
    

Integration Tips

  • Symfony Integration: Works seamlessly with Symfony components (e.g., Serializer, Validator). Use Laravel’s service container to bind Symfony services.
  • Testing: Mock handlers and validators for unit tests:
    $handler = $this->createMock(CommandHandlerInterface::class);
    $handler->method('__invoke')->willReturn(new ResponseDto(...));
    
    $this->app->instance(
        CreateUserCommandHandler::class,
        $handler
    );
    
  • Error Handling: Extend ExceptionHandler to customize error responses:
    namespace App\Application\ExceptionHandlers;
    
    use DigitalCraftsman\CQSRouting\ExceptionHandlerInterface;
    use Symfony\Component\HttpFoundation\Response;
    
    class CustomExceptionHandler implements ExceptionHandlerInterface
    {
        public function __invoke(\Throwable $exception): Response
        {
            return new Response(
                json_encode(['error' => $exception->getMessage()]),
                400
            );
        }
    }
    
    Register in config:
    'exception_handler' => CustomExceptionHandler::class,
    

Gotchas and Tips

Pitfalls

  1. Circular Dependencies Avoid circular references between commands/queries and their handlers. Use interfaces for handlers to decouple implementations:

    interface CancelOrderHandlerInterface
    {
        public function __invoke(CancelOrderCommand $command): void;
    }
    
  2. Request Decoding Ensure payloads match the DTO structure exactly. Use JsonRequestDecoder for JSON APIs, but validate manually for custom formats:

    // Custom decoder example
    'request_decoder' => CustomRequestDecoder::class,
    
  3. Transaction Scope ConnectionTransactionWrapper rolls back on exceptions. Handle exceptions explicitly in handlers to avoid silent failures:

    try {
        $this->entityManager->persist($user);
        $this->entityManager->flush();
    } catch (\Exception $e) {
        throw new \RuntimeException('Failed to create user', 0, $e);
    }
    
  4. Performance Avoid heavy validation or serialization in handlers. Offload complex logic to services:

    // Handler
    public function __invoke(CreateUserCommand $command)
    {
        $this->userService->create($command->name, $command->email);
    }
    
  5. Route Caching Clear route cache after adding new CQS routes:

    php artisan route:clear
    

Debugging

  1. Handler Not Found Verify the handler is registered as a service and implements CommandHandlerInterface/QueryHandlerInterface. Check for typos in class names.

  2. Validation Errors Enable Symfony’s validator debug mode:

    // In config/services.php
    'validator' => [
        'debug' => env('APP_DEBUG', false),
    ],
    

    Check logs for constraint violations.

  3. Serialization Issues Ensure DTOs are serializable. Use #[SerializedName] for custom field names:

    use JMS\Serializer\Annotation as Serializer;
    
    class CreateUserCommand
    {
        #[Serializer\SerializedName('user_name')]
        public string $name;
    }
    
  4. Middleware Order Place CQSRouting middleware after auth/validation middleware to avoid redundant checks:

    Route::middleware(['throttle:60', CQSRouting::class])->post('/users', CreateUserCommand::class);
    

Extension Points

  1. Custom Request Decoders Implement RequestDecoderInterface for non-JSON payloads (e.g., form data):

    namespace App\Application\RequestDecoders;
    
    use DigitalCraftsman\CQSRouting\RequestDecoder\RequestDecoderInterface;
    use Symfony\Component\HttpFoundation\Request;
    
    class FormRequestDecoder implements RequestDecoderInterface
    {
        public function decode(Request $request, string $commandClass): mixed
        {
            return new $commandClass(
                $request->request->get('name'),
                $request->request->get('email')
            );
        }
    }
    

    Register in config:

    'request_decoder' => FormRequestDecoder::class,
    
  2. Dynamic Handlers Use Laravel’s service providers to bind handlers dynamically:

    // In AppServiceProvider
    public function register()
    {
        $this->app->bind(
            CreateUserCommandHandler::class,
            fn () => new CreateUserCommandHandler($this->app->make(UserRepository::class))
        );
    }
    
  3. Event Dispatching Extend handlers to dispatch events (e.g., UserCreatedEvent):

    use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
    
    class CreateUserCommandHandler implements CommandHandlerInterface
    {
        public function __construct(
            private EventDispatcherInterface $dispatcher
        ) {}
    
        public function __invoke(CreateUserCommand $command): void
        {
            $user = $this->userRepository->create($command);
            $this->dispatcher->dispatch(new UserCreatedEvent($user));
        }
    }
    
  4. Logging Add logging to handlers for observability:

    use Psr\Log\LoggerInterface;
    
    class CreateUserCommandHandler
    {
        public function __construct(private LoggerInterface $logger) {}
    
        public function __invoke(CreateUserCommand $command): void
        {
            $this->logger->info('Creating user', ['email' => $command->email]);
            // ...
        }
    }
    
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jayeshmepani/jpl-moshier-ephemeris-php
elnasnato/laraliveui
labrodev/rest-sdk
sampaui/sampaui
babelqueue/php-sdk
facebook/capi-param-builder-php
babelqueue/symfony
hamzi/corewatch
minionfactory/raw-hydrator
hexters/coinpayment
rjcodes/rjcms
act-training/laravel-permissions-manager
alimarchal/laravel-chart-of-accounts
babenkoivan/elastic-scout-driver
mkwebdesign/filament-watchdog-v5
renatomarinho/laravel-page-speed
zedmagdy/filament-business-hours
renatovdemoura/blade-elements-ui
devgeek/beacon-admin
benjamin-rqt/data-watcher-bundle