lmc / cqrs-types
A library containing types (interfaces and value objects) to help with Queries and Commands
Installs: 22 957
Dependents: 4
Suggesters: 0
Security: 0
Stars: 1
Watchers: 16
Forks: 0
Open Issues: 0
Requires
- php: ^8.2
- ext-hash: *
- ext-json: *
- ext-mbstring: *
- psr/cache: ^2.0 || ^3.0
- psr/http-client: ^1.0
- psr/http-message: ^1.0 || ^2.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
README
This library contains types, value objects, interfaces and base implementation for CQRS library. So anyone can easily write an extension for it.
Table of contents
Installation
composer require lmc/cqrs-types
Interfaces (types)
There are many interfaces and types here, which are implemented in other CQRS libraries
Query Interface
The main interface for all Queries. Query is a request which fetch a data without changing anything.
It is responsible for declaring and creating a request, which will be handled by QueryHandlerInterface
.
An object implementing Query Interface may implement feature and it should be handled by a QueryFetcher
.
Available features:
- caching
- profiling
Query Fetcher Interface
An interface for a Query Fetcher (see Handler/QueryFetcher).
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
Query Handler Interface
It is responsible for handling a specific Query request and passing a result into OnSuccess
callback.
It must say which request it supports and it must not be able to handle a different request.
When unsupported request is passed to handle method, it must pass UnsupportedRequestException
into OnError
callback.
It must not throw any exception, all exception must be passed into OnError
callback.
If necessary it may prepare a Query (for example inject a Client) - yet prepare
method should not change a Query type or its content.
It should prepare only supported queries.
It should not throw any exception either.
Command Interface
The main interface for all Commands. Command is a request which change a data and may return result data.
It is responsible for declaring and creating a request, which will be handled by SendCommandHandlerInterface
.
An object implementing Command Interface may implement feature and it should be handled by a CommandSender
.
Available features:
- profiling
Command Sender Interface
An interface for a Command Sender (see Handler/CommandSender).
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
Send Command Handler Interface
It is responsible for handling a specific Command request and passing a result into OnSuccess
callback.
It must say which request it supports and it must not be able to handle a different request.
When unsupported request is passed to handle method, it must pass UnsupportedRequestException
into OnError
callback.
It must not throw any exception, all exception must be passed into OnError
callback.
If necessary it may prepare a Command (for example inject a Client) - yet prepare
method should not change a Command type or its content.
It should prepare only supported commands.
It should not throw any exception either.
Response Decoder Interface
It is meant to decode a response (a result of either QueryHandlerInterface
or a SendCommandHandlerInterface
).
Decoder itself should be as small as possible and it should only support the one type to decode.
Decoders should (and are in a default implementation) be used by a priority one by one.
If you need a decoder to be final and no other decoding to be done, you must return a DecodedValue
object.
- QueryFetcher and CommandSender is responsible not to send a
DecodedValue
to any other decoder
It should be pure.
The response should be decoded in the same way everytime, in that case it can be safely cached.
If you need to do some impure operations in your decoder, you should use ImpureResponseDecoderInterface
instead.
It works exactly the same as a pure decoder but in the addition the QueryFetcher
knows, that a decoding process is not pure, so it may do some extra stuff.
For example QueryFetcher
should cache the result before the first impure decoding, so it won't cache the wrong result.
If an unsupported response is passed to decode method, it should return it untouched. It must not throw an exception.
There is also one predefined Response Decoder to decode a json string into an array - JsonResponseDecoder
.
Profiler Formatter Interface
When a Command or a Query implements Feature\ProfileableInterface
QueryFetcher/CommandSender will create a ProfilerItem
with some information.
The ProfilerItem
contains a raw data about a duration, request type, response, error and more and it is meant to be shown in Symfony Profiler (if you use a CQRS bundle).
Profiler Formatter format those items to provide a better experience than just a raw data, which might be lazy or unreadable without formatting.
It is responsible to format a ProfilerItem
to the ProfilerItem
again, so it can get/set all of ProfilerItem
properties.
There is a FormattedValue
Value object to help with a formatting, it can be passed to the most of the ProfilerItem
properties as a value.
It contains both original and formatted value.
Profiler formatter can even further format already FormattedValue
.
Multiple Formatters should be called by priority on the ProfilerItem
one by one to create the most readable form of a ProfilerItem
.
There is also one predefined Profiler Formatter to format (decode) a json string into an array - JsonProfilerFormatter
.
Features
Cacheable Interface
It allows to store and load an object implementing this interface to Psr\Cache\CacheItemPoolInterface
.
It uses a CacheKey
which should be as unique as possible.
NOTE: It is also required to set up Psr\Cache\CacheItemPoolInterface
implementation to QueryFetcher
.
Profileable Interface
It allows a profiling the object implementing this interface.
ProfilerId
is a string, which does not have to be unique.
Base Implementations
Base implementations offers a method(s) which will be mostly needed in implementing Handlers etc.
Handlers
AbstractQueryHandler
andAbstractSendCommandHandler
Offers a base implementation for asserting a supported Command/Query given to the handle method and base prepare
method, which does nothing to the query/command (as most of handlers won't need it).
It supposed to be used like follows
class MyQueryHandler extends AbstractQueryHandler { public function supports(QueryInterface $query): bool { return $query->getRequestType() === ExpectedRequestClass::class; } public function handle(QueryInterface $query, OnSuccessInterface $onSuccess, OnErrorInterface $onError): void { if (!$this->assertIsSupported(ExpectedRequestClass::class, $query, $onError)) { return; } try { $response = ...; // handle query/command ... $onSuccess($response); } catch (\Throwable $e) { $onError($e); } } }
Decoders
CallbackResponseDecoder
If you need a quick way of decoding a response, you can use this Callback Response Decoder, which allows you to pass a function to do decoding.
$decoder = new CallbackResponseDecoder( fn (string $response, $initiator) => is_string($response), fn (string $response) => sprintf('decoded:%s', $response), );
JsonResponseDecoder
It decodes a string which contains a json into decoded array.