diff --git a/.gitignore b/.gitignore index 41248c4c..22a0e157 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ phpunit.xml phpstan.neon infection.html infection.log +/docs_php diff --git a/composer.json b/composer.json index d5dc8bee..0e81239c 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,7 @@ ], "require": { "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", - "patchlevel/event-sourcing": "^3.13.0", + "patchlevel/event-sourcing": "^3.18.0", "symfony/cache": "^6.4.0 || ^7.0.0 || ^8.0.0", "symfony/config": "^6.4.0 || ^7.0.0 || ^8.0.0", "symfony/console": "^6.4.1 || ^7.0.1 || ^8.0.0", diff --git a/composer.lock b/composer.lock index 1b1bc6c4..336afe2d 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": "bef09ad87864285aced2976dc36cb3d1", + "content-hash": "51590c2ee68cba244bb21097c7c48a92", "packages": [ { "name": "brick/math", diff --git a/src/Command/StoreMigrateCommand.php b/src/Command/StoreMigrateCommand.php index cbd7a2c4..a9342d89 100644 --- a/src/Command/StoreMigrateCommand.php +++ b/src/Command/StoreMigrateCommand.php @@ -17,6 +17,7 @@ use function count; +/** @deprecated since version 3.15.0, to be removed in 4.0.0. Use the StoreMigrateCommand from the patchlevel/event-sourcing package instead. */ #[AsCommand( 'event-sourcing:store:migrate', 'migrate events from one store to another', diff --git a/src/DependencyInjection/DoctrineCleanupCompilerPass.php b/src/DependencyInjection/DoctrineCleanupCompilerPass.php new file mode 100644 index 00000000..7e82bc55 --- /dev/null +++ b/src/DependencyInjection/DoctrineCleanupCompilerPass.php @@ -0,0 +1,30 @@ +has('doctrine')) { + $container->register(DbalCleanupTaskHandler::class, DbalCleanupTaskHandler::class) + ->setArguments([ + new Reference('doctrine'), + ]) + ->addTag('event_sourcing.cleanup_task_handler'); + } elseif ($container->has('event_sourcing.dbal_public_connection')) { + $container->register(DbalCleanupTaskHandler::class, DbalCleanupTaskHandler::class) + ->setArguments([ + new Reference('event_sourcing.dbal_public_connection'), + ]) + ->addTag('event_sourcing.cleanup_task_handler'); + } + } +} diff --git a/src/DependencyInjection/PatchlevelEventSourcingExtension.php b/src/DependencyInjection/PatchlevelEventSourcingExtension.php index 4f8d6bf3..22192181 100644 --- a/src/DependencyInjection/PatchlevelEventSourcingExtension.php +++ b/src/DependencyInjection/PatchlevelEventSourcingExtension.php @@ -32,9 +32,11 @@ use Patchlevel\EventSourcing\Console\Command\SchemaUpdateCommand; use Patchlevel\EventSourcing\Console\Command\ShowAggregateCommand; use Patchlevel\EventSourcing\Console\Command\ShowCommand; +use Patchlevel\EventSourcing\Console\Command\StoreMigrateCommand; use Patchlevel\EventSourcing\Console\Command\SubscriptionBootCommand; use Patchlevel\EventSourcing\Console\Command\SubscriptionPauseCommand; use Patchlevel\EventSourcing\Console\Command\SubscriptionReactivateCommand; +use Patchlevel\EventSourcing\Console\Command\SubscriptionRefreshCommand; use Patchlevel\EventSourcing\Console\Command\SubscriptionRemoveCommand; use Patchlevel\EventSourcing\Console\Command\SubscriptionRunCommand; use Patchlevel\EventSourcing\Console\Command\SubscriptionSetupCommand; @@ -94,6 +96,9 @@ use Patchlevel\EventSourcing\Store\Store; use Patchlevel\EventSourcing\Store\StreamDoctrineDbalStore; use Patchlevel\EventSourcing\Store\StreamReadOnlyStore; +use Patchlevel\EventSourcing\Subscription\Cleanup\Cleaner; +use Patchlevel\EventSourcing\Subscription\Cleanup\CleanupTaskHandler; +use Patchlevel\EventSourcing\Subscription\Cleanup\DefaultCleaner; use Patchlevel\EventSourcing\Subscription\Engine\CatchUpSubscriptionEngine; use Patchlevel\EventSourcing\Subscription\Engine\DefaultSubscriptionEngine; use Patchlevel\EventSourcing\Subscription\Engine\GapResolverStoreMessageLoader; @@ -116,7 +121,6 @@ use Patchlevel\EventSourcing\Subscription\Subscriber\SubscriberHelper; use Patchlevel\EventSourcingBundle\Attribute\AsListener; use Patchlevel\EventSourcingBundle\Clock\FrozenClockFactory; -use Patchlevel\EventSourcingBundle\Command\StoreMigrateCommand; use Patchlevel\EventSourcingBundle\CommandBus\SymfonyCommandBus; use Patchlevel\EventSourcingBundle\DataCollector\EventSourcingCollector; use Patchlevel\EventSourcingBundle\DataCollector\MessageCollectorEventBus; @@ -516,6 +520,16 @@ static function (ChildDefinition $definition): void { $container->setAlias(SubscriberAccessorRepository::class, MetadataSubscriberAccessorRepository::class); + $container->registerForAutoconfiguration(CleanupTaskHandler::class) + ->addTag('event_sourcing.cleanup_task_handler'); + + $container->register(DefaultCleaner::class) + ->setArguments([ + new TaggedIteratorArgument('event_sourcing.cleanup_task_handler'), + ]); + + $container->setAlias(Cleaner::class, DefaultCleaner::class); + $container->register(DefaultSubscriptionEngine::class) ->setArguments([ new Reference(MessageLoader::class), @@ -523,6 +537,7 @@ static function (ChildDefinition $definition): void { new Reference(SubscriberAccessorRepository::class), new Reference(RetryStrategyRepository::class), new Reference('logger', ContainerInterface::NULL_ON_INVALID_REFERENCE), + new Reference(Cleaner::class), ]) ->addTag('monolog.logger', ['channel' => 'event_sourcing']); @@ -981,6 +996,12 @@ private function configureCommands(ContainerBuilder $container): void new Reference(SubscriptionEngine::class), ]) ->addTag('console.command'); + + $container->register(SubscriptionRefreshCommand::class) + ->setArguments([ + new Reference(SubscriptionEngine::class), + ]) + ->addTag('console.command'); } /** @param Config $config */ diff --git a/src/PatchlevelEventSourcingBundle.php b/src/PatchlevelEventSourcingBundle.php index 81c73768..40d129c9 100644 --- a/src/PatchlevelEventSourcingBundle.php +++ b/src/PatchlevelEventSourcingBundle.php @@ -5,6 +5,7 @@ namespace Patchlevel\EventSourcingBundle; use Patchlevel\EventSourcingBundle\DependencyInjection\CommandHandlerCompilerPass; +use Patchlevel\EventSourcingBundle\DependencyInjection\DoctrineCleanupCompilerPass; use Patchlevel\EventSourcingBundle\DependencyInjection\HandlerServiceLocatorCompilerPass; use Patchlevel\EventSourcingBundle\DependencyInjection\QueryHandlerCompilerPass; use Patchlevel\EventSourcingBundle\DependencyInjection\RepositoryCompilerPass; @@ -23,5 +24,6 @@ public function build(ContainerBuilder $container): void $container->addCompilerPass(new QueryHandlerCompilerPass(), priority: 100); $container->addCompilerPass(new HandlerServiceLocatorCompilerPass(), priority: -100); $container->addCompilerPass(new TranslatorCompilerPass()); + $container->addCompilerPass(new DoctrineCleanupCompilerPass()); } } diff --git a/tests/Unit/TestCaseAllPublicCompilerPass.php b/tests/Unit/AllPublicCompilerPass.php similarity index 86% rename from tests/Unit/TestCaseAllPublicCompilerPass.php rename to tests/Unit/AllPublicCompilerPass.php index cf579bd0..36123ab9 100644 --- a/tests/Unit/TestCaseAllPublicCompilerPass.php +++ b/tests/Unit/AllPublicCompilerPass.php @@ -7,7 +7,7 @@ use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; -final class TestCaseAllPublicCompilerPass implements CompilerPassInterface +final class AllPublicCompilerPass implements CompilerPassInterface { private const SERVICE_PREFIX = 'event_sourcing.'; private const NAMESPACE_PREFIX = 'Patchlevel\\'; @@ -21,7 +21,7 @@ public function process(ContainerBuilder $container): void } foreach ($container->getAliases() as $id => $alias) { - if ($this->isOwnService($id)) { + if ($this->isOwnService($id) || $this->isOwnService((string)$alias)) { $alias->setPublic(true); } } diff --git a/tests/Unit/NoLazyCompilerPass.php b/tests/Unit/NoLazyCompilerPass.php new file mode 100644 index 00000000..10054305 --- /dev/null +++ b/tests/Unit/NoLazyCompilerPass.php @@ -0,0 +1,16 @@ +getDefinitions() as $definition) { + $definition->setLazy(false); + } + } +} \ No newline at end of file diff --git a/tests/Unit/PatchlevelEventSourcingBundleTest.php b/tests/Unit/PatchlevelEventSourcingBundleTest.php index fd20caff..94397426 100644 --- a/tests/Unit/PatchlevelEventSourcingBundleTest.php +++ b/tests/Unit/PatchlevelEventSourcingBundleTest.php @@ -4,12 +4,12 @@ use ArrayObject; use Doctrine\DBAL\Connection; -use Doctrine\DBAL\Platforms\PostgreSQLPlatform; use Doctrine\Migrations\Tools\Console\Command\CurrentCommand; use Doctrine\Migrations\Tools\Console\Command\DiffCommand; use Doctrine\Migrations\Tools\Console\Command\ExecuteCommand; use Doctrine\Migrations\Tools\Console\Command\MigrateCommand; use Doctrine\Migrations\Tools\Console\Command\StatusCommand; +use Doctrine\Persistence\ManagerRegistry; use InvalidArgumentException; use Patchlevel\EventSourcing\Attribute\Aggregate; use Patchlevel\EventSourcing\Attribute\Event; @@ -26,9 +26,11 @@ use Patchlevel\EventSourcing\Console\Command\SchemaUpdateCommand; use Patchlevel\EventSourcing\Console\Command\ShowAggregateCommand; use Patchlevel\EventSourcing\Console\Command\ShowCommand; +use Patchlevel\EventSourcing\Console\Command\StoreMigrateCommand; use Patchlevel\EventSourcing\Console\Command\SubscriptionBootCommand; use Patchlevel\EventSourcing\Console\Command\SubscriptionPauseCommand; use Patchlevel\EventSourcing\Console\Command\SubscriptionReactivateCommand; +use Patchlevel\EventSourcing\Console\Command\SubscriptionRefreshCommand; use Patchlevel\EventSourcing\Console\Command\SubscriptionRemoveCommand; use Patchlevel\EventSourcing\Console\Command\SubscriptionRunCommand; use Patchlevel\EventSourcing\Console\Command\SubscriptionSetupCommand; @@ -67,6 +69,9 @@ use Patchlevel\EventSourcing\Store\StreamDoctrineDbalStore; use Patchlevel\EventSourcing\Store\StreamReadOnlyStore; use Patchlevel\EventSourcing\Store\StreamStore; +use Patchlevel\EventSourcing\Subscription\Cleanup\Cleaner; +use Patchlevel\EventSourcing\Subscription\Cleanup\Dbal\DbalCleanupTaskHandler; +use Patchlevel\EventSourcing\Subscription\Cleanup\DefaultCleaner; use Patchlevel\EventSourcing\Subscription\Engine\CatchUpSubscriptionEngine; use Patchlevel\EventSourcing\Subscription\Engine\DefaultSubscriptionEngine; use Patchlevel\EventSourcing\Subscription\Engine\GapResolverStoreMessageLoader; @@ -82,8 +87,6 @@ use Patchlevel\EventSourcing\Subscription\Store\InMemorySubscriptionStore; use Patchlevel\EventSourcing\Subscription\Store\SubscriptionStore; use Patchlevel\EventSourcing\Subscription\Subscriber\MetadataSubscriberAccessorRepository; -use Patchlevel\EventSourcingBundle\Command\StoreMigrateCommand; -use Patchlevel\EventSourcingBundle\DependencyInjection\Configuration; use Patchlevel\EventSourcingBundle\DependencyInjection\PatchlevelEventSourcingExtension; use Patchlevel\EventSourcingBundle\EventBus\SymfonyEventBus; use Patchlevel\EventSourcingBundle\PatchlevelEventSourcingBundle; @@ -374,6 +377,7 @@ public function testMigrateStore(): void $container = new ContainerBuilder(); $container->register('my_translator', ExcludeEventWithHeaderTranslator::class) + ->setPublic(true) ->setArguments([ArchivedHeader::class]); $this->compileContainer( @@ -786,6 +790,66 @@ public function testGapDetectionWithExplicitValues(): void self::assertInstanceOf(GapResolverStoreMessageLoader::class, $messageLoader); } + public function testSubscriptionCleanupWithDoctrine(): void + { + $registry = $this->createMock(ManagerRegistry::class); + + $container = new ContainerBuilder(); + $container->set('doctrine', $registry); + + $this->compileContainer( + $container, + [ + 'patchlevel_event_sourcing' => [ + 'connection' => [ + 'service' => 'doctrine.dbal.eventstore_connection', + ], + 'store' => [ + 'merge_orm_schema' => true, + ], + ], + ] + ); + + $definition = $container->getDefinition(DbalCleanupTaskHandler::class); + $tags = $definition->getTag('event_sourcing.cleanup_task_handler'); + + self::assertCount(1, $tags); + + $cleaner = $container->get(Cleaner::class); + self::assertInstanceOf(DefaultCleaner::class, $cleaner); + + $cleanupTaskHandler = $container->get(DbalCleanupTaskHandler::class); + self::assertInstanceOf(DbalCleanupTaskHandler::class, $cleanupTaskHandler); + } + + public function testSubscriptionCleanupWithProjectionConnection(): void + { + $container = new ContainerBuilder(); + $this->compileContainer( + $container, + [ + 'patchlevel_event_sourcing' => [ + 'connection' => [ + 'url' => 'sqlite3:///:memory:', + 'provide_dedicated_connection' => true, + ], + ], + ] + ); + + $definition = $container->getDefinition(DbalCleanupTaskHandler::class); + $tags = $definition->getTag('event_sourcing.cleanup_task_handler'); + + self::assertCount(1, $tags); + + $cleaner = $container->get(Cleaner::class); + self::assertInstanceOf(DefaultCleaner::class, $cleaner); + + $cleanupTaskHandler = $container->get(DbalCleanupTaskHandler::class); + self::assertInstanceOf(DbalCleanupTaskHandler::class, $cleanupTaskHandler); + } + public function testSnapshotStore(): void { $container = new ContainerBuilder(); @@ -1013,6 +1077,7 @@ public function testCommands(): void self::assertInstanceOf(SubscriptionSetupCommand::class, $container->get(SubscriptionSetupCommand::class)); self::assertInstanceOf(SubscriptionStatusCommand::class, $container->get(SubscriptionStatusCommand::class)); self::assertInstanceOf(SubscriptionTeardownCommand::class, $container->get(SubscriptionTeardownCommand::class)); + self::assertInstanceOf(SubscriptionRefreshCommand::class, $container->get(SubscriptionRefreshCommand::class)); self::assertInstanceOf(SchemaCreateCommand::class, $container->get(SchemaCreateCommand::class)); self::assertInstanceOf(SchemaUpdateCommand::class, $container->get(SchemaUpdateCommand::class)); self::assertInstanceOf(SchemaDropCommand::class, $container->get(SchemaDropCommand::class)); @@ -1564,10 +1629,6 @@ private function compileContainer(ContainerBuilder $container, array $config): v $container->setParameter('kernel.project_dir', __DIR__); $connection = $this->createMock(Connection::class); - $connection - ->expects($this->never()) - ->method('getDatabasePlatform') - ->willReturn(new PostgreSQLPlatform()); $container->set('doctrine.dbal.eventstore_connection', $connection); $container->set('event.bus', $this->createMock(MessageBusInterface::class)); @@ -1583,10 +1644,19 @@ private function compileContainer(ContainerBuilder $container, array $config): v $compilerPassConfig = $container->getCompilerPassConfig(); $compilerPassConfig->setRemovingPasses([]); - $compilerPassConfig->addPass(new TestCaseAllPublicCompilerPass()); + $compilerPassConfig->addPass(new AllPublicCompilerPass()); + $compilerPassConfig->addPass(new NoLazyCompilerPass()); $container->compile(); + foreach ($container->getServiceIds() as $id) { + if (str_ends_with($id, '.inner')) { + continue; + } + + $container->get($id); + } + (new XmlDumper($container))->dump(); } }