diff --git a/README.md b/README.md index 34ecf15..9e97ae6 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,9 @@ gember_event_sourcing: reflector: path: '%kernel.project_dir%/src' command_handler: + reflector: + path: '%kernel.project_dir%/src' + saga: reflector: path: '%kernel.project_dir%/src' ``` diff --git a/composer.json b/composer.json index 721007b..ad220bf 100644 --- a/composer.json +++ b/composer.json @@ -22,11 +22,11 @@ ], "require": { "php": "^8.3", - "gember/event-sourcing": "^0.9", - "gember/identity-generator-symfony": "^0.8", - "gember/message-bus-symfony": "^0.8", - "gember/rdbms-event-store-doctrine-dbal": "^0.8", - "gember/serializer-symfony": "^0.8", + "gember/event-sourcing": "^0.10", + "gember/identity-generator-symfony": "^0.9", + "gember/message-bus-symfony": "^0.9", + "gember/rdbms-event-store-doctrine-dbal": "^0.9", + "gember/serializer-symfony": "^0.9", "symfony/config": "^7.1|^7.2", "symfony/console": "^7.1|^7.2", "symfony/dependency-injection": "^7.1|^7.2", diff --git a/composer.lock b/composer.lock index d62c9ee..5c96e83 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "dbd704537f3e5fc8c4dc1fc8ba15fb2b", + "content-hash": "202e59974617eb441aaf5eea2419d014", "packages": [ { "name": "doctrine/dbal", @@ -162,16 +162,16 @@ }, { "name": "gember/dependency-contracts", - "version": "0.1.0", + "version": "0.2.1", "source": { "type": "git", "url": "https://github.com/GemberPHP/dependency-contracts.git", - "reference": "e82562e0f46c091d4991508ebf43257fc3a9a424" + "reference": "dba36626a596e8ef49c1a12ed234a4c874d335b9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/GemberPHP/dependency-contracts/zipball/e82562e0f46c091d4991508ebf43257fc3a9a424", - "reference": "e82562e0f46c091d4991508ebf43257fc3a9a424", + "url": "https://api.github.com/repos/GemberPHP/dependency-contracts/zipball/dba36626a596e8ef49c1a12ed234a4c874d335b9", + "reference": "dba36626a596e8ef49c1a12ed234a4c874d335b9", "shasum": "" }, "require": { @@ -212,28 +212,28 @@ ], "support": { "issues": "https://github.com/GemberPHP/dependency-contracts/issues", - "source": "https://github.com/GemberPHP/dependency-contracts/tree/0.1.0" + "source": "https://github.com/GemberPHP/dependency-contracts/tree/0.2.1" }, - "time": "2025-09-03T17:15:32+00:00" + "time": "2025-10-10T07:33:21+00:00" }, { "name": "gember/event-sourcing", - "version": "0.9.0", + "version": "0.10.0", "source": { "type": "git", "url": "https://github.com/GemberPHP/event-sourcing.git", - "reference": "f4ae2fcc9e86f32a29e24b14662886927f193816" + "reference": "d133ef34f56332370c5b31bbf0f2f839a1edac03" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/GemberPHP/event-sourcing/zipball/f4ae2fcc9e86f32a29e24b14662886927f193816", - "reference": "f4ae2fcc9e86f32a29e24b14662886927f193816", + "url": "https://api.github.com/repos/GemberPHP/event-sourcing/zipball/d133ef34f56332370c5b31bbf0f2f839a1edac03", + "reference": "d133ef34f56332370c5b31bbf0f2f839a1edac03", "shasum": "" }, "require": { "ext-mbstring": "*", "ext-tokenizer": "*", - "gember/dependency-contracts": "^0.1", + "gember/dependency-contracts": "^0.2", "php": "^8.3", "psr/simple-cache": "^3.0" }, @@ -276,26 +276,26 @@ ], "support": { "issues": "https://github.com/GemberPHP/event-sourcing/issues", - "source": "https://github.com/GemberPHP/event-sourcing/tree/0.9.0" + "source": "https://github.com/GemberPHP/event-sourcing/tree/0.10.0" }, - "time": "2025-09-12T17:26:53+00:00" + "time": "2025-10-10T13:13:18+00:00" }, { "name": "gember/identity-generator-symfony", - "version": "0.8.0", + "version": "0.9.0", "source": { "type": "git", "url": "https://github.com/GemberPHP/identity-generator-symfony.git", - "reference": "0b4b07fe2a6427fe33653bb0f18e5a45212c32ed" + "reference": "9865d15cf5ac696c5b8386d98c15a7dec19d59ba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/GemberPHP/identity-generator-symfony/zipball/0b4b07fe2a6427fe33653bb0f18e5a45212c32ed", - "reference": "0b4b07fe2a6427fe33653bb0f18e5a45212c32ed", + "url": "https://api.github.com/repos/GemberPHP/identity-generator-symfony/zipball/9865d15cf5ac696c5b8386d98c15a7dec19d59ba", + "reference": "9865d15cf5ac696c5b8386d98c15a7dec19d59ba", "shasum": "" }, "require": { - "gember/dependency-contracts": "^0.1", + "gember/dependency-contracts": "^0.2", "php": "^8.3", "symfony/uid": "^7.1" }, @@ -340,26 +340,26 @@ ], "support": { "issues": "https://github.com/GemberPHP/identity-generator-symfony/issues", - "source": "https://github.com/GemberPHP/identity-generator-symfony/tree/0.8.0" + "source": "https://github.com/GemberPHP/identity-generator-symfony/tree/0.9.0" }, - "time": "2025-09-03T17:20:51+00:00" + "time": "2025-10-10T12:12:03+00:00" }, { "name": "gember/message-bus-symfony", - "version": "0.8.0", + "version": "0.9.0", "source": { "type": "git", "url": "https://github.com/GemberPHP/message-bus-symfony.git", - "reference": "b32cd78575ec2e45da3878c83bcc6a4880489dca" + "reference": "870c26e6003ace844f50dedc3155f6d477568648" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/GemberPHP/message-bus-symfony/zipball/b32cd78575ec2e45da3878c83bcc6a4880489dca", - "reference": "b32cd78575ec2e45da3878c83bcc6a4880489dca", + "url": "https://api.github.com/repos/GemberPHP/message-bus-symfony/zipball/870c26e6003ace844f50dedc3155f6d477568648", + "reference": "870c26e6003ace844f50dedc3155f6d477568648", "shasum": "" }, "require": { - "gember/dependency-contracts": "^0.1", + "gember/dependency-contracts": "^0.2", "php": "^8.3", "symfony/messenger": "^7.1" }, @@ -404,27 +404,27 @@ ], "support": { "issues": "https://github.com/GemberPHP/message-bus-symfony/issues", - "source": "https://github.com/GemberPHP/message-bus-symfony/tree/0.8.0" + "source": "https://github.com/GemberPHP/message-bus-symfony/tree/0.9.0" }, - "time": "2025-09-03T17:23:48+00:00" + "time": "2025-10-10T07:04:09+00:00" }, { "name": "gember/rdbms-event-store-doctrine-dbal", - "version": "0.8.0", + "version": "0.9.0", "source": { "type": "git", "url": "https://github.com/GemberPHP/rdbms-event-store-doctrine-dbal.git", - "reference": "beb4b4de4377e0091bffb0602c13270c2bead6de" + "reference": "047ba3cc52339f9c2297fd48d9cc268936adf30b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/GemberPHP/rdbms-event-store-doctrine-dbal/zipball/beb4b4de4377e0091bffb0602c13270c2bead6de", - "reference": "beb4b4de4377e0091bffb0602c13270c2bead6de", + "url": "https://api.github.com/repos/GemberPHP/rdbms-event-store-doctrine-dbal/zipball/047ba3cc52339f9c2297fd48d9cc268936adf30b", + "reference": "047ba3cc52339f9c2297fd48d9cc268936adf30b", "shasum": "" }, "require": { "doctrine/dbal": "^4.0", - "gember/dependency-contracts": "^0.1", + "gember/dependency-contracts": "^0.2.1", "php": "^8.3" }, "require-dev": { @@ -468,26 +468,26 @@ ], "support": { "issues": "https://github.com/GemberPHP/rdbms-event-store-doctrine-dbal/issues", - "source": "https://github.com/GemberPHP/rdbms-event-store-doctrine-dbal/tree/0.8.0" + "source": "https://github.com/GemberPHP/rdbms-event-store-doctrine-dbal/tree/0.9.0" }, - "time": "2025-09-03T17:42:28+00:00" + "time": "2025-10-10T07:57:48+00:00" }, { "name": "gember/serializer-symfony", - "version": "0.8.0", + "version": "0.9.0", "source": { "type": "git", "url": "https://github.com/GemberPHP/serializer-symfony.git", - "reference": "4872aafbdd337c7d86062faa3c38b9fbdb8078ad" + "reference": "0eb8d4534714b94e6258ddcbdae52f7755eb065e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/GemberPHP/serializer-symfony/zipball/4872aafbdd337c7d86062faa3c38b9fbdb8078ad", - "reference": "4872aafbdd337c7d86062faa3c38b9fbdb8078ad", + "url": "https://api.github.com/repos/GemberPHP/serializer-symfony/zipball/0eb8d4534714b94e6258ddcbdae52f7755eb065e", + "reference": "0eb8d4534714b94e6258ddcbdae52f7755eb065e", "shasum": "" }, "require": { - "gember/dependency-contracts": "^0.1", + "gember/dependency-contracts": "^0.2", "php": "^8.3", "symfony/serializer": "^7.1" }, @@ -531,9 +531,9 @@ ], "support": { "issues": "https://github.com/GemberPHP/serializer-symfony/issues", - "source": "https://github.com/GemberPHP/serializer-symfony/tree/0.8.0" + "source": "https://github.com/GemberPHP/serializer-symfony/tree/0.9.0" }, - "time": "2025-09-03T17:22:02+00:00" + "time": "2025-10-10T12:08:49+00:00" }, { "name": "psr/cache", diff --git a/config/services.yaml b/config/services.yaml index 2e1b6fa..7488823 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -145,12 +145,12 @@ services: class: Gember\EventSourcing\Util\Serialization\Serializer\Stacked\StackedSerializer arguments: - [ - '@gember.event_sourcing.util.serialization.serializer.serializable_domain_event.serializable_domain_event_serializer', + '@gember.event_sourcing.util.serialization.serializer.interface.serializable_interface_serializer', '@gember.serializer_symfony.symfony_serializer' ] - gember.event_sourcing.util.serialization.serializer.serializable_domain_event.serializable_domain_event_serializer: - class: Gember\EventSourcing\Util\Serialization\Serializer\SerializableDomainEvent\SerializableDomainEventSerializer + gember.event_sourcing.util.serialization.serializer.interface.serializable_interface_serializer: + class: Gember\EventSourcing\Util\Serialization\Serializer\Interface\SerializableInterfaceSerializer gember.serializer_symfony.symfony_serializer: class: Gember\SerializerSymfony\SymfonySerializer @@ -181,6 +181,7 @@ services: arguments: - '@gember.event_sourcing.resolver.domain_event.default.event_name.event_name_resolver' - '@gember.event_sourcing.resolver.common.domain_tag.domain_tag_resolver' + - '@gember.event_sourcing.resolver.common.saga_id.saga_id_resolver' gember.event_sourcing.resolver.domain_event.cached.cached_domain_event_resolver_decorator: class: Gember\EventSourcing\Resolver\DomainEvent\Cached\CachedDomainEventResolverDecorator @@ -254,7 +255,7 @@ services: gember.event_sourcing.resolver.use_case.use_case_resolver: class: Gember\EventSourcing\Resolver\UseCase\Default\DefaultUseCaseResolver arguments: - - '@gember.event_sourcing.resolver.common.domain_tag.domain_tag_resolver' + - '@gember.event_sourcing.resolver.common.domain_tag.attribute.attribute_domain_tag_resolver' - '@gember.event_sourcing.resolver.use_case.default.command_handler.command_handler_resolver' - '@gember.event_sourcing.resolver.use_case.default.event_subscriber.event_subscriber_resolver' @@ -264,4 +265,112 @@ services: arguments: - '@.inner' - '@gember.psr.simple_cache.cache_interface' - - '@gember.event_sourcing.util.string.friendly_class_namer.friendly_class_namer' \ No newline at end of file + - '@gember.event_sourcing.util.string.friendly_class_namer.friendly_class_namer' + + gember.event_sourcing.registry.saga.saga_registry: + class: Gember\EventSourcing\Registry\Saga\Reflector\ReflectorSagaRegistry + arguments: + - '@gember.event_sourcing.util.file.finder.finder' + - '@gember.event_sourcing.util.file.reflector.reflector' + - '@gember.event_sourcing.resolver.saga.saga_resolver' + - '@gember.event_sourcing.resolver.saga.default.event_subscriber.saga_event_subscriber_resolver' + + gember.event_sourcing.registry.saga.cached.cached_saga_registry_decorator: + class: Gember\EventSourcing\Registry\Saga\Cached\CachedSagaRegistryDecorator + #decorates: gember.event_sourcing.registry.saga.saga_registry + arguments: + - '@.inner' + - '@gember.psr.simple_cache.cache_interface' + + gember.event_sourcing.resolver.saga.saga_resolver: + class: Gember\EventSourcing\Resolver\Saga\Default\DefaultSagaResolver + arguments: + - '@gember.event_sourcing.resolver.saga.default.saga_name.saga_name_resolver' + - '@gember.event_sourcing.resolver.common.saga_id.saga_id_resolver' + - '@gember.event_sourcing.resolver.saga.default.event_subscriber.saga_event_subscriber_resolver' + + gember.event_sourcing.resolver.saga.cached.cached_saga_resolver_decorator: + class: Gember\EventSourcing\Resolver\Saga\Cached\CachedSagaResolverDecorator` + #decorates: gember.event_sourcing.resolver.saga.saga_resolver + arguments: + - '@.inner' + - '@gember.psr.simple_cache.cache_interface' + - '@gember.event_sourcing.util.string.friendly_class_namer.friendly_class_namer' + + gember.event_sourcing.resolver.saga.default.saga_name.saga_name_resolver: + class: Gember\EventSourcing\Resolver\Saga\Default\SagaName\Stacked\StackedSagaNameResolver + arguments: + - [ + '@gember.event_sourcing.resolver.saga.default.saga_name.attribute.attribute_saga_name_resolver', + '@gember.event_sourcing.resolver.saga.default.saga_name.interface.interface_saga_name_resolver' + ] + - '@gember.event_sourcing.resolver.saga.default.saga_name.class_name.class_name_saga_name_resolver' + + gember.event_sourcing.resolver.saga.default.saga_name.attribute.attribute_saga_name_resolver: + class: Gember\EventSourcing\Resolver\Saga\Default\SagaName\Attribute\AttributeSagaNameResolver + arguments: + - '@gember.event_sourcing.util.attribute.resolver.attribute_resolver' + + gember.event_sourcing.resolver.saga.default.saga_name.interface.interface_saga_name_resolver: + class: Gember\EventSourcing\Resolver\Saga\Default\SagaName\Interface\InterfaceSagaNameResolver + + gember.event_sourcing.resolver.saga.default.saga_name.class_name.class_name_saga_name_resolver: + class: Gember\EventSourcing\Resolver\Saga\Default\SagaName\ClassName\ClassNameSagaNameResolver + arguments: + - '@gember.event_sourcing.util.string.friendly_class_namer.friendly_class_namer' + + gember.event_sourcing.resolver.common.saga_id.saga_id_resolver: + class: Gember\EventSourcing\Resolver\Common\SagaId\Attribute\AttributeSagaIdResolver + arguments: + - '@gember.event_sourcing.util.attribute.resolver.attribute_resolver' + + gember.event_sourcing.resolver.saga.default.event_subscriber.saga_event_subscriber_resolver: + class: Gember\EventSourcing\Resolver\Saga\Default\EventSubscriber\Attribute\AttributeSagaEventSubscriberResolver + arguments: + - '@gember.event_sourcing.util.attribute.resolver.attribute_resolver' + + Gember\EventSourcing\Saga\SagaEventHandler: + class: Gember\EventSourcing\Saga\SagaEventHandler + arguments: + - '@gember.event_sourcing.resolver.domain_event.domain_event_resolver' + - '@gember.event_sourcing.registry.saga.saga_registry' + - '@gember.event_sourcing.repository.saga_store' + - '@gember.event_sourcing.util.messaging.message_bus.command_bus' + + gember.event_sourcing.util.messaging.message_bus.command_bus: + class: Gember\MessageBusSymfony\SymfonyCommandBus + arguments: + - '@gember.symfony.component.messenger.message_bus.command_bus' + + gember.symfony.component.messenger.message_bus.command_bus: '@command.bus' + + gember.event_sourcing.repository.saga_store: + class: Gember\EventSourcing\Repository\Rdbms\RdbmsSagaStore + arguments: + - '@gember.event_sourcing.resolver.saga.saga_resolver' + - '@gember.event_store.saga.rdbms_saga_store_repository' + - '@gember.event_sourcing.repository.rdbms.saga_factory' + - '@gember.event_sourcing.util.serialization.serializer.serializer' + - '@gember.event_sourcing.util.time.clock.clock' + + gember.event_sourcing.repository.rdbms.saga_factory: + class: Gember\EventSourcing\Repository\Rdbms\SagaFactory + arguments: + - '@gember.event_sourcing.util.serialization.serializer.serializer' + + gember.event_store.saga.rdbms_saga_store_repository: + class: Gember\RdbmsEventStoreDoctrineDbal\Saga\DoctrineRdbmsSagaStoreRepository + arguments: + - '@gember.doctrine.dbal.connection' + - '@gember.rdbms_event_store_doctrine_dbal.saga.table_schema.saga_store_table_schema' + - '@gember.rdbms_event_store_doctrine_dbal.saga.doctrine_dbal_rdbms_saga_factory' + + gember.rdbms_event_store_doctrine_dbal.saga.table_schema.saga_store_table_schema: + class: Gember\RdbmsEventStoreDoctrineDbal\Saga\TableSchema\SagaStoreTableSchema + factory: [ + Gember\RdbmsEventStoreDoctrineDbal\Saga\TableSchema\SagaTableSchemaFactory, + 'createDefaultSagaStore' + ] + + gember.rdbms_event_store_doctrine_dbal.saga.doctrine_dbal_rdbms_saga_factory: + class: Gember\RdbmsEventStoreDoctrineDbal\Saga\DoctrineDbalRdbmsSagaFactory \ No newline at end of file diff --git a/src/GemberEventSourcingBundle.php b/src/GemberEventSourcingBundle.php index 2052039..2a12549 100644 --- a/src/GemberEventSourcingBundle.php +++ b/src/GemberEventSourcingBundle.php @@ -4,6 +4,8 @@ namespace Gember\EventSourcingSymfonyBundle; +use Gember\EventSourcing\Saga\Attribute\SagaEventSubscriber; +use Gember\EventSourcing\Saga\SagaEventHandler; use Gember\EventSourcing\UseCase\Attribute\DomainCommandHandler; use Gember\EventSourcing\UseCase\CommandHandler\UseCaseCommandHandler; use Symfony\Component\Config\Definition\Configurator\DefinitionConfigurator; @@ -56,6 +58,15 @@ public function configure(DefinitionConfigurator $definition): void ->end() ->end() ->end() + ->arrayNode('saga') + ->children() + ->arrayNode('reflector') + ->children() + ->scalarNode('path')->end() + ->end() + ->end() + ->end() + ->end() ->end() ->end() ->arrayNode('message_bus') @@ -115,6 +126,9 @@ public function loadExtension(array $config, ContainerConfigurator $container, C $services->get('gember.event_sourcing.registry.command_handler.cached.cached_command_handler_registry_decorator') ->decorate('gember.event_sourcing.registry.command_handler.command_handler_registry'); + $services->get('gember.event_sourcing.registry.saga.cached.cached_saga_registry_decorator') + ->decorate('gember.event_sourcing.registry.saga.saga_registry'); + $services->get('gember.event_sourcing.resolver.domain_event.cached.cached_domain_event_resolver_decorator') ->decorate('gember.event_sourcing.resolver.domain_event.domain_event_resolver'); @@ -124,6 +138,9 @@ public function loadExtension(array $config, ContainerConfigurator $container, C $services->get('gember.event_sourcing.resolver.use_case.cached.cached_use_case_resolver_decorator') ->decorate('gember.event_sourcing.resolver.use_case.use_case_resolver'); + $services->get('gember.event_sourcing.resolver.saga.cached.cached_saga_resolver_decorator') + ->decorate('gember.event_sourcing.resolver.saga.saga_resolver'); + $cacheType = isset($config['cache']['psr16']) ? 'psr16' : 'psr6'; $cacheService = ltrim($config['cache'][$cacheType]['service'] ?? 'cache.app', '@'); @@ -148,6 +165,9 @@ public function loadExtension(array $config, ContainerConfigurator $container, C $services->get('gember.event_sourcing.registry.command_handler.command_handler_registry') ->arg('$path', $config['registry']['command_handler']['reflector']['path'] ?? '%kernel.project_dir%/src'); + $services->get('gember.event_sourcing.registry.saga.saga_registry') + ->arg('$path', $config['registry']['saga']['reflector']['path'] ?? '%kernel.project_dir%/src'); + if (!empty($config['message_bus']['symfony']['event_bus'] ?? null)) { $services->alias( 'gember.symfony.component.messenger.message_bus.event_bus', @@ -155,6 +175,13 @@ public function loadExtension(array $config, ContainerConfigurator $container, C ); } + if (!empty($config['message_bus']['symfony']['command_bus'] ?? null)) { + $services->alias( + 'gember.symfony.component.messenger.message_bus.command_bus', + ltrim($config['message_bus']['symfony']['command_bus'], '@'), + ); + } + if (!empty($config['event_store']['rdbms']['doctrine_dbal']['connection'] ?? null)) { $services->alias( 'gember.doctrine.dbal.connection', @@ -192,5 +219,22 @@ function (ChildDefinition $definition, DomainCommandHandler $attribute, Reflecti ]); }, ); + + $builder->registerAttributeForAutoconfiguration( + SagaEventSubscriber::class, + function (ChildDefinition $definition, SagaEventSubscriber $attribute, ReflectionMethod $reflector) use ($builder, $config): void { + $parameter = $reflector->getParameters()[0]; + + $bus = $config['message_bus']['symfony']['event_bus'] ?? 'event.bus'; + + $builder + ->getDefinition(SagaEventHandler::class) + ->addTag('messenger.message_handler', [ + 'bus' => str_starts_with($bus, '@') ? substr($bus, 1) : $bus, + 'handles' => $parameter->getType()->getName(), + 'method' => '__invoke', + ]); + }, + ); } }