Mediator - CQRS Symfony bundle. Auto Command/Query routing to corresponding handlers.

Overview

Installation

$ composer require whsv26/mediator

Bundle configuration

// config/packages/mediator.php

return static function (MediatorConfig $config) {
    $config->query()->middlewares([
        SlowLogQueryMiddleware::class
    ]);
    
    $config->command()->middlewares([
        TransactionalCommandMiddleware::class
    ]);
};

Commands

/**
 * @implements CommandInterface<Either<Rejection, Success>>
 */
class CreateUserCommand implements CommandInterface
{
    public function __construct(
        public readonly string $email,
        public readonly string $password,
    ) { }
}

/**
 * NOTE: You need to register CreateUserCommandHandler as service
 * 
 * @implements CommandHandlerInterface<UserCreated, CreateUserCommand>
 */
class CreateUserCommandHandler implements CommandHandlerInterface
{
    public function __construct(
        private readonly UserRepository $users,
        private readonly HasherInterface $hasher,
        private readonly ClockInterface $clock
    ) { }

    /**
     * @param CreateUserCommand $command
     * @return UserCreated
     */
    public function handle($command): UserCreated
    {
        $user = new User(
            Id::next(),
            new Email($command->email),
            new PlainPassword($command->password),
            $this->hasher,
            $this->clock,
        );

        $this->users->save($user);

        return new UserCreated(
            $user->getId()->value,
            $user->getEmail()->value,
            $user->getCreatedAt()->toW3cString()
        );
    }
}

class CreateUserAction
{
    public function __construct(
        private readonly MediatorInterface $mediator
    ) { }

    #[Route(path: '/users', name: self::class, methods: ['POST'])]
    public function __invoke(CreateUserCommand $createUser): UserCreated
    {
        // $createUser deserialized from request body
        // via custom controller argument value resolver
    
        return $this->mediator->send($createUser);
    }
}

Queries

/**
 * @implements QueryInterface<Option<User>>
 */
class FindUserQuery implements QueryInterface
{
    public function __construct(
        public readonly ?string $id = null,
        public readonly ?string $email = null,
    ) { }
}

/**
 * NOTE: You need to register FindUserQueryHandler as service
 * 
 * @implements QueryHandlerInterface<Option<User>, FindUserQuery>
 */
class FindUserQueryHandler implements QueryHandlerInterface
{
    public function __construct(
        private readonly UserRepository $users
    ) { }

    /**
     * @param FindUserQuery $query
     * @return Option<User>
     */
    public function handle($query): Option
    {
        return Option::fromNullable($query->id)
            ->map(fn(string $id) => new Id($id))
            ->flatMap(fn(Id $id) => $this->users->findById($id))
            ->orElse(fn() => Option::fromNullable($query->email)
                ->map(fn(string $email) => new Email($email))
                ->flatMap(fn(Email $email) => $this->users->findByEmail($email))
            );
    }
}

Middlewares

  1. Implement CommandMiddlewareInterface or QueryMiddlewareInterface
  2. Register middleware class as service
  3. Enable middleware services in bundle config
/**
 * Example command middleware
 * 
 * NOTE: You need to register TransactionalCommandMiddleware as service
 */
class TransactionalCommandMiddleware implements CommandMiddlewareInterface
{
    public function __construct(
        private readonly Connection $connection
    ) { }

    /**
     * @template TResponse
     * @template TCommand of CommandInterface<TResponse>
     *
     * @param TCommand $command
     * @param Closure(TCommand): TResponse $next
     *
     * @return TResponse
     */
    public function handle(CommandInterface $command, Closure $next): mixed
    {
        $this->connection->beginTransaction();

        try {
            $res = $next($command);
            $this->connection->commit();
            
            return $res;
        } catch (Throwable $e) {
            $this->connection->rollBack();
            
            throw $e;
        }
    }
}
You might also like...
Symfony bundle for EventSauce (WIP)

Symfony EventSauce (WIP) This bundle provides the basic and extended container configuration of symfony for the EventSauce library. Before using it, I

Symfony bundle integrating server-sent native notifications
Symfony bundle integrating server-sent native notifications

Symfony UX Notify Symfony UX Notify is a Symfony bundle integrating server-sent native notifications in Symfony applications using Mercure. It is part

Bundle to integrate Tactician with Symfony projects

TacticianBundle Symfony2 Bundle for the Tactician library https://github.com/thephpleague/tactician/ Installation Step 1: Download the Bundle Open a c

A Symfony bundle that provides #StandWithUkraine banner and has some built-in features to block access to your resource for Russian-speaking users.
A Symfony bundle that provides #StandWithUkraine banner and has some built-in features to block access to your resource for Russian-speaking users.

StandWithUkraineBundle На русском? Смотри README.ru.md This bundle provides a built-in StandWithUkraine banner for your Symfony application and has so

A lightweight middleware to make api routing session capable.

Laravel stateless session A lightweight middleware to make api routing session capable. Installing $ composer require overtrue/laravel-stateless-sessi

Cryptocurrency exchange script Codono supports Auto detection of deposits, Each user is assigned with Unique deposit per coin
Cryptocurrency exchange script Codono supports Auto detection of deposits, Each user is assigned with Unique deposit per coin

#Cryptocurrency exchange script Codono supports Auto detection of deposits, Each user is assigned with Unique deposit per coin. Deposits are detected

OpenAPI(v3) Validators for Symfony http-foundation, using `league/openapi-psr7-validator` and `symfony/psr-http-message-bridge`.

openapi-http-foundation-validator OpenAPI(v3) Validators for Symfony http-foundation, using league/openapi-psr7-validator and symfony/psr-http-message

Fork of Symfony Rate Limiter Component for Symfony 4

Rate Limiter Component Fork (Compatible with Symfony =4.4) The Rate Limiter component provides a Token Bucket implementation to rate limit input and

Enter-to-the-Matrix-with-Symfony-Console - Reproduction of the
Enter-to-the-Matrix-with-Symfony-Console - Reproduction of the "Matrix characterfall" effect with the Symfony Console component.

Enter to the Matrix (with Symfony Console) Reproduction of the "Matrix characterfall" effect with the Symfony Console component. Run Clone the project

Releases(2.0.1)
Owner
Alexander Sv.
Alexander Sv.
Facebook Query Builder: A query builder for nested requests in the Facebook Graph API

A query builder that makes it easy to create complex & efficient nested requests to Facebook's Graph API to get lots of specific data back with one request.

Sammy Kaye Powers 92 Dec 18, 2022
Auto register services aliases in the Symfony container.

Service Alias Auto Register A bundle for Symfony 5. Description The S.O.L.I.D. principles are a set of five design principles intended to make softwar

(infinite) loophp 1 Feb 4, 2022
ObjectHydrator - Object Hydration library to create Command and Query objects.

Object Hydrator This is a utility that converts structured request data (for example: decoded JSON) into a complex object structure. The intended use

EventSauce 264 Dec 14, 2022
This bundle provides tools to build a complete GraphQL server in your Symfony App.

OverblogGraphQLBundle This Symfony bundle provides integration of GraphQL using webonyx/graphql-php and GraphQL Relay. It also supports: batching with

Webedia - Overblog 720 Dec 25, 2022
Pure PHP implementation of GraphQL Server – Symfony Bundle

Symfony GraphQl Bundle This is a bundle based on the pure PHP GraphQL Server implementation This bundle provides you with: Full compatibility with the

null 283 Dec 15, 2022
DataTables bundle for Symfony

Symfony DataTables Bundle This bundle provides convenient integration of the popular DataTables jQuery library for realtime Ajax tables in your Symfon

Omines Internetbureau 199 Jan 3, 2023
GraphQL Bundle for Symfony 2.

Symfony 2 GraphQl Bundle Use Facebook GraphQL with Symfony 2. This library port laravel-graphql. It is based on the PHP implementation here. Installat

Sergey Varibrus 35 Nov 17, 2022
An Unleash bundle for Symfony applications to provide an easy way to use feature flags

Unleash Bundle An Unleash bundle for Symfony applications. This provide an easy way to implement feature flags using Gitlab Feature Flags Feature. Ins

Stogon 7 Oct 20, 2022
Symfony Health Check Bundle Monitoring Project Status

Symfony Health Check Bundle Version Build Status Code Coverage master develop Installation Step 1: Download the Bundle Open a command console, enter y

MacPaw Inc. 27 Jul 7, 2022
A bundle providing routes and glue code between Symfony and a WOPI connector.

WOPI Bundle A Symfony bundle to facilitate the implementation of the WOPI endpoints and protocol. Description The Web Application Open Platform Interf

Champs-Libres 5 Aug 20, 2022