Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

metadata namespace #196

Merged
merged 4 commits into from
Mar 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 8 additions & 50 deletions src/Aggregate/AggregateRoot.php
Original file line number Diff line number Diff line change
Expand Up @@ -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<class-string<self>, AggregateRootMetadata> */
private static array $metadata = [];
private static ?AggregateRootMetadataFactory $metadataFactory = null;

/** @var list<Message> */
private array $uncommittedMessages = [];
Expand All @@ -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;
Expand Down Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

use function sprintf;

final class ApplyAttributeNotFound extends AggregateException
final class ApplyMethodNotFound extends AggregateException
{
public function __construct(AggregateRoot $aggregate, object $event)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

declare(strict_types=1);

namespace Patchlevel\EventSourcing\Aggregate;
namespace Patchlevel\EventSourcing\Metadata\AggregateRoot;

/**
* @internal
Expand Down
15 changes: 15 additions & 0 deletions src/Metadata/AggregateRoot/AggregateRootMetadataFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace Patchlevel\EventSourcing\Metadata\AggregateRoot;

use Patchlevel\EventSourcing\Aggregate\AggregateRoot;

interface AggregateRootMetadataFactory
{
/**
* @param class-string<AggregateRoot> $aggregate
*/
public function metadata(string $aggregate): AggregateRootMetadata;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php

declare(strict_types=1);

namespace Patchlevel\EventSourcing\Metadata\AggregateRoot;

use Patchlevel\EventSourcing\Aggregate\AggregateRoot;
use Patchlevel\EventSourcing\Attribute\Apply;
use Patchlevel\EventSourcing\Attribute\SuppressMissingApply;
use ReflectionClass;

use function array_key_exists;

final class AttributeAggregateRootMetadataFactory implements AggregateRootMetadataFactory
{
/** @var array<class-string<AggregateRoot>, AggregateRootMetadata> */
private array $aggregateMetadata = [];

/**
* @param class-string<AggregateRoot> $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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<AggregateRoot> $aggregate
Expand All @@ -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,
Expand Down
11 changes: 11 additions & 0 deletions src/Metadata/MetadataException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

namespace Patchlevel\EventSourcing\Metadata;

use RuntimeException;

abstract class MetadataException extends RuntimeException
{
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,34 @@

declare(strict_types=1);

namespace Patchlevel\EventSourcing\Projection;
namespace Patchlevel\EventSourcing\Metadata\Projection;

use Patchlevel\EventSourcing\Attribute\Create;
use Patchlevel\EventSourcing\Attribute\Drop;
use Patchlevel\EventSourcing\Attribute\Handle;
use Patchlevel\EventSourcing\EventBus\Message;
use Patchlevel\EventSourcing\Projection\Projection;
use ReflectionClass;
use ReflectionMethod;
use ReflectionNamedType;

use function array_key_exists;
use function sprintf;

final class AttributeProjectionMetadataFactory implements ProjectionMetadataFactory
{
/** @var array<class-string<Projection>, ProjectionMetadata> */
private array $projectionMetadata = [];

public function metadata(Projection $projection): ProjectionMetadata
/**
* @param class-string<Projection> $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();
Expand All @@ -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()
Expand All @@ -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();
Expand All @@ -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;
}

Expand Down
28 changes: 28 additions & 0 deletions src/Metadata/Projection/DuplicateCreateMethod.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

namespace Patchlevel\EventSourcing\Metadata\Projection;

use Patchlevel\EventSourcing\Metadata\MetadataException;
use Patchlevel\EventSourcing\Projection\Projection;

use function sprintf;

final class DuplicateCreateMethod extends MetadataException
{
/**
* @param class-string<Projection> $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,
)
);
}
}
28 changes: 28 additions & 0 deletions src/Metadata/Projection/DuplicateDropMethod.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

namespace Patchlevel\EventSourcing\Metadata\Projection;

use Patchlevel\EventSourcing\Metadata\MetadataException;
use Patchlevel\EventSourcing\Projection\Projection;

use function sprintf;

final class DuplicateDropMethod extends MetadataException
{
/**
* @param class-string<Projection> $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,
)
);
}
}
Loading