Skip to content

Commit

Permalink
Merge pull request #106 from patchlevel/refactor-aggregate-changed
Browse files Browse the repository at this point in the history
refactor aggregate changed for better psalm supprt
  • Loading branch information
DavidBadura authored Dec 9, 2021
2 parents 5f2a091 + 6b5294a commit fe16d70
Show file tree
Hide file tree
Showing 35 changed files with 245 additions and 90 deletions.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,16 @@ use App\Domain\Profile\Email;
use App\Domain\Profile\ProfileId;
use Patchlevel\EventSourcing\Aggregate\AggregateChanged;

/**
* @template-extends AggregateChanged<array{profileId: string, email: string}>
*/
final class ProfileCreated extends AggregateChanged
{
public static function raise(
ProfileId $id,
Email $email
): AggregateChanged {
return self::occur(
return new self(
$id->toString(),
[
'profileId' => $id->toString(),
Expand Down
26 changes: 5 additions & 21 deletions baseline.xml
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<files psalm-version="4.13.1@5cf660f63b548ccd4a56f62d916ee4d6028e01a3">
<file src="src/Pipeline/Middleware/FilterEventMiddleware.php">
<MixedPropertyTypeCoercion occurrences="1">
<code>$callable</code>
</MixedPropertyTypeCoercion>
</file>
<file src="tests/Integration/BasicImplementation/Aggregate/Profile.php">
<MoreSpecificImplementedParamType occurrences="1">
<code>$payload</code>
</MoreSpecificImplementedParamType>
</file>
<file src="tests/Integration/Pipeline/PipelineChangeStoreTest.php">
<ArgumentTypeCoercion occurrences="1"/>
</file>
<file src="tests/Unit/Aggregate/AggregateChangedTest.php">
<InvalidArgument occurrences="2"/>
</file>
Expand All @@ -30,21 +32,6 @@
<code>$text</code>
</PropertyNotSetInConstructor>
</file>
<file src="tests/Unit/Fixture/MessagePublished.php">
<MixedArgument occurrences="1">
<code>$this-&gt;payload['message']</code>
</MixedArgument>
</file>
<file src="tests/Unit/Fixture/ProfileCreated.php">
<MixedArgument occurrences="1">
<code>$this-&gt;payload['email']</code>
</MixedArgument>
</file>
<file src="tests/Unit/Fixture/ProfileVisited.php">
<MixedArgument occurrences="1">
<code>$this-&gt;payload['visitorId']</code>
</MixedArgument>
</file>
<file src="tests/Unit/Fixture/ProfileWithSnapshot.php">
<LessSpecificImplementedReturnType occurrences="1">
<code>SnapshotableAggregateRoot</code>
Expand All @@ -53,9 +40,6 @@
<code>$payload</code>
</MoreSpecificImplementedParamType>
</file>
<file src="tests/Unit/Pipeline/Middleware/ReplaceEventMiddlewareTest.php">
<ArgumentTypeCoercion occurrences="2"/>
</file>
<file src="tests/Unit/Pipeline/Middleware/UntilEventMiddlewareTest.php">
<InvalidArgument occurrences="1"/>
</file>
Expand Down
3 changes: 3 additions & 0 deletions phpcs.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@

<rule ref="PatchlevelCodingStandard">
<exclude name="SlevomatCodingStandard.Classes.SuperfluousExceptionNaming.SuperfluousSuffix" />
<exclude name="SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingTraversableTypeHintSpecification" />
<exclude name="SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingTraversableTypeHintSpecification" />
<exclude name="SlevomatCodingStandard.TypeHints.PropertyTypeHint.MissingTraversableTypeHintSpecification" />
<exclude name="Generic.Files.LineLength.TooLong"/>
</rule>
</ruleset>
85 changes: 85 additions & 0 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
@@ -1,12 +1,97 @@
parameters:
ignoreErrors:
-
message: "#^Method Patchlevel\\\\EventSourcing\\\\Aggregate\\\\AggregateChanged\\:\\:serialize\\(\\) return type with generic class Patchlevel\\\\EventSourcing\\\\Aggregate\\\\AggregateChanged does not specify its types\\: T$#"
count: 1
path: src/Aggregate/AggregateChanged.php

-
message: "#^Method Patchlevel\\\\EventSourcing\\\\Aggregate\\\\AggregateRoot\\:\\:apply\\(\\) has parameter \\$event with generic class Patchlevel\\\\EventSourcing\\\\Aggregate\\\\AggregateChanged but does not specify its types\\: T$#"
count: 1
path: src/Aggregate/AggregateRoot.php

-
message: "#^Method Patchlevel\\\\EventSourcing\\\\Aggregate\\\\AggregateRoot\\:\\:createFromEventStream\\(\\) has parameter \\$stream with generic class Patchlevel\\\\EventSourcing\\\\Aggregate\\\\AggregateChanged but does not specify its types\\: T$#"
count: 1
path: src/Aggregate/AggregateRoot.php

-
message: "#^Method Patchlevel\\\\EventSourcing\\\\Aggregate\\\\AggregateRoot\\:\\:releaseEvents\\(\\) return type with generic class Patchlevel\\\\EventSourcing\\\\Aggregate\\\\AggregateChanged does not specify its types\\: T$#"
count: 1
path: src/Aggregate/AggregateRoot.php

-
message: "#^Property Patchlevel\\\\EventSourcing\\\\Aggregate\\\\AggregateRoot\\:\\:\\$uncommittedEvents with generic class Patchlevel\\\\EventSourcing\\\\Aggregate\\\\AggregateChanged does not specify its types\\: T$#"
count: 1
path: src/Aggregate/AggregateRoot.php

-
message: "#^Method Patchlevel\\\\EventSourcing\\\\Aggregate\\\\ApplyMethodNotFound\\:\\:__construct\\(\\) has parameter \\$event with generic class Patchlevel\\\\EventSourcing\\\\Aggregate\\\\AggregateChanged but does not specify its types\\: T$#"
count: 1
path: src/Aggregate/ApplyMethodNotFound.php

-
message: "#^Method Patchlevel\\\\EventSourcing\\\\Console\\\\DoctrineHelper\\:\\:databaseName\\(\\) should return string but returns mixed\\.$#"
count: 2
path: src/Console/DoctrineHelper.php

-
message: "#^Method Patchlevel\\\\EventSourcing\\\\Console\\\\EventPrinter\\:\\:write\\(\\) has parameter \\$event with generic class Patchlevel\\\\EventSourcing\\\\Aggregate\\\\AggregateChanged but does not specify its types\\: T$#"
count: 1
path: src/Console/EventPrinter.php

-
message: "#^Method Patchlevel\\\\EventSourcing\\\\Pipeline\\\\Middleware\\\\ExcludeEventMiddleware\\:\\:__construct\\(\\) has parameter \\$classes with generic class Patchlevel\\\\EventSourcing\\\\Aggregate\\\\AggregateChanged but does not specify its types\\: T$#"
count: 1
path: src/Pipeline/Middleware/ExcludeEventMiddleware.php

-
message: "#^Property Patchlevel\\\\EventSourcing\\\\Pipeline\\\\Middleware\\\\ExcludeEventMiddleware\\:\\:\\$classes with generic class Patchlevel\\\\EventSourcing\\\\Aggregate\\\\AggregateChanged does not specify its types\\: T$#"
count: 1
path: src/Pipeline/Middleware/ExcludeEventMiddleware.php

-
message: "#^Method Patchlevel\\\\EventSourcing\\\\Pipeline\\\\Middleware\\\\FilterEventMiddleware\\:\\:__construct\\(\\) has parameter \\$callable with generic class Patchlevel\\\\EventSourcing\\\\Aggregate\\\\AggregateChanged but does not specify its types\\: T$#"
count: 1
path: src/Pipeline/Middleware/FilterEventMiddleware.php

-
message: "#^Property Patchlevel\\\\EventSourcing\\\\Pipeline\\\\Middleware\\\\FilterEventMiddleware\\:\\:\\$callable with generic class Patchlevel\\\\EventSourcing\\\\Aggregate\\\\AggregateChanged does not specify its types\\: T$#"
count: 1
path: src/Pipeline/Middleware/FilterEventMiddleware.php

-
message: "#^Method Patchlevel\\\\EventSourcing\\\\Pipeline\\\\Middleware\\\\IncludeEventMiddleware\\:\\:__construct\\(\\) has parameter \\$classes with generic class Patchlevel\\\\EventSourcing\\\\Aggregate\\\\AggregateChanged but does not specify its types\\: T$#"
count: 1
path: src/Pipeline/Middleware/IncludeEventMiddleware.php

-
message: "#^Property Patchlevel\\\\EventSourcing\\\\Pipeline\\\\Middleware\\\\IncludeEventMiddleware\\:\\:\\$classes with generic class Patchlevel\\\\EventSourcing\\\\Aggregate\\\\AggregateChanged does not specify its types\\: T$#"
count: 1
path: src/Pipeline/Middleware/IncludeEventMiddleware.php

-
message: "#^Unable to resolve the template type E in call to method static method Patchlevel\\\\EventSourcing\\\\Aggregate\\\\AggregateChanged\\<array\\<string, mixed\\>\\>\\:\\:deserialize\\(\\)$#"
count: 1
path: src/Store/MultiTableStore.php

-
message: "#^Unable to resolve the template type T in call to method static method Patchlevel\\\\EventSourcing\\\\Store\\\\DoctrineStore\\:\\:normalizeResult\\(\\)$#"
count: 1
path: src/Store/MultiTableStore.php

-
message: "#^Method Patchlevel\\\\EventSourcing\\\\WatchServer\\\\WatchServer\\:\\:listen\\(\\) has parameter \\$callback with generic class Patchlevel\\\\EventSourcing\\\\Aggregate\\\\AggregateChanged but does not specify its types\\: T$#"
count: 1
path: src/WatchServer/WatchServer.php

-
message: "#^While loop condition is always true\\.$#"
count: 1
path: src/WatchServer/WatchServer.php

-
message: "#^Method Patchlevel\\\\EventSourcing\\\\WatchServer\\\\WatchServerClient\\:\\:send\\(\\) has parameter \\$event with generic class Patchlevel\\\\EventSourcing\\\\Aggregate\\\\AggregateChanged but does not specify its types\\: T$#"
count: 1
path: src/WatchServer/WatchServerClient.php

32 changes: 17 additions & 15 deletions src/Aggregate/AggregateChanged.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,26 @@

use const JSON_THROW_ON_ERROR;

/**
* @template-covariant T of array<string, mixed>
*/
abstract class AggregateChanged
{
/** @readonly */
protected string $aggregateId;

/** @var array<string, mixed> */
/**
* @readonly
* @var T
*/
protected array $payload;
private ?int $playhead;
private ?DateTimeImmutable $recordedOn;

/**
* @param array<string, mixed> $payload
* @param T $payload
*/
final private function __construct(string $aggregateId, array $payload = [])
final public function __construct(string $aggregateId, array $payload = [])
{
$this->aggregateId = $aggregateId;
$this->payload = $payload;
Expand All @@ -47,23 +54,13 @@ public function recordedOn(): ?DateTimeImmutable
}

/**
* @return array<string, mixed>
* @return T
*/
public function payload(): array
{
return $this->payload;
}

/**
* @param array<string, mixed> $payload
*
* @return static
*/
protected static function occur(string $aggregateId, array $payload = []): self
{
return new static($aggregateId, $payload);
}

/**
* @internal
*
Expand All @@ -75,6 +72,7 @@ public function recordNow(int $playhead): self
throw new AggregateChangeRecordedAlready();
}

/** @psalm-suppress UnsafeGenericInstantiation */
$event = new static($this->aggregateId, $this->payload);
$event->playhead = $playhead;
$event->recordedOn = $this->createRecordDate();
Expand All @@ -83,7 +81,11 @@ public function recordNow(int $playhead): self
}

/**
* @param array{aggregateId: string, playhead: int, event: class-string<self>, payload: string, recordedOn: DateTimeImmutable} $data
* @param array{aggregateId: string, playhead: int, event: class-string<E>, payload: string, recordedOn: DateTimeImmutable} $data
*
* @return E
*
* @template E of self
*/
public static function deserialize(array $data): self
{
Expand Down
3 changes: 3 additions & 0 deletions src/Aggregate/AggregateRoot.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ abstract public function aggregateRootId(): string;

abstract protected function apply(AggregateChanged $event): void;

/**
* @param AggregateChanged<array<string, mixed>> $event
*/
protected function record(AggregateChanged $event): void
{
$this->playhead++;
Expand Down
2 changes: 1 addition & 1 deletion src/Aggregate/SnapshotableAggregateRoot.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ abstract protected function serialize(): array;
abstract protected static function deserialize(array $payload): self;

/**
* @param array<AggregateChanged> $stream
* @param array<AggregateChanged<array<string, mixed>>> $stream
*
* @return static
*/
Expand Down
5 changes: 4 additions & 1 deletion src/EventBus/DefaultEventBus.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

final class DefaultEventBus implements EventBus
{
/** @var list<AggregateChanged> */
/** @var list<AggregateChanged<array<string, mixed>>> */
private array $queue;
/** @var list<Listener> */
private array $listeners;
Expand All @@ -27,6 +27,9 @@ public function __construct(array $listeners = [])
$this->processing = false;
}

/**
* @param AggregateChanged<array<string, mixed>> $event
*/
public function dispatch(AggregateChanged $event): void
{
$this->queue[] = $event;
Expand Down
3 changes: 3 additions & 0 deletions src/EventBus/EventBus.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,8 @@

interface EventBus
{
/**
* @param AggregateChanged<array<string, mixed>> $event
*/
public function dispatch(AggregateChanged $event): void;
}
3 changes: 3 additions & 0 deletions src/EventBus/Listener.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,8 @@

interface Listener
{
/**
* @param AggregateChanged<array<string, mixed>> $event
*/
public function __invoke(AggregateChanged $event): void;
}
3 changes: 3 additions & 0 deletions src/EventBus/SymfonyEventBus.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ public function __construct(MessageBusInterface $bus)
$this->bus = $bus;
}

/**
* @param AggregateChanged<array<string, mixed>> $event
*/
public function dispatch(AggregateChanged $event): void
{
$envelope = (new Envelope($event))
Expand Down
8 changes: 7 additions & 1 deletion src/Pipeline/EventBucket.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,13 @@ class EventBucket
/** @var class-string<AggregateRoot> */
private string $aggregateClass;
private int $index;

/** @var AggregateChanged<array<string, mixed>> */
private AggregateChanged $event;

/**
* @param class-string<AggregateRoot> $aggregateClass
* @param class-string<AggregateRoot> $aggregateClass
* @param AggregateChanged<array<string, mixed>> $event
*/
public function __construct(string $aggregateClass, int $index, AggregateChanged $event)
{
Expand All @@ -37,6 +40,9 @@ public function index(): int
return $this->index;
}

/**
* @return AggregateChanged<array<string, mixed>>
*/
public function event(): AggregateChanged
{
return $this->event;
Expand Down
4 changes: 2 additions & 2 deletions src/Pipeline/Middleware/ClassRenameMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@

class ClassRenameMiddleware implements Middleware
{
/** @var array<class-string<AggregateChanged>, class-string<AggregateChanged>> */
/** @var array<class-string<AggregateChanged<array<string, mixed>>>, class-string<AggregateChanged<array<string, mixed>>>> */
private array $classes;

/**
* @param array<class-string<AggregateChanged>, class-string<AggregateChanged>> $classes
* @param array<class-string<AggregateChanged<array<string, mixed>>>, class-string<AggregateChanged<array<string, mixed>>>> $classes
*/
public function __construct(array $classes)
{
Expand Down
11 changes: 7 additions & 4 deletions src/Pipeline/Middleware/ReplaceEventMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,23 @@
use ReflectionClass;
use ReflectionProperty;

/**
* @template T of AggregateChanged
*/
class ReplaceEventMiddleware implements Middleware
{
/** @var class-string<AggregateChanged> */
/** @var class-string<T> */
private string $class;

/** @var callable(AggregateChanged $event):AggregateChanged */
/** @var callable(T $event):AggregateChanged<array<string, mixed>> */
private $callable;

private ReflectionProperty $recoredOnProperty;
private ReflectionProperty $playheadProperty;

/**
* @param class-string<AggregateChanged> $class
* @param callable(AggregateChanged $event):AggregateChanged $callable
* @param class-string<T> $class
* @param callable(T $event):AggregateChanged<array<string, mixed>> $callable
*/
public function __construct(string $class, callable $callable)
{
Expand Down
1 change: 1 addition & 0 deletions src/Projection/DefaultProjectionRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public function handle(AggregateChanged $event): void
$handlers = $projection->handledEvents();

foreach ($handlers as $class => $method) {
/** @psalm-suppress DocblockTypeContradiction */
if (!$event instanceof $class) {
continue;
}
Expand Down
Loading

0 comments on commit fe16d70

Please sign in to comment.