Skip to content

Commit

Permalink
Merge pull request #578 from patchlevel/improve-store-criteria
Browse files Browse the repository at this point in the history
improve store criteria
  • Loading branch information
DavidBadura authored Apr 18, 2024
2 parents 1328977 + c2ae809 commit f26e664
Show file tree
Hide file tree
Showing 25 changed files with 508 additions and 110 deletions.
8 changes: 8 additions & 0 deletions baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,14 @@
<code><![CDATA[$index]]></code>
</InvalidPropertyAssignmentValue>
</file>
<file src="src/Store/Criteria/Criteria.php">
<InvalidReturnStatement>
<code><![CDATA[$this->criteria[$criteriaClass]]]></code>
</InvalidReturnStatement>
<InvalidReturnType>
<code><![CDATA[T]]></code>
</InvalidReturnType>
</file>
<file src="src/Subscription/Store/DoctrineSubscriptionStore.php">
<MixedArgument>
<code><![CDATA[$context]]></code>
Expand Down
35 changes: 24 additions & 11 deletions docs/pages/store.md
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ $stream = $store->load();
The load method also has a few parameters to filter, limit and sort the events.

```php
use Patchlevel\EventSourcing\Store\Criteria;
use Patchlevel\EventSourcing\Store\Criteria\Criteria;
use Patchlevel\EventSourcing\Store\Store;

/** @var Store $store */
Expand All @@ -232,20 +232,33 @@ $stream = $store->load(
The `Criteria` object is used to filter the events.

```php
use Patchlevel\EventSourcing\Store\Criteria;
use Patchlevel\EventSourcing\Store\Criteria\AggregateIdCriterion;
use Patchlevel\EventSourcing\Store\Criteria\AggregateNameCriterion;
use Patchlevel\EventSourcing\Store\Criteria\ArchivedCriterion;
use Patchlevel\EventSourcing\Store\Criteria\Criteria;
use Patchlevel\EventSourcing\Store\Criteria\FromIndexCriterion;

$criteria = new Criteria(
aggregateName: 'profile',
aggregateId: 'e3e3e3e3-3e3e-3e3e-3e3e-3e3e3e3e3e3e',
fromIndex: 100,
fromPlayhead: 2,
archived: true,
new AggregateNameCriterion('profile'),
new AggregateIdCriterion('e3e3e3e3-3e3e-3e3e-3e3e-3e3e3e3e3e3e'),
new FromPlayheadCriterion(2),
new FromIndexCriterion(100),
new ArchivedCriterion(true),
);
```
!!! note
Or you can the criteria builder to create the criteria.

The individual criteria must all apply, but not all of them have to be set.

```php
use Patchlevel\EventSourcing\Store\Criteria\CriteriaBuilder;

$criteria = (new CriteriaBuilder())
->aggregateName('profile')
->aggregateId('e3e3e3e3-3e3e-3e3e-3e3e-3e3e3e3e3e3e')
->fromPlayhead(2)
->fromIndex(100)
->archived(true)
->build();
```
#### Stream

The load method returns a `Stream` object and is a generator.
Expand Down Expand Up @@ -287,7 +300,7 @@ $count = $store->count();
The count method also has the possibility to filter the events.

```php
use Patchlevel\EventSourcing\Store\Criteria;
use Patchlevel\EventSourcing\Store\Criteria\Criteria;
use Patchlevel\EventSourcing\Store\Store;

/** @var Store $store */
Expand Down
5 changes: 5 additions & 0 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ parameters:
count: 1
path: src/Store/ArrayStream.php

-
message: "#^Method Patchlevel\\\\EventSourcing\\\\Store\\\\Criteria\\\\Criteria\\:\\:get\\(\\) should return T of object but returns object\\.$#"
count: 1
path: src/Store/Criteria/Criteria.php

-
message: "#^Ternary operator condition is always true\\.$#"
count: 1
Expand Down
9 changes: 7 additions & 2 deletions src/Console/Command/ShowAggregateCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
use Patchlevel\EventSourcing\Message\Serializer\HeadersSerializer;
use Patchlevel\EventSourcing\Metadata\AggregateRoot\AggregateRootRegistry;
use Patchlevel\EventSourcing\Serializer\EventSerializer;
use Patchlevel\EventSourcing\Store\Criteria;
use Patchlevel\EventSourcing\Store\Criteria\AggregateIdCriterion;
use Patchlevel\EventSourcing\Store\Criteria\AggregateNameCriterion;
use Patchlevel\EventSourcing\Store\Criteria\Criteria;
use Patchlevel\EventSourcing\Store\Store;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
Expand Down Expand Up @@ -72,7 +74,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int
}

$stream = $this->store->load(
new Criteria($this->aggregateRootRegistry->aggregateClass($aggregate), $id),
new Criteria(
new AggregateNameCriterion($aggregate),
new AggregateIdCriterion($id),
),
);

$hasMessage = false;
Expand Down
18 changes: 11 additions & 7 deletions src/Console/Command/WatchCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
use Patchlevel\EventSourcing\Console\OutputStyle;
use Patchlevel\EventSourcing\Message\Serializer\HeadersSerializer;
use Patchlevel\EventSourcing\Serializer\EventSerializer;
use Patchlevel\EventSourcing\Store\Criteria;
use Patchlevel\EventSourcing\Store\Criteria\CriteriaBuilder;
use Patchlevel\EventSourcing\Store\Criteria\FromIndexCriterion;
use Patchlevel\EventSourcing\Store\Store;
use Patchlevel\EventSourcing\Store\SubscriptionStore;
use Patchlevel\Worker\DefaultWorker;
Expand Down Expand Up @@ -70,18 +71,21 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$this->store->setupSubscription();
}

$criteria = (new CriteriaBuilder())
->aggregateName($aggregate)
->aggregateId($aggregateId)
->build();

$worker = DefaultWorker::create(
function () use ($console, &$index, $aggregate, $aggregateId, $sleep): void {
function () use ($console, &$index, $criteria, $sleep): void {
$stream = $this->store->load(
new Criteria(
$aggregate,
$aggregateId,
$index,
),
$criteria->add(new FromIndexCriterion($index)),
);

foreach ($stream as $message) {
$console->message($this->eventSerializer, $this->headersSerializer, $message);

/** @var int $index */
$index = $stream->index();
}

Expand Down
2 changes: 1 addition & 1 deletion src/Repository/DefaultRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
use Patchlevel\EventSourcing\Snapshot\SnapshotStore;
use Patchlevel\EventSourcing\Snapshot\SnapshotVersionInvalid;
use Patchlevel\EventSourcing\Store\ArchivableStore;
use Patchlevel\EventSourcing\Store\CriteriaBuilder;
use Patchlevel\EventSourcing\Store\Criteria\CriteriaBuilder;
use Patchlevel\EventSourcing\Store\Store;
use Patchlevel\EventSourcing\Store\Stream;
use Patchlevel\EventSourcing\Store\StreamStartHeader;
Expand Down
17 changes: 0 additions & 17 deletions src/Store/Criteria.php

This file was deleted.

13 changes: 13 additions & 0 deletions src/Store/Criteria/AggregateIdCriterion.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace Patchlevel\EventSourcing\Store\Criteria;

final class AggregateIdCriterion
{
public function __construct(
public readonly string $aggregateId,
) {
}
}
13 changes: 13 additions & 0 deletions src/Store/Criteria/AggregateNameCriterion.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace Patchlevel\EventSourcing\Store\Criteria;

final class AggregateNameCriterion
{
public function __construct(
public readonly string $aggregateName,
) {
}
}
13 changes: 13 additions & 0 deletions src/Store/Criteria/ArchivedCriterion.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace Patchlevel\EventSourcing\Store\Criteria;

final class ArchivedCriterion
{
public function __construct(
public readonly bool $archived,
) {
}
}
68 changes: 68 additions & 0 deletions src/Store/Criteria/Criteria.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php

declare(strict_types=1);

namespace Patchlevel\EventSourcing\Store\Criteria;

use function array_key_exists;
use function array_values;

final class Criteria
{
/** @var array<class-string, object> */
private readonly array $criteria;

public function __construct(object ...$criteria)
{
$result = [];

foreach ($criteria as $criterion) {
$result[$criterion::class] = $criterion;
}

$this->criteria = $result;
}

/**
* @param class-string<T> $criteriaClass
*
* @return T
*
* @template T of object
*/
public function get(string $criteriaClass): object
{
if (!array_key_exists($criteriaClass, $this->criteria)) {
throw new CriterionNotFound($criteriaClass);
}

return $this->criteria[$criteriaClass];
}

public function has(string $criteriaClass): bool
{
return array_key_exists($criteriaClass, $this->criteria);
}

/** @return list<object> */
public function all(): array
{
return array_values($this->criteria);
}

public function add(object $criteria): self
{
return new self(
...$this->all(),
...[$criteria],
);
}

public function remove(string $criteriaClass): self
{
$criteria = $this->criteria;
unset($criteria[$criteriaClass]);

return new self(...$criteria);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

declare(strict_types=1);

namespace Patchlevel\EventSourcing\Store;
namespace Patchlevel\EventSourcing\Store\Criteria;

final class CriteriaBuilder
{
Expand Down Expand Up @@ -49,12 +49,28 @@ public function archived(bool|null $archived): self

public function build(): Criteria
{
return new Criteria(
$this->aggregateName,
$this->aggregateId,
$this->fromIndex,
$this->fromPlayhead,
$this->archived,
);
$criteria = [];

if ($this->aggregateName !== null) {
$criteria[] = new AggregateNameCriterion($this->aggregateName);
}

if ($this->aggregateId !== null) {
$criteria[] = new AggregateIdCriterion($this->aggregateId);
}

if ($this->fromPlayhead !== null) {
$criteria[] = new FromPlayheadCriterion($this->fromPlayhead);
}

if ($this->fromIndex !== null) {
$criteria[] = new FromIndexCriterion($this->fromIndex);
}

if ($this->archived !== null) {
$criteria[] = new ArchivedCriterion($this->archived);
}

return new Criteria(...$criteria);
}
}
18 changes: 18 additions & 0 deletions src/Store/Criteria/CriterionNotFound.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace Patchlevel\EventSourcing\Store\Criteria;

use RuntimeException;

use function sprintf;

final class CriterionNotFound extends RuntimeException
{
/** @param class-string $criterionClass */
public function __construct(string $criterionClass)
{
parent::__construct(sprintf('criterion %s not found', $criterionClass));
}
}
13 changes: 13 additions & 0 deletions src/Store/Criteria/FromIndexCriterion.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace Patchlevel\EventSourcing\Store\Criteria;

final class FromIndexCriterion
{
public function __construct(
public readonly int $fromIndex,
) {
}
}
13 changes: 13 additions & 0 deletions src/Store/Criteria/FromPlayheadCriterion.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace Patchlevel\EventSourcing\Store\Criteria;

final class FromPlayheadCriterion
{
public function __construct(
public readonly int $fromPlayhead,
) {
}
}
Loading

0 comments on commit f26e664

Please sign in to comment.