lmc / cqrs-handler
A library containing handlers to help with Queries and Commands
Installs: 19 040
Dependents: 2
Suggesters: 1
Security: 0
Stars: 1
Watchers: 16
Forks: 0
Open Issues: 0
Requires
- php: ^8.2
- ext-json: *
- ext-mbstring: *
- lmc/cqrs-types: ^3.2
- psr/cache: ^2.0 || ^3.0
- psr/http-message: ^1.0.1 || ^2.0
- ramsey/collection: ^1.2.2 || ^2.0
- ramsey/uuid: ^4.2.3
- symfony/stopwatch: ^5.0 || ^6.0 || ^7.0
Requires (Dev)
- ergebnis/composer-normalize: ^2.5
- lmc/coding-standard: ^3.3
- php-parallel-lint/php-parallel-lint: ^1.2
- phpstan/extension-installer: ^1.1
- phpstan/phpstan: ^1.4
- phpstan/phpstan-phpunit: ^1.0
- phpunit/phpunit: ^11.0.4
- symfony/cache: ^5.2 || ^6.0 || ^7.0
README
This library contains a base implementation for CQRS/Types.
Table of contents
- Installation
- Queries
- Commands
- ProfilerBag
Installation
composer require lmc/cqrs-handler
Query Fetcher
Base implementation for a Query Fetcher Interface (see Types/QueryFetcherInterface).
It is responsible for
- finding a Query Handler based on Query request type
- handle all Query features
- caching
- requires an instance of
Psr\Cache\CacheItemPoolInterface
- requires an instance of
- profiling
- requires an instance of
Lmc\Cqrs\Handler\ProfilerBag
- requires an instance of
- caching
- decoding a response from the Query Handler
Usage
If you are not using a CQRS/Bundle you need to set up a Query Fetcher yourself.
Minimal Initialization
$queryFetcher = new QueryFetcher( // Cache false, // disabled cache null, // no cache pool -> no caching // Profiling null // no profiler bag -> no profiling );
Full Initialization with all features.
$profilerBag = new ProfilerBag(); $queryFetcher = new QueryFetcher( // Cache true, // is cache enabled $cache, // instance of Psr\Cache\CacheItemPoolInterface // Profiling $profilerBag, // collection of profiled information // Custom handlers // NOTE: there is multiple ways of defining handler [ [new TopMostHandler(), PrioritizedItem::PRIORITY_HIGHEST], // Array definition of priority new OtherHandler(), // Used with default priority of 50 new PrioritizedItem(new FallbackHandler(), PrioritizedItem::PRIORITY_LOWEST) // PrioritizedItem value object definition ], // Custom response decoders // NOTE: there is multiple ways of defining response decoders [ [new TopMostDecoder(), PrioritizedItem::PRIORITY_HIGHEST], // Array definition of priority new OtherDecoder(), // Used with default priority of 50 new PrioritizedItem(new FallbackDecoder(), PrioritizedItem::PRIORITY_LOWEST) // PrioritizedItem value object definition ] );
You can add handlers and decoders by add
methods.
$this->queryFetcher->addHandler(new MyQueryHandler(), PrioritizedItem::PRIORITY_MEDIUM); $this->queryFetcher->addDecoder(new MyQueryResponseDecoder(), PrioritizedItem::PRIORITY_HIGH);
Fetching a query
You can do whatever you want with a response, we will persist a result into db, for an example or log an error.
// with continuation $this->queryFetcher->fetch( $query, fn ($response) => $this->repository->save($response), fn (\Throwable $error) => $this->logger->critical($error->getMassage()) ); // with return try { $response = $this->queryFetcher->fetchAndReturn($query); $this->repository->save($response); } catch (\Throwable $error) { $this->logger->critical($error->getMessage()); }
Query Handlers
It is responsible for handling a specific Query request and passing a result into OnSuccess
callback. See more here.
GetCachedHandler
This handler is automatically created QueryFetcher
and added amongst handlers with priority 80
when an instance of CacheItemPoolInterface
is passed into QueryFetcher
.
It supports queries implementing CacheableInterface
with cacheTime > 0
. The second condition allows you to avoid caching in queries with CacheableInterface
by just a cache time value.
There is also CacheTime::noCache()
named constructor to make it explicit.
It handles a query by retrieving a result out of a cache (if the cache has the item and is hit
(see PSR-6 for more).
CallbackQueryHandler
This handler supports a query with request type of "callable"
, "Closure"
or "callback"
(which all stands for a callable
request).
It simply calls a created request as a function and returns a result to OnSuccess
callback.
Query
Query is a request which fetch a data without changing anything. See more here
CachedDataQuery
This is a predefined implementation for a Query with CacheableInterface
.
It is handy for in-app queries where you want to use cache for a result. You can also extend it and add more features.
$query = new CallbackQueryHandler( fn () => $this->repository->fetchData(), new CacheKey('my-data-key'), CacheTime::oneHour() );
ProfiledCachedDataQuery
This is a predefined implementation for a Query with CacheableInterface
and ProfileableInterface
.
It is handy for in-app queries where you want to use cache for a result and also profile it. You can also extend it and add more features.
$query = new ProfiledCallbackQueryHandler( fn () => $this->repository->fetchData(), new CacheKey('my-data-key'), CacheTime::oneHour(), 'my-profiler-key', ['additional' => 'data'] // optional );
Command Sender
Base implementation for a Command Sender Interface (see Types/CommandSenderInterface).
It is responsible for
- finding a Send Command Handler based on Command request type
- handle all Command features
- profiling
- requires an instance of
Lmc\Cqrs\Handler\ProfilerBag
- requires an instance of
- profiling
- decoding a response from the Send Command Handler
Usage
If you are not using a CQRS/Bundle you need to set up a Command Sender yourself.
Minimal Initialization
$commandSender = new CommandSender( // Profiling null // no profiler bag -> no profiling );
Full Initialization with all features.
$profilerBag = new ProfilerBag(); $commandSender = new CommandSender( // Profiling $profilerBag, // collection of profiled information // Custom handlers // NOTE: there is multiple ways of defining handler [ [new TopMostHandler(), PrioritizedItem::PRIORITY_HIGHEST], // Array definition of priority new OtherHandler(), // Used with default priority of 50 new PrioritizedItem(new FallbackHandler(), PrioritizedItem::PRIORITY_LOWEST) // PrioritizedItem value object definition ], // Custom response decoders // NOTE: there is multiple ways of defining response decoders [ [new TopMostDecoder(), PrioritizedItem::PRIORITY_HIGHEST], // Array definition of priority new OtherDecoder(), // Used with default priority of 50 new PrioritizedItem(new FallbackDecoder(), PrioritizedItem::PRIORITY_LOWEST) // PrioritizedItem value object definition ] );
You can add handlers and decoders by add
methods.
$this->commandSender->addHandler(new MyCommandHandler(), PrioritizedItem::PRIORITY_MEDIUM); $this->commandSender->addDecoder(new MyCommandResponseDecoder(), PrioritizedItem::PRIORITY_HIGH);
Sending a command
You can do whatever you want with a response, we will persist a result into db, for an example or log an error.
// with continuation $this->commandSender->send( $command, fn ($response) => $this->repository->save($response), fn (\Throwable $error) => $this->logger->critical($error->getMassage()) ); // with return try { $response = $this->commandSender->sendAndReturn($query); $this->repository->save($response); } catch (\Throwable $error) { $this->logger->critical($error->getMessage()); }
Send Command Handlers
It is responsible for handling a specific Command request and passing a result into OnSuccess
callback. See more here.
CallbackSendCommandHandler
This handler supports a command with request type of "callable"
, "Closure"
or "callback"
(which all stands for a callable
request).
It simply calls a created request as a function and returns a result to OnSuccess
callback.
Command
Command is a request which change a data and may return result data. See more here
ProfiledDataCommand
This is a predefined implementation for a Command with ProfileableInterface
.
It is handy for in-app commands where you want to profile it. You can also extend it and add more features.
$command = new ProfiledDataCommand( fn () => $this->repository->fetchData(), new CacheKey('my-data-key'), CacheTime::oneHour(), 'my-profiler-key', ['additional' => 'data'] // optional );
ProfilerBag
Service, which is a collection of all profiler information in the current request.
If you pass it to the QueryFetcher
or CommandSender
, they will profile query/command implementing ProfileableInterface
to the ProfilerBag
.
The information inside are used by a CqrsDataCollector
, which shows them in the Symfony profiler (used in CQRS/Bundle).
Verbosity
Profiler bag can also hold an information about a verbosity level of profiling.
Levels:
- NORMAL =
empty value
(default) - VERBOSE =
'verbose'
- DEBUG =
'debug'
There might be additional data added to the ProfilerItem
with higher levels of verbosity.
You can set it by
$profilerBag->setVerbosity(ProfilerBag::VERBOSITY_VERBOSE);