From 18ce850002f5af32b934240c0aa7624527d7eef3 Mon Sep 17 00:00:00 2001 From: Pavol Kirschbaum Date: Wed, 29 Dec 2021 10:35:26 +0100 Subject: [PATCH] Add support for php 8.1, drop support for php 7.4 --- .github/workflows/continuous-integration.yml | 4 +- composer.json | 16 ++-- example/config.php | 4 - src/AbstractFactory.php | 83 ++++++++++++++------ src/CommandBusFactory.php | 64 ++++++++------- src/Event/RenamedEvent.php | 7 -- src/EventBusFactory.php | 36 ++++----- src/EventPublisherFactory.php | 24 ++++-- src/EventStoreFactory.php | 39 ++++++--- src/IdentityMapFactory.php | 21 +++-- src/SerializerFactory.php | 51 ++++++------ src/TransactionManagerFactory.php | 34 ++++++-- 12 files changed, 236 insertions(+), 147 deletions(-) delete mode 100644 src/Event/RenamedEvent.php diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 789a255..a24efe2 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -17,7 +17,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: 7.4 + php-version: 8.1 coverage: none - name: Get composer cache directory @@ -46,7 +46,7 @@ jobs: strategy: matrix: - php-versions: ['7.4', '8.0'] + php-versions: ['8.0', '8.1'] steps: - name: Checkout code diff --git a/composer.json b/composer.json index d1ca8c9..363538b 100644 --- a/composer.json +++ b/composer.json @@ -4,19 +4,23 @@ "type": "library", "license": "MIT", "config": { - "sort-packages": true + "sort-packages": true, + "allow-plugins": { + "composer/package-versions-deprecated": true + } }, "require": { - "php": "^7.4 | ^8.0", - "psr/container": "^1.0", - "pauci/cqrs": "^0.5.2" + "php": "~8.0.0||~8.1.0", + "pauci/cqrs": "^0.6.0", + "psr/container": "^1.0||^2.0" }, "require-dev": { "doctrine/orm": "^2.8", "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/phpstan": "^0.12.58", + "phpstan/phpstan": "^1.2", "phpunit/phpunit": "^9.5", - "squizlabs/php_codesniffer": "^3.5" + "squizlabs/php_codesniffer": "^3.5", + "symfony/serializer": "^6.0" }, "autoload": { "psr-4": { diff --git a/example/config.php b/example/config.php index d56c809..a4fc860 100644 --- a/example/config.php +++ b/example/config.php @@ -59,9 +59,5 @@ //'instance' => 'my_serializer_alias', ], ], - - 'event_type_map' => [ - 'App\Model\Entity\Event\OriginalEvent' => CQRSFactory\Event\RenamedEvent::class, - ], ], ]; diff --git a/src/AbstractFactory.php b/src/AbstractFactory.php index fd1127e..65a6566 100644 --- a/src/AbstractFactory.php +++ b/src/AbstractFactory.php @@ -2,23 +2,27 @@ namespace CQRSFactory; +use CQRSFactory\Exception\DomainException; use Psr\Container\ContainerInterface; -/** @internal */ +/** + * @phpstan-template T of object + * @internal + */ abstract class AbstractFactory { private string $configKey; - public function __construct(string $configKey = 'cqrs_default') + /** @internal */ + final public function __construct(string $configKey = 'cqrs_default') { $this->configKey = $configKey; } /** - * @param ContainerInterface $container - * @return mixed + * @phpstan-return T */ - public function __invoke(ContainerInterface $container) + public function __invoke(ContainerInterface $container): object { return $this->createWithConfig($container, $this->configKey); } @@ -35,12 +39,10 @@ public function __invoke(ContainerInterface $container) * ]; * * - * @param string $name - * @param array $arguments - * @return mixed + * @phpstan-return T * @throws Exception\DomainException */ - public static function __callStatic(string $name, array $arguments) + public static function __callStatic(string $name, array $arguments): object { if (!array_key_exists(0, $arguments) || !$arguments[0] instanceof ContainerInterface) { throw new Exception\InvalidArgumentException(sprintf( @@ -49,36 +51,39 @@ public static function __callStatic(string $name, array $arguments) )); } - /** @phpstan-ignore-next-line */ return (new static($name))($arguments[0]); } /** * Creates a new instance from a specified config. * - * @param ContainerInterface $container - * @param string $configKey - * @return mixed + * @phpstan-return T */ - abstract protected function createWithConfig(ContainerInterface $container, string $configKey); + abstract protected function createWithConfig(ContainerInterface $container, string $configKey): object; /** * Returns the default config. + * + * @phpstan-return array */ abstract protected function getDefaultConfig(): array; /** * Retrieves the config for a specific section. + * + * @phpstan-return array */ protected function retrieveConfig(ContainerInterface $container, string $configKey, string $section): array { + /** @var array{cqrs?: array} $applicationConfig */ $applicationConfig = $container->has('config') ? $container->get('config') : []; - $config = $applicationConfig['cqrs'][$section][$configKey] ?? []; + $sectionConfig = $applicationConfig['cqrs'][$section] ?? []; + + if (array_key_exists($configKey, $sectionConfig)) { + return $sectionConfig[$configKey] + $this->getDefaultConfig(); + } - return array_merge( - $this->getDefaultConfig(), - $config - ); + return $this->getDefaultConfig(); } /** @@ -87,24 +92,52 @@ protected function retrieveConfig(ContainerInterface $container, string $configK * If the container does not know about the dependency, it is pulled from a fresh factory. This saves the user from * registering factories which they are not gonna access themself at all, and thus minimized configuration. * - * @param ContainerInterface $container - * @param string $configKey - * @param string $section - * @param string $factoryClassName - * @return mixed + * @phpstan-template Dependency of object + * @phpstan-param class-string> $factoryClassName + * @phpstan-return Dependency */ protected function retrieveDependency( ContainerInterface $container, string $configKey, string $section, string $factoryClassName - ) { + ): object { $containerKey = sprintf('cqrs.%s.%s', $section, $configKey); if ($container->has($containerKey)) { + /** @phpstan-ignore-next-line */ return $container->get($containerKey); } return (new $factoryClassName($configKey))($container); } + + /** + * @phpstan-template Service of object + * @phpstan-param class-string $className + * @phpstan-return Service + */ + protected function retrieveService( + ContainerInterface $container, + array $config, + string $key, + string $className + ): object { + $service = $config[$key] ?? null; + + if (is_string($service)) { + $service = $container->get($service); + } + + if (!$service instanceof $className) { + throw new DomainException(sprintf( + 'Service "%s" must be an instance of %s, got %s', + $key, + $className, + get_debug_type($service) + )); + } + + return $service; + } } diff --git a/src/CommandBusFactory.php b/src/CommandBusFactory.php index 837852f..a4a8c6b 100644 --- a/src/CommandBusFactory.php +++ b/src/CommandBusFactory.php @@ -3,50 +3,54 @@ namespace CQRSFactory; use CQRS\CommandHandling\CommandBusInterface; -use CQRS\CommandHandling\CommandHandlerLocator; +use CQRS\CommandHandling\PsrContainerCommandHandlerLocator; use CQRS\CommandHandling\SequentialCommandBus; -use CQRS\HandlerResolver\CommandHandlerResolver; -use CQRS\HandlerResolver\ContainerHandlerResolver; -use CQRSFactory\Exception\InvalidArgumentException; +use CQRS\CommandHandling\TransactionManager\TransactionManagerInterface; +use CQRS\EventHandling\Publisher\EventPublisherInterface; use Psr\Container\ContainerInterface; +/** + * @phpstan-type CommandBusConfig array{ + * class: class-string, + * handlers: array, + * transaction_manager: string, + * event_publisher: string + * } + * @phpstan-extends AbstractFactory + */ class CommandBusFactory extends AbstractFactory { - /** - * @param ContainerInterface $container - * @param string $configKey - * @return CommandBusInterface - * @throws InvalidArgumentException - */ protected function createWithConfig(ContainerInterface $container, string $configKey): CommandBusInterface { + /** @var CommandBusConfig $config */ $config = $this->retrieveConfig($container, $configKey, 'command_bus'); - return new $config['class']( - new CommandHandlerLocator( - $config['handlers'], - new ContainerHandlerResolver( + if ($config['class'] === SequentialCommandBus::class) { + return new SequentialCommandBus( + new PsrContainerCommandHandlerLocator( + $container, + $config['handlers'] + ), + $this->retrieveDependency( $container, - new CommandHandlerResolver() + $config['transaction_manager'], + 'transaction_manager', + TransactionManagerFactory::class + ), + $this->retrieveDependency( + $container, + $config['event_publisher'], + 'event_publisher', + EventPublisherFactory::class ) - ), - $this->retrieveDependency( - $container, - $config['transaction_manager'], - 'transaction_manager', - TransactionManagerFactory::class - ), - $this->retrieveDependency( - $container, - $config['event_publisher'], - 'event_publisher', - EventPublisherFactory::class - ) - ); + ); + } + + return new $config['class']; } /** - * {@inheritdoc} + * @phpstan-return CommandBusConfig */ protected function getDefaultConfig(): array { diff --git a/src/Event/RenamedEvent.php b/src/Event/RenamedEvent.php deleted file mode 100644 index 5d9399e..0000000 --- a/src/Event/RenamedEvent.php +++ /dev/null @@ -1,7 +0,0 @@ -, + * handlers: array> + * } + * @phpstan-extends AbstractFactory + */ class EventBusFactory extends AbstractFactory { - /** - * @param ContainerInterface $container - * @param string $configKey - * @return EventBusInterface - * @throws InvalidArgumentException - */ protected function createWithConfig(ContainerInterface $container, string $configKey): EventBusInterface { + /** @var EventBusConfig $config */ $config = $this->retrieveConfig($container, $configKey, 'event_bus'); - return new $config['class']( - new EventHandlerLocator( - $config['handlers'], - new ContainerHandlerResolver( + if ($config['class'] === SynchronousEventBus::class) { + return new SynchronousEventBus( + new PsrContainerEventHandlerLocator( $container, - new EventHandlerResolver() + $config['handlers'], ) - ) - ); + ); + } + + return new $config['class']; } /** - * {@inheritdoc} + * @phpstan-return EventBusConfig */ protected function getDefaultConfig(): array { diff --git a/src/EventPublisherFactory.php b/src/EventPublisherFactory.php index f5c8114..0cc16dc 100644 --- a/src/EventPublisherFactory.php +++ b/src/EventPublisherFactory.php @@ -9,13 +9,21 @@ use Doctrine\ORM\EntityManagerInterface; use Psr\Container\ContainerInterface; +/** + * @phpstan-type EventPublisherConfig array{ + * class: class-string, + * event_bus: string, + * identity_map: string, + * event_store: string, + * entity_manager: string + * } + * @phpstan-extends AbstractFactory + */ class EventPublisherFactory extends AbstractFactory { - /** - * {@inheritdoc} - */ public function createWithConfig(ContainerInterface $container, string $configKey): EventPublisherInterface { + /** @var EventPublisherConfig $config */ $config = $this->retrieveConfig($container, $configKey, 'event_publisher'); $eventPublisher = new $config['class']( @@ -42,8 +50,12 @@ public function createWithConfig(ContainerInterface $container, string $configKe ); if ($eventPublisher instanceof EventSubscriber) { - /** @var EntityManagerInterface $entityManager */ - $entityManager = $container->get($config['entity_manager']); + $entityManager = $this->retrieveService( + $container, + $config, + 'entity_manager', + EntityManagerInterface::class + ); $entityManager->getEventManager() ->addEventSubscriber($eventPublisher); } @@ -52,7 +64,7 @@ public function createWithConfig(ContainerInterface $container, string $configKe } /** - * {@inheritdoc} + * @phpstan-return EventPublisherConfig */ protected function getDefaultConfig(): array { diff --git a/src/EventStoreFactory.php b/src/EventStoreFactory.php index 3cfd07f..f6637bd 100644 --- a/src/EventStoreFactory.php +++ b/src/EventStoreFactory.php @@ -3,20 +3,30 @@ namespace CQRSFactory; use CQRS\EventStore\ChainingEventStore; +use CQRS\EventStore\EventFilterInterface; use CQRS\EventStore\EventStoreInterface; use CQRS\EventStore\FilteringEventStore; use CQRS\EventStore\MemoryEventStore; use Psr\Container\ContainerInterface; +/** + * @phpstan-type EventStoreConfig array{ + * class: class-string, + * event_store: string, + * event_stores: array, + * event_filter: string, + * serializer: string, + * connection: class-string|object|null, + * namespace: ?string, + * size: ?int + * } + * @phpstan-extends AbstractFactory + */ class EventStoreFactory extends AbstractFactory { - /** - * @param ContainerInterface $container - * @param string $configKey - * @return EventStoreInterface - */ protected function createWithConfig(ContainerInterface $container, string $configKey): EventStoreInterface { + /** @var EventStoreConfig $config */ $config = $this->retrieveConfig($container, $configKey, 'event_store'); switch ($config['class']) { @@ -40,13 +50,24 @@ protected function createWithConfig(ContainerInterface $container, string $confi static::class ); - $eventFilter = $container->get($config['event_filter']); + $eventFilter = $this->retrieveService( + $container, + $config, + 'event_filter', + EventFilterInterface::class + ); + return new FilteringEventStore($eventStore, $eventFilter); case MemoryEventStore::class: return new MemoryEventStore(); } + $connection = $config['connection']; + if (is_string($connection)) { + $connection = $container->get($connection); + } + return new $config['class']( $this->retrieveDependency( $container, @@ -54,16 +75,14 @@ protected function createWithConfig(ContainerInterface $container, string $confi 'serializer', SerializerFactory::class ), - is_string($config['connection']) - ? $container->get($config['connection']) - : $config['connection'], + $connection, $config['namespace'], $config['size'] ); } /** - * {@inheritdoc} + * @return EventStoreConfig */ protected function getDefaultConfig(): array { diff --git a/src/IdentityMapFactory.php b/src/IdentityMapFactory.php index 52f1a37..ac9b3af 100644 --- a/src/IdentityMapFactory.php +++ b/src/IdentityMapFactory.php @@ -8,22 +8,27 @@ use Doctrine\ORM\EntityManagerInterface; use Psr\Container\ContainerInterface; +/** + * @phpstan-type IdentityMapConfig array{class: class-string, entity_manager?: string} + * @phpstan-extends AbstractFactory + */ class IdentityMapFactory extends AbstractFactory { - /** - * @param ContainerInterface $container - * @param string $configKey - * @return IdentityMapInterface - */ protected function createWithConfig(ContainerInterface $container, string $configKey): IdentityMapInterface { + /** @var IdentityMapConfig $config */ $config = $this->retrieveConfig($container, $configKey, 'identity_map'); $identityMap = new $config['class']; if ($identityMap instanceof EventSubscriber) { - /** @var EntityManagerInterface $entityManager */ - $entityManager = $container->get($config['entity_manager']); + $entityManager = $this->retrieveService( + $container, + $config, + 'entity_manager', + EntityManagerInterface::class + ); + $entityManager->getEventManager() ->addEventSubscriber($identityMap); } @@ -32,7 +37,7 @@ protected function createWithConfig(ContainerInterface $container, string $confi } /** - * {@inheritdoc} + * @return IdentityMapConfig */ protected function getDefaultConfig(): array { diff --git a/src/SerializerFactory.php b/src/SerializerFactory.php index b34f56b..3965ed5 100644 --- a/src/SerializerFactory.php +++ b/src/SerializerFactory.php @@ -2,48 +2,53 @@ namespace CQRSFactory; -use CQRS\Serializer\HybridSerializer; -use CQRS\Serializer\JsonSerializer; use CQRS\Serializer\SerializerInterface; +use CQRS\Serializer\SymfonySerializer; +use CQRSFactory\Exception\DomainException; use Psr\Container\ContainerInterface; - +use Symfony\Component\Serializer\SerializerInterface as SymfonySerializerInterface; + +/** + * @phpstan-type SerializerConfig array{ + * class: class-string, + * instance?: class-string|object, + * format?: string, + * context?: array + * } + * @phpstan-extends AbstractFactory + */ class SerializerFactory extends AbstractFactory { - /** - * @param ContainerInterface $container - * @param string $configKey - * @return SerializerInterface - */ protected function createWithConfig(ContainerInterface $container, string $configKey): SerializerInterface { + /** @var SerializerConfig $config */ $config = $this->retrieveConfig($container, $configKey, 'serializer'); - if ($config['class'] === HybridSerializer::class) { - $dictionary = $config['event_type_map'] ?: []; + if ($config['class'] === SymfonySerializer::class) { + $instance = $this->retrieveService( + $container, + $config, + 'instance', + SymfonySerializerInterface::class + ); - $jsonSerializer = new JsonSerializer(); + $format = $config['format'] ?? 'json'; + $context = $config['context'] ?? []; - return new $config['class']( - $jsonSerializer, - $dictionary - ); + return new SymfonySerializer($instance, $format, $context); } - return new $config['class']( - is_string($config['instance']) - ? $container->get($config['instance']) - : $config['instance'] - ); + return new $config['class']; } /** - * {@inheritdoc} + * @phpstan-return SerializerConfig */ protected function getDefaultConfig(): array { return [ - 'class' => JsonSerializer::class, - 'instance' => null, + 'class' => SymfonySerializer::class, + 'instance' => SymfonySerializerInterface::class, ]; } } diff --git a/src/TransactionManagerFactory.php b/src/TransactionManagerFactory.php index 6a35805..2d3a186 100644 --- a/src/TransactionManagerFactory.php +++ b/src/TransactionManagerFactory.php @@ -6,32 +6,50 @@ use CQRS\CommandHandling\TransactionManager\TransactionManagerInterface; use CQRS\Plugin\Doctrine\CommandHandling\ExplicitOrmTransactionManager; use CQRS\Plugin\Doctrine\CommandHandling\ImplicitOrmTransactionManager; +use Doctrine\ORM\EntityManagerInterface; use Psr\Container\ContainerInterface; +/** + * @phpstan-type TransactionManagerConfig array{ + * class: class-string, + * connection?: string + * } + * @phpstan-extends AbstractFactory + */ class TransactionManagerFactory extends AbstractFactory { - /** - * {@inheritdoc} - */ public function createWithConfig(ContainerInterface $container, string $configKey): TransactionManagerInterface { + /** @phpstan-var TransactionManagerConfig $config */ $config = $this->retrieveConfig($container, $configKey, 'transaction_manager'); switch ($config['class']) { case ExplicitOrmTransactionManager::class: + $entityManager = $this->retrieveService( + $container, + $config, + 'connection', + EntityManagerInterface::class + ); + + return new ExplicitOrmTransactionManager($entityManager); + case ImplicitOrmTransactionManager::class: - $entityManager = is_string($config['connection']) - ? $container->get($config['connection']) - : $config['connection']; + $entityManager = $this->retrieveService( + $container, + $config, + 'connection', + EntityManagerInterface::class + ); - return new $config['class']($entityManager); + return new ImplicitOrmTransactionManager($entityManager); } return new $config['class']; } /** - * {@inheritdoc} + * @phpstan-return TransactionManagerConfig */ protected function getDefaultConfig(): array {