From d8b54fe459e5780b8711a7c43c9567212158c537 Mon Sep 17 00:00:00 2001 From: Florent Blaison Date: Sun, 29 Apr 2018 23:42:39 +0200 Subject: [PATCH 1/5] Allow to add a custom event store to AggregateRepository --- .../Aggregate/AggregateRepositoryFactory.php | 8 +- .../AggregateRepositoryFactoryTest.php | 65 ++++++++++ tests/Mock/EventStoreMock.php | 111 ++++++++++++++++++ 3 files changed, 183 insertions(+), 1 deletion(-) create mode 100644 tests/Mock/EventStoreMock.php diff --git a/src/Container/Aggregate/AggregateRepositoryFactory.php b/src/Container/Aggregate/AggregateRepositoryFactory.php index 18b3ff1..d024ee2 100644 --- a/src/Container/Aggregate/AggregateRepositoryFactory.php +++ b/src/Container/Aggregate/AggregateRepositoryFactory.php @@ -81,7 +81,13 @@ public function __invoke(ContainerInterface $container): AggregateRepository throw ConfigurationException::configurationError(sprintf('Repository class %s must be a sub class of %s', $repositoryClass, AggregateRepository::class)); } - $eventStore = $container->get(EventStore::class); + $eventStoreClass = $config['event_store_class'] ?? EventStore::class; + + $eventStore = $container->get($eventStoreClass); + + if (! $eventStore instanceof EventStore) { + throw ConfigurationException::configurationError(sprintf('Event store class %s must be of type %s', $eventStoreClass, EventStore::class)); + } if (is_array($config['aggregate_type'])) { $aggregateType = AggregateType::fromMapping($config['aggregate_type']); diff --git a/tests/Container/Aggregate/AggregateRepositoryFactoryTest.php b/tests/Container/Aggregate/AggregateRepositoryFactoryTest.php index 80d0028..2c2b1c0 100644 --- a/tests/Container/Aggregate/AggregateRepositoryFactoryTest.php +++ b/tests/Container/Aggregate/AggregateRepositoryFactoryTest.php @@ -17,6 +17,7 @@ use Prooph\EventSourcing\Container\Aggregate\AggregateRepositoryFactory; use Prooph\EventStore\EventStore; use Prooph\EventStore\Exception\ConfigurationException; +use ProophTest\EventSourcing\Mock\EventStoreMock; use ProophTest\EventSourcing\Mock\RepositoryMock; use ProophTest\EventStore\ActionEventEmitterEventStoreTestCase; use ProophTest\EventStore\Mock\User; @@ -54,6 +55,37 @@ public function it_creates_an_aggregate_from_static_call(): void self::assertInstanceOf(RepositoryMock::class, $factory($container->reveal())); } + /** + * @test + */ + public function it_creates_an_aggregate_from_static_call_with_custom_event_store(): void + { + $container = $this->prophesize(ContainerInterface::class); + $container->has('config')->willReturn(true); + $container->get('config')->willReturn([ + 'prooph' => [ + 'event_sourcing' => [ + 'aggregate_repository' => [ + 'repository_mock' => [ + 'repository_class' => RepositoryMock::class, + 'event_store_class' => EventStoreMock::class, + 'aggregate_type' => User::class, + 'aggregate_translator' => 'user_translator', + ], + ], + ], + ], + ]); + $container->get(EventStoreMock::class)->willReturn($this->eventStore); + + $userTranslator = $this->prophesize(AggregateTranslator::class); + + $container->get('user_translator')->willReturn($userTranslator->reveal()); + + $factory = [AggregateRepositoryFactory::class, 'repository_mock']; + self::assertInstanceOf(RepositoryMock::class, $factory($container->reveal())); + } + /** * @test */ @@ -119,6 +151,39 @@ public function it_throws_exception_when_invalid_repository_class_given(): void $factory->__invoke($container->reveal()); } + /** + * @test + */ + public function it_throws_exception_when_invalid_event_store_class_given(): void + { + $this->expectException(ConfigurationException::class); + + $container = $this->prophesize(ContainerInterface::class); + $container->has('config')->willReturn(true); + $container->get('config')->willReturn([ + 'prooph' => [ + 'event_sourcing' => [ + 'aggregate_repository' => [ + 'repository_mock' => [ + 'repository_class' => RepositoryMock::class, + 'event_store_class' => 'stdClass', + 'aggregate_type' => User::class, + 'aggregate_translator' => 'user_translator', + ], + ], + ], + ], + ]); + $container->get('stdClass')->willReturn('stdClass'); + + $userTranslator = $this->prophesize(AggregateTranslator::class); + + $container->get('user_translator')->willReturn($userTranslator->reveal()); + + $factory = [AggregateRepositoryFactory::class, 'repository_mock']; + self::assertInstanceOf(RepositoryMock::class, $factory($container->reveal())); + } + /** * @test */ diff --git a/tests/Mock/EventStoreMock.php b/tests/Mock/EventStoreMock.php new file mode 100644 index 0000000..902a0ba --- /dev/null +++ b/tests/Mock/EventStoreMock.php @@ -0,0 +1,111 @@ + + * (c) 2015-2018 Sascha-Oliver Prolic + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ProophTest\EventSourcing\Mock; + +use Iterator; +use Prooph\EventStore\EventStore; +use Prooph\EventStore\Metadata\MetadataMatcher; +use Prooph\EventStore\Stream; +use Prooph\EventStore\StreamName; + +final class EventStoreMock implements EventStore +{ + + public function updateStreamMetadata(StreamName $streamName, array $newMetadata): void + { + + } + + public function create(Stream $stream): void + { + + } + + public function appendTo(StreamName $streamName, Iterator $streamEvents): void + { + + } + + public function delete(StreamName $streamName): void + { + + } + + public function fetchStreamMetadata(StreamName $streamName): array + { + return []; + } + + public function hasStream(StreamName $streamName): bool + { + return true; + } + + public function load( + StreamName $streamName, + int $fromNumber = 1, + int $count = null, + MetadataMatcher $metadataMatcher = null + ): Iterator { + return new \ArrayIterator(); + } + + public function loadReverse( + StreamName $streamName, + int $fromNumber = null, + int $count = null, + MetadataMatcher $metadataMatcher = null + ): Iterator { + return new \ArrayIterator(); + } + + /** + * @return StreamName[] + */ + public function fetchStreamNames( + ?string $filter, + ?MetadataMatcher $metadataMatcher, + int $limit = 20, + int $offset = 0 + ): array { + return []; + } + + /** + * @return StreamName[] + */ + public function fetchStreamNamesRegex( + string $filter, + ?MetadataMatcher $metadataMatcher, + int $limit = 20, + int $offset = 0 + ): array { + return []; + } + + /** + * @return string[] + */ + public function fetchCategoryNames(?string $filter, int $limit = 20, int $offset = 0): array + { + return []; + } + + /** + * @return string[] + */ + public function fetchCategoryNamesRegex(string $filter, int $limit = 20, int $offset = 0): array + { + return []; + } +} From 5223e14aefe08c13d61a940adc58e57d151fdc1c Mon Sep 17 00:00:00 2001 From: Florent Blaison Date: Sun, 29 Apr 2018 23:49:50 +0200 Subject: [PATCH 2/5] CS fix --- tests/Mock/EventStoreMock.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/Mock/EventStoreMock.php b/tests/Mock/EventStoreMock.php index 902a0ba..1719f0f 100644 --- a/tests/Mock/EventStoreMock.php +++ b/tests/Mock/EventStoreMock.php @@ -20,25 +20,20 @@ final class EventStoreMock implements EventStore { - public function updateStreamMetadata(StreamName $streamName, array $newMetadata): void { - } public function create(Stream $stream): void { - } public function appendTo(StreamName $streamName, Iterator $streamEvents): void { - } public function delete(StreamName $streamName): void { - } public function fetchStreamMetadata(StreamName $streamName): array From a8ff6d74f7c808afa745aa0bf0883de8aae270f5 Mon Sep 17 00:00:00 2001 From: Florent Blaison Date: Mon, 30 Apr 2018 11:06:09 +0200 Subject: [PATCH 3/5] Update from code review and change tests --- .../Aggregate/AggregateRepositoryFactory.php | 23 ++++++++++++------- .../AggregateRepositoryFactoryTest.php | 6 ++--- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/Container/Aggregate/AggregateRepositoryFactory.php b/src/Container/Aggregate/AggregateRepositoryFactory.php index d024ee2..7f51382 100644 --- a/src/Container/Aggregate/AggregateRepositoryFactory.php +++ b/src/Container/Aggregate/AggregateRepositoryFactory.php @@ -13,6 +13,7 @@ namespace Prooph\EventSourcing\Container\Aggregate; use Interop\Config\ConfigurationTrait; +use Interop\Config\ProvidesDefaultOptions; use Interop\Config\RequiresConfigId; use Interop\Config\RequiresMandatoryOptions; use InvalidArgumentException; @@ -23,7 +24,7 @@ use Prooph\EventStore\StreamName; use Psr\Container\ContainerInterface; -final class AggregateRepositoryFactory implements RequiresConfigId, RequiresMandatoryOptions +final class AggregateRepositoryFactory implements RequiresConfigId, RequiresMandatoryOptions, ProvidesDefaultOptions { use ConfigurationTrait; @@ -81,13 +82,7 @@ public function __invoke(ContainerInterface $container): AggregateRepository throw ConfigurationException::configurationError(sprintf('Repository class %s must be a sub class of %s', $repositoryClass, AggregateRepository::class)); } - $eventStoreClass = $config['event_store_class'] ?? EventStore::class; - - $eventStore = $container->get($eventStoreClass); - - if (! $eventStore instanceof EventStore) { - throw ConfigurationException::configurationError(sprintf('Event store class %s must be of type %s', $eventStoreClass, EventStore::class)); - } + $eventStore = $container->get($config['event_store']); if (is_array($config['aggregate_type'])) { $aggregateType = AggregateType::fromMapping($config['aggregate_type']); @@ -126,4 +121,16 @@ public function mandatoryOptions(): iterable 'aggregate_translator', ]; } + + /** + * Returns a list of default options, which are merged in \Interop\Config\RequiresConfig::options() + * + * @return iterable List with default options and values, can be nested + */ + public function defaultOptions() : iterable + { + return [ + 'event_store' => EventStore::class, + ]; + } } diff --git a/tests/Container/Aggregate/AggregateRepositoryFactoryTest.php b/tests/Container/Aggregate/AggregateRepositoryFactoryTest.php index 2c2b1c0..3e1f393 100644 --- a/tests/Container/Aggregate/AggregateRepositoryFactoryTest.php +++ b/tests/Container/Aggregate/AggregateRepositoryFactoryTest.php @@ -68,7 +68,7 @@ public function it_creates_an_aggregate_from_static_call_with_custom_event_store 'aggregate_repository' => [ 'repository_mock' => [ 'repository_class' => RepositoryMock::class, - 'event_store_class' => EventStoreMock::class, + 'event_store' => EventStoreMock::class, 'aggregate_type' => User::class, 'aggregate_translator' => 'user_translator', ], @@ -156,7 +156,7 @@ public function it_throws_exception_when_invalid_repository_class_given(): void */ public function it_throws_exception_when_invalid_event_store_class_given(): void { - $this->expectException(ConfigurationException::class); + $this->expectException(\TypeError::class); $container = $this->prophesize(ContainerInterface::class); $container->has('config')->willReturn(true); @@ -166,7 +166,7 @@ public function it_throws_exception_when_invalid_event_store_class_given(): void 'aggregate_repository' => [ 'repository_mock' => [ 'repository_class' => RepositoryMock::class, - 'event_store_class' => 'stdClass', + 'event_store' => 'stdClass', 'aggregate_type' => User::class, 'aggregate_translator' => 'user_translator', ], From a3f873e00d51e0b3944a1694b2525e6c321f6543 Mon Sep 17 00:00:00 2001 From: Florent Blaison Date: Mon, 30 Apr 2018 11:21:50 +0200 Subject: [PATCH 4/5] FIX php cs --- src/Container/Aggregate/AggregateRepositoryFactory.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Container/Aggregate/AggregateRepositoryFactory.php b/src/Container/Aggregate/AggregateRepositoryFactory.php index 7f51382..378ab00 100644 --- a/src/Container/Aggregate/AggregateRepositoryFactory.php +++ b/src/Container/Aggregate/AggregateRepositoryFactory.php @@ -127,7 +127,7 @@ public function mandatoryOptions(): iterable * * @return iterable List with default options and values, can be nested */ - public function defaultOptions() : iterable + public function defaultOptions(): iterable { return [ 'event_store' => EventStore::class, From 09826ffd817415e6be5e8812958305fd96eecc8e Mon Sep 17 00:00:00 2001 From: Florent Blaison Date: Mon, 30 Apr 2018 11:52:41 +0200 Subject: [PATCH 5/5] ADD documentation --- docs/interop_factories.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/interop_factories.md b/docs/interop_factories.md index 39c96ff..989bfc0 100644 --- a/docs/interop_factories.md +++ b/docs/interop_factories.md @@ -96,6 +96,24 @@ You can also configure a custom stream name (default is `event_stream`): ] ``` +You can add your custom event store too (default is `EventStore::class`): +```php +[ + 'prooph' => [ + 'event_sourcing' => [ + 'aggregate_repository' => [ + 'user_repository' => [ + 'repository_class' => MyUserRepository::class, + 'event_store' => MyCustomEventStore::class, // <-- Custom event store service id + 'aggregate_type' => MyUser::class, + 'aggregate_translator' => 'user_translator', + ], + ], + ], + ], +] +``` + Last but not least you can enable the so called "One-Stream-Per-Aggregate-Mode": ```php [