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

refactor aggregate changed for better psalm supprt #106

Merged
merged 9 commits into from
Dec 9, 2021
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
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