diff --git a/src/Aggregate/AggregateRoot.php b/src/Aggregate/AggregateRoot.php index a0bcfa29..71de1d3e 100644 --- a/src/Aggregate/AggregateRoot.php +++ b/src/Aggregate/AggregateRoot.php @@ -4,17 +4,16 @@ namespace Patchlevel\EventSourcing\Aggregate; -use Patchlevel\EventSourcing\Attribute\Apply; -use Patchlevel\EventSourcing\Attribute\SuppressMissingApply; use Patchlevel\EventSourcing\EventBus\Message; -use ReflectionClass; +use Patchlevel\EventSourcing\Metadata\AggregateRoot\AggregateRootMetadata; +use Patchlevel\EventSourcing\Metadata\AggregateRoot\AggregateRootMetadataFactory; +use Patchlevel\EventSourcing\Metadata\AggregateRoot\AttributeAggregateRootMetadataFactory; use function array_key_exists; abstract class AggregateRoot { - /** @var array, AggregateRootMetadata> */ - private static array $metadata = []; + private static ?AggregateRootMetadataFactory $metadataFactory = null; /** @var list */ private array $uncommittedMessages = []; @@ -34,7 +33,7 @@ protected function apply(object $event): void if (!array_key_exists($event::class, $metadata->applyMethods)) { if (!$metadata->suppressAll && !array_key_exists($event::class, $metadata->suppressEvents)) { - throw new ApplyAttributeNotFound($this, $event); + throw new ApplyMethodNotFound($this, $event); } return; @@ -96,51 +95,10 @@ final public function playhead(): int private static function metadata(): AggregateRootMetadata { - if (array_key_exists(static::class, self::$metadata)) { - return self::$metadata[static::class]; + if (!self::$metadataFactory) { + self::$metadataFactory = new AttributeAggregateRootMetadataFactory(); } - $metadata = new AggregateRootMetadata(); - - $reflector = new ReflectionClass(static::class); - $attributes = $reflector->getAttributes(SuppressMissingApply::class); - - foreach ($attributes as $attribute) { - $instance = $attribute->newInstance(); - - if ($instance->suppressAll()) { - $metadata->suppressAll = true; - - continue; - } - - foreach ($instance->suppressEvents() as $event) { - $metadata->suppressEvents[$event] = true; - } - } - - $methods = $reflector->getMethods(); - - foreach ($methods as $method) { - $attributes = $method->getAttributes(Apply::class); - - foreach ($attributes as $attribute) { - $instance = $attribute->newInstance(); - $eventClass = $instance->eventClass(); - - if (array_key_exists($eventClass, $metadata->applyMethods)) { - throw new DuplicateApplyMethod( - self::class, - $eventClass, - $metadata->applyMethods[$eventClass], - $method->getName() - ); - } - - $metadata->applyMethods[$eventClass] = $method->getName(); - } - } - - return $metadata; + return self::$metadataFactory->metadata(static::class); } } diff --git a/src/Aggregate/ApplyAttributeNotFound.php b/src/Aggregate/ApplyMethodNotFound.php similarity index 86% rename from src/Aggregate/ApplyAttributeNotFound.php rename to src/Aggregate/ApplyMethodNotFound.php index 2b3a6838..0c5195dd 100644 --- a/src/Aggregate/ApplyAttributeNotFound.php +++ b/src/Aggregate/ApplyMethodNotFound.php @@ -6,7 +6,7 @@ use function sprintf; -final class ApplyAttributeNotFound extends AggregateException +final class ApplyMethodNotFound extends AggregateException { public function __construct(AggregateRoot $aggregate, object $event) { diff --git a/src/Aggregate/AggregateRootMetadata.php b/src/Metadata/AggregateRoot/AggregateRootMetadata.php similarity index 83% rename from src/Aggregate/AggregateRootMetadata.php rename to src/Metadata/AggregateRoot/AggregateRootMetadata.php index eda65e75..6e356e03 100644 --- a/src/Aggregate/AggregateRootMetadata.php +++ b/src/Metadata/AggregateRoot/AggregateRootMetadata.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Patchlevel\EventSourcing\Aggregate; +namespace Patchlevel\EventSourcing\Metadata\AggregateRoot; /** * @internal diff --git a/src/Metadata/AggregateRoot/AggregateRootMetadataFactory.php b/src/Metadata/AggregateRoot/AggregateRootMetadataFactory.php new file mode 100644 index 00000000..f6a07199 --- /dev/null +++ b/src/Metadata/AggregateRoot/AggregateRootMetadataFactory.php @@ -0,0 +1,15 @@ + $aggregate + */ + public function metadata(string $aggregate): AggregateRootMetadata; +} diff --git a/src/Metadata/AggregateRoot/AttributeAggregateRootMetadataFactory.php b/src/Metadata/AggregateRoot/AttributeAggregateRootMetadataFactory.php new file mode 100644 index 00000000..77d8e716 --- /dev/null +++ b/src/Metadata/AggregateRoot/AttributeAggregateRootMetadataFactory.php @@ -0,0 +1,73 @@ +, AggregateRootMetadata> */ + private array $aggregateMetadata = []; + + /** + * @param class-string $aggregate + */ + public function metadata(string $aggregate): AggregateRootMetadata + { + if (array_key_exists($aggregate, $this->aggregateMetadata)) { + return $this->aggregateMetadata[$aggregate]; + } + + $metadata = new AggregateRootMetadata(); + + $reflector = new ReflectionClass($aggregate); + $attributes = $reflector->getAttributes(SuppressMissingApply::class); + + foreach ($attributes as $attribute) { + $instance = $attribute->newInstance(); + + if ($instance->suppressAll()) { + $metadata->suppressAll = true; + + continue; + } + + foreach ($instance->suppressEvents() as $event) { + $metadata->suppressEvents[$event] = true; + } + } + + $methods = $reflector->getMethods(); + + foreach ($methods as $method) { + $attributes = $method->getAttributes(Apply::class); + + foreach ($attributes as $attribute) { + $instance = $attribute->newInstance(); + $eventClass = $instance->eventClass(); + + if (array_key_exists($eventClass, $metadata->applyMethods)) { + throw new DuplicateApplyMethod( + $aggregate, + $eventClass, + $metadata->applyMethods[$eventClass], + $method->getName() + ); + } + + $metadata->applyMethods[$eventClass] = $method->getName(); + } + } + + $this->aggregateMetadata[$aggregate] = $metadata; + + return $metadata; + } +} diff --git a/src/Aggregate/DuplicateApplyMethod.php b/src/Metadata/AggregateRoot/DuplicateApplyMethod.php similarity index 64% rename from src/Aggregate/DuplicateApplyMethod.php rename to src/Metadata/AggregateRoot/DuplicateApplyMethod.php index 448dcfd9..4e75540f 100644 --- a/src/Aggregate/DuplicateApplyMethod.php +++ b/src/Metadata/AggregateRoot/DuplicateApplyMethod.php @@ -2,11 +2,14 @@ declare(strict_types=1); -namespace Patchlevel\EventSourcing\Aggregate; +namespace Patchlevel\EventSourcing\Metadata\AggregateRoot; + +use Patchlevel\EventSourcing\Aggregate\AggregateRoot; +use Patchlevel\EventSourcing\Metadata\MetadataException; use function sprintf; -final class DuplicateApplyMethod extends AggregateException +final class DuplicateApplyMethod extends MetadataException { /** * @param class-string $aggregate @@ -16,7 +19,7 @@ public function __construct(string $aggregate, string $event, string $fistMethod { parent::__construct( sprintf( - 'Two methods "%s" and "%s" on the aggregate "%s" want to apply the same event "%s".', + 'Two methods "%s" and "%s" on the aggregate "%s" want to apply the same event "%s". Only one method can apply an event.', $fistMethod, $secondMethod, $aggregate, diff --git a/src/Metadata/MetadataException.php b/src/Metadata/MetadataException.php new file mode 100644 index 00000000..9b6e3cab --- /dev/null +++ b/src/Metadata/MetadataException.php @@ -0,0 +1,11 @@ +, ProjectionMetadata> */ private array $projectionMetadata = []; - public function metadata(Projection $projection): ProjectionMetadata + /** + * @param class-string $projection + */ + public function metadata(string $projection): ProjectionMetadata { - if (array_key_exists($projection::class, $this->projectionMetadata)) { - return $this->projectionMetadata[$projection::class]; + if (array_key_exists($projection, $this->projectionMetadata)) { + return $this->projectionMetadata[$projection]; } - $reflector = new ReflectionClass($projection::class); + $reflector = new ReflectionClass($projection); $methods = $reflector->getMethods(); $metadata = new ProjectionMetadata(); @@ -40,7 +43,7 @@ public function metadata(Projection $projection): ProjectionMetadata if (array_key_exists($eventClass, $metadata->handleMethods)) { throw new DuplicateHandleMethod( - $projection::class, + $projection, $eventClass, $metadata->handleMethods[$eventClass]->methodName, $method->getName() @@ -55,11 +58,11 @@ public function metadata(Projection $projection): ProjectionMetadata if ($method->getAttributes(Create::class)) { if ($metadata->createMethod) { - throw new MetadataException(sprintf( - 'There can only be one create method in a projection. Defined in "%s" and "%s".', + throw new DuplicateCreateMethod( + $projection, $metadata->createMethod, $method->getName() - )); + ); } $metadata->createMethod = $method->getName(); @@ -70,16 +73,18 @@ public function metadata(Projection $projection): ProjectionMetadata } if ($metadata->dropMethod) { - throw new MetadataException(sprintf( - 'There can only be one drop method in a projection. Defined in "%s" and "%s".', + throw new DuplicateDropMethod( + $projection, $metadata->dropMethod, $method->getName() - )); + ); } $metadata->dropMethod = $method->getName(); } + $this->projectionMetadata[$projection] = $metadata; + return $metadata; } diff --git a/src/Metadata/Projection/DuplicateCreateMethod.php b/src/Metadata/Projection/DuplicateCreateMethod.php new file mode 100644 index 00000000..43f0b060 --- /dev/null +++ b/src/Metadata/Projection/DuplicateCreateMethod.php @@ -0,0 +1,28 @@ + $projection + */ + public function __construct(string $projection, string $fistMethod, string $secondMethod) + { + parent::__construct( + sprintf( + 'Two methods "%s" and "%s" on the projection "%s" have been marked as "create" methods. Only one method can be defined like this.', + $fistMethod, + $secondMethod, + $projection, + ) + ); + } +} diff --git a/src/Metadata/Projection/DuplicateDropMethod.php b/src/Metadata/Projection/DuplicateDropMethod.php new file mode 100644 index 00000000..157e0808 --- /dev/null +++ b/src/Metadata/Projection/DuplicateDropMethod.php @@ -0,0 +1,28 @@ + $projection + */ + public function __construct(string $projection, string $fistMethod, string $secondMethod) + { + parent::__construct( + sprintf( + 'Two methods "%s" and "%s" on the projection "%s" have been marked as "create" methods. Only one method can be defined like this.', + $fistMethod, + $secondMethod, + $projection, + ) + ); + } +} diff --git a/src/Projection/DuplicateHandleMethod.php b/src/Metadata/Projection/DuplicateHandleMethod.php similarity index 64% rename from src/Projection/DuplicateHandleMethod.php rename to src/Metadata/Projection/DuplicateHandleMethod.php index 339eb06a..bb494237 100644 --- a/src/Projection/DuplicateHandleMethod.php +++ b/src/Metadata/Projection/DuplicateHandleMethod.php @@ -2,11 +2,14 @@ declare(strict_types=1); -namespace Patchlevel\EventSourcing\Projection; +namespace Patchlevel\EventSourcing\Metadata\Projection; + +use Patchlevel\EventSourcing\Metadata\MetadataException; +use Patchlevel\EventSourcing\Projection\Projection; use function sprintf; -final class DuplicateHandleMethod extends ProjectionException +final class DuplicateHandleMethod extends MetadataException { /** * @param class-string $projection @@ -16,7 +19,7 @@ public function __construct(string $projection, string $event, string $fistMetho { parent::__construct( sprintf( - 'Two methods "%s" and "%s" on the projection "%s" want to handle the same event "%s".', + 'Two methods "%s" and "%s" on the projection "%s" want to handle the same event "%s". Only one method can handle an event.', $fistMethod, $secondMethod, $projection, diff --git a/src/Projection/ProjectionHandleMetadata.php b/src/Metadata/Projection/ProjectionHandleMetadata.php similarity index 79% rename from src/Projection/ProjectionHandleMetadata.php rename to src/Metadata/Projection/ProjectionHandleMetadata.php index 0b1a1cac..4935dd5a 100644 --- a/src/Projection/ProjectionHandleMetadata.php +++ b/src/Metadata/Projection/ProjectionHandleMetadata.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Patchlevel\EventSourcing\Projection; +namespace Patchlevel\EventSourcing\Metadata\Projection; /** * @internal diff --git a/src/Projection/ProjectionMetadata.php b/src/Metadata/Projection/ProjectionMetadata.php similarity index 82% rename from src/Projection/ProjectionMetadata.php rename to src/Metadata/Projection/ProjectionMetadata.php index 9a01621a..b64e34d5 100644 --- a/src/Projection/ProjectionMetadata.php +++ b/src/Metadata/Projection/ProjectionMetadata.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Patchlevel\EventSourcing\Projection; +namespace Patchlevel\EventSourcing\Metadata\Projection; /** * @internal diff --git a/src/Metadata/Projection/ProjectionMetadataFactory.php b/src/Metadata/Projection/ProjectionMetadataFactory.php new file mode 100644 index 00000000..41ede493 --- /dev/null +++ b/src/Metadata/Projection/ProjectionMetadataFactory.php @@ -0,0 +1,15 @@ + $projection + */ + public function metadata(string $projection): ProjectionMetadata; +} diff --git a/src/Pipeline/Target/ProjectionTarget.php b/src/Pipeline/Target/ProjectionTarget.php index 0e1605cd..78de6d3b 100644 --- a/src/Pipeline/Target/ProjectionTarget.php +++ b/src/Pipeline/Target/ProjectionTarget.php @@ -5,9 +5,9 @@ namespace Patchlevel\EventSourcing\Pipeline\Target; use Patchlevel\EventSourcing\EventBus\Message; -use Patchlevel\EventSourcing\Projection\AttributeProjectionMetadataFactory; +use Patchlevel\EventSourcing\Metadata\Projection\AttributeProjectionMetadataFactory; +use Patchlevel\EventSourcing\Metadata\Projection\ProjectionMetadataFactory; use Patchlevel\EventSourcing\Projection\Projection; -use Patchlevel\EventSourcing\Projection\ProjectionMetadataFactory; use function array_key_exists; @@ -26,7 +26,7 @@ public function __construct( public function save(Message $message): void { - $metadata = $this->metadataFactory->metadata($this->projection); + $metadata = $this->metadataFactory->metadata($this->projection::class); $event = $message->event(); if (!array_key_exists($event::class, $metadata->handleMethods)) { diff --git a/src/Projection/DefaultProjectionHandler.php b/src/Projection/DefaultProjectionHandler.php index cf4b5d91..9d8e84bc 100644 --- a/src/Projection/DefaultProjectionHandler.php +++ b/src/Projection/DefaultProjectionHandler.php @@ -5,6 +5,8 @@ namespace Patchlevel\EventSourcing\Projection; use Patchlevel\EventSourcing\EventBus\Message; +use Patchlevel\EventSourcing\Metadata\Projection\AttributeProjectionMetadataFactory; +use Patchlevel\EventSourcing\Metadata\Projection\ProjectionMetadataFactory; use function array_key_exists; @@ -29,7 +31,7 @@ public function handle(Message $message): void $event = $message->event(); foreach ($this->projections as $projection) { - $metadata = $this->metadataFactor->metadata($projection); + $metadata = $this->metadataFactor->metadata($projection::class); if (!array_key_exists($event::class, $metadata->handleMethods)) { continue; @@ -51,7 +53,7 @@ public function handle(Message $message): void public function create(): void { foreach ($this->projections as $projection) { - $metadata = $this->metadataFactor->metadata($projection); + $metadata = $this->metadataFactor->metadata($projection::class); $method = $metadata->createMethod; if (!$method) { @@ -65,7 +67,7 @@ public function create(): void public function drop(): void { foreach ($this->projections as $projection) { - $metadata = $this->metadataFactor->metadata($projection); + $metadata = $this->metadataFactor->metadata($projection::class); $method = $metadata->dropMethod; if (!$method) { diff --git a/src/Projection/MetadataException.php b/src/Projection/MetadataException.php deleted file mode 100644 index dc26082c..00000000 --- a/src/Projection/MetadataException.php +++ /dev/null @@ -1,9 +0,0 @@ -expectException(ApplyAttributeNotFound::class); + $this->expectException(ApplyMethodNotFound::class); $profileId = ProfileId::fromString('1'); $email = Email::fromString('hallo@patchlevel.de'); diff --git a/tests/Unit/Projection/AttributeProjectionMetadataFactoryTest.php b/tests/Unit/Metadata/Projection/AttributeProjectionMetadataFactoryTest.php similarity index 78% rename from tests/Unit/Projection/AttributeProjectionMetadataFactoryTest.php rename to tests/Unit/Metadata/Projection/AttributeProjectionMetadataFactoryTest.php index d4839112..260d873c 100644 --- a/tests/Unit/Projection/AttributeProjectionMetadataFactoryTest.php +++ b/tests/Unit/Metadata/Projection/AttributeProjectionMetadataFactoryTest.php @@ -2,15 +2,16 @@ declare(strict_types=1); -namespace Patchlevel\EventSourcing\Tests\Unit\Projection; +namespace Patchlevel\EventSourcing\Tests\Unit\Metadata\Projection; use Patchlevel\EventSourcing\Attribute\Create; use Patchlevel\EventSourcing\Attribute\Drop; use Patchlevel\EventSourcing\Attribute\Handle; -use Patchlevel\EventSourcing\Projection\AttributeProjectionMetadataFactory; -use Patchlevel\EventSourcing\Projection\MetadataException; +use Patchlevel\EventSourcing\Metadata\Projection\AttributeProjectionMetadataFactory; +use Patchlevel\EventSourcing\Metadata\Projection\DuplicateCreateMethod; +use Patchlevel\EventSourcing\Metadata\Projection\DuplicateDropMethod; +use Patchlevel\EventSourcing\Metadata\Projection\ProjectionHandleMetadata; use Patchlevel\EventSourcing\Projection\Projection; -use Patchlevel\EventSourcing\Projection\ProjectionHandleMetadata; use Patchlevel\EventSourcing\Tests\Unit\Fixture\ProfileCreated; use Patchlevel\EventSourcing\Tests\Unit\Fixture\ProfileVisited; use PHPUnit\Framework\TestCase; @@ -23,7 +24,7 @@ public function testEmptyProjection(): void }; $metadataFactory = new AttributeProjectionMetadataFactory(); - $metadata = $metadataFactory->metadata($projection); + $metadata = $metadataFactory->metadata($projection::class); self::assertSame([], $metadata->handleMethods); self::assertNull($metadata->createMethod); @@ -50,7 +51,7 @@ public function drop(): void }; $metadataFactory = new AttributeProjectionMetadataFactory(); - $metadata = $metadataFactory->metadata($projection); + $metadata = $metadataFactory->metadata($projection::class); self::assertEquals( [ProfileVisited::class => new ProjectionHandleMetadata('handle')], @@ -72,7 +73,7 @@ public function handle(): void }; $metadataFactory = new AttributeProjectionMetadataFactory(); - $metadata = $metadataFactory->metadata($projection); + $metadata = $metadataFactory->metadata($projection::class); self::assertEquals( [ @@ -85,7 +86,7 @@ public function handle(): void public function testDuplicateCreateAttributeException(): void { - $this->expectException(MetadataException::class); + $this->expectException(DuplicateCreateMethod::class); $projection = new class implements Projection { #[Create] @@ -100,12 +101,12 @@ public function create2(): void }; $metadataFactory = new AttributeProjectionMetadataFactory(); - $metadataFactory->metadata($projection); + $metadataFactory->metadata($projection::class); } public function testDuplicateDropAttributeException(): void { - $this->expectException(MetadataException::class); + $this->expectException(DuplicateDropMethod::class); $projection = new class implements Projection { #[Drop] @@ -120,6 +121,6 @@ public function drop2(): void }; $metadataFactory = new AttributeProjectionMetadataFactory(); - $metadataFactory->metadata($projection); + $metadataFactory->metadata($projection::class); } }