Skip to content
Closed
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
2 changes: 1 addition & 1 deletion infection.json.dist
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"mutators": {
"@default": true
},
"minMsi": 72,
"minMsi": 87,
"minCoveredMsi": 87,
"testFrameworkOptions": "--testsuite=unit"
}
14 changes: 7 additions & 7 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -156,18 +156,18 @@ parameters:
count: 1
path: src/Subscription/Store/DoctrineSubscriptionStore.php

-
message: '#^Trait Patchlevel\\EventSourcing\\Subscription\\Subscriber\\SubscriberUtil is used zero times and is not analysed\.$#'
identifier: trait.unused
count: 1
path: src/Subscription/Subscriber/SubscriberUtil.php

-
message: '#^Cannot cast mixed to string\.$#'
identifier: cast.string
count: 3
path: src/Subscription/ThrowableToErrorContextTransformer.php

-
message: '#^Property Patchlevel\\EventSourcing\\Tests\\Benchmark\\BasicImplementation\\ProfileWithCommands\:\:\$id is never read, only written\.$#'
identifier: property.onlyWritten
count: 1
path: tests/Benchmark/BasicImplementation/ProfileWithCommands.php

-
message: '#^Cannot access offset ''name'' on array\<string, mixed\>\|false\.$#'
identifier: offsetAccess.nonOffsetAccessible
Expand Down Expand Up @@ -463,7 +463,7 @@ parameters:
path: tests/Unit/Message/Translator/ReplaceEventTranslatorTest.php

-
message: '#^Method class@anonymous/tests/Unit/Metadata/Subscriber/AttributeSubscriberMetadataFactoryTest\.php\:212\:\:profileVisited\(\) has parameter \$message with no type specified\.$#'
message: '#^Method class@anonymous/tests/Unit/Metadata/Subscriber/AttributeSubscriberMetadataFactoryTest\.php\:290\:\:profileVisited\(\) has parameter \$message with no type specified\.$#'
identifier: missingType.parameter
count: 1
path: tests/Unit/Metadata/Subscriber/AttributeSubscriberMetadataFactoryTest.php
Expand Down
2 changes: 1 addition & 1 deletion src/Attribute/Processor.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
final class Processor extends Subscriber
{
public function __construct(
string $id,
string|null $id = null,
string $group = 'processor',
RunMode $runMode = RunMode::FromNow,
) {
Expand Down
2 changes: 1 addition & 1 deletion src/Attribute/Projector.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
final class Projector extends Subscriber
{
public function __construct(
string $id,
string|null $id = null,
string $group = 'projector',
RunMode $runMode = RunMode::FromBeginning,
) {
Expand Down
2 changes: 1 addition & 1 deletion src/Attribute/Subscriber.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
class Subscriber
{
public function __construct(
public readonly string $id,
public readonly string|null $id,
public readonly RunMode $runMode,
public readonly string $group = 'default',
) {
Expand Down
12 changes: 12 additions & 0 deletions src/Attribute/SubscriptionId.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace Patchlevel\EventSourcing\Attribute;

use Attribute;

#[Attribute(Attribute::TARGET_CLASS_CONSTANT)]
final class SubscriptionId
{
}
31 changes: 30 additions & 1 deletion src/Metadata/Subscriber/AttributeSubscriberMetadataFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Patchlevel\EventSourcing\Attribute\Setup;
use Patchlevel\EventSourcing\Attribute\Subscribe;
use Patchlevel\EventSourcing\Attribute\Subscriber;
use Patchlevel\EventSourcing\Attribute\SubscriptionId;
use Patchlevel\EventSourcing\Attribute\Teardown;
use ReflectionAttribute;
use ReflectionClass;
Expand All @@ -17,6 +18,7 @@

use function array_key_exists;
use function count;
use function is_string;

final class AttributeSubscriberMetadataFactory implements SubscriberMetadataFactory
{
Expand Down Expand Up @@ -109,8 +111,35 @@
throw DuplicateSubscribeMethod::mixedWithAll($subscriber);
}

$subscriptionId = $subscriberInfo->id;

foreach ($reflector->getReflectionConstants() as $constant) {
if (!$constant->getAttributes(SubscriptionId::class)) {
continue;
}

$subscriptionIdValue = $constant->getValue();

if (!is_string($subscriptionIdValue)) {
throw new SubscriptionIdWrongType($subscriber, $subscriptionIdValue);
}

if ($subscriptionId !== null) {
throw new SubscriberHasMultipleSubscriptionIds(
$subscriber,
[$subscriptionId, $subscriptionIdValue],

Check warning on line 130 in src/Metadata/Subscriber/AttributeSubscriberMetadataFactory.php

View workflow job for this annotation

GitHub Actions / Mutation tests on diff (locked, 8.5, ubuntu-latest)

Escaped Mutant for Mutator "ArrayItemRemoval": @@ @@ if ($subscriptionId !== null) { throw new SubscriberHasMultipleSubscriptionIds( $subscriber, - [$subscriptionId, $subscriptionIdValue], + [$subscriptionIdValue], ); }
);
}

$subscriptionId = $subscriptionIdValue;
}

if ($subscriptionId === null) {
throw new SubscriptionIdMissing($subscriber);
}

$metadata = new SubscriberMetadata(
$subscriberInfo->id,
$subscriptionId,
$subscriberInfo->group,
$subscriberInfo->runMode,
$subscribeMethods,
Expand Down
28 changes: 28 additions & 0 deletions src/Metadata/Subscriber/SubscriberHasMultipleSubscriptionIds.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

namespace Patchlevel\EventSourcing\Metadata\Subscriber;

use Patchlevel\EventSourcing\Metadata\MetadataException;

use function implode;
use function sprintf;

final class SubscriberHasMultipleSubscriptionIds extends MetadataException
{
/**
* @param class-string $subscriberClass
* @param list<string> $ids
*/
public function __construct(string $subscriberClass, array $ids)
{
parent::__construct(
sprintf(
'There are multiple subscription ids (%s) defined for the subscriber "%s".',
$subscriberClass,
implode(', ', $ids),
),
);
}
}
23 changes: 23 additions & 0 deletions src/Metadata/Subscriber/SubscriptionIdMissing.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

namespace Patchlevel\EventSourcing\Metadata\Subscriber;

use Patchlevel\EventSourcing\Metadata\MetadataException;

use function sprintf;

final class SubscriptionIdMissing extends MetadataException
{
/** @param class-string $subscriberClass */
public function __construct(string $subscriberClass)
{
parent::__construct(
sprintf(
'There is no subscription id defined for the subscriber "%s". Define it via #[Subscriber] or #[SubscriptionId] attribute.',
$subscriberClass,
),
);
}
}
25 changes: 25 additions & 0 deletions src/Metadata/Subscriber/SubscriptionIdWrongType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

declare(strict_types=1);

namespace Patchlevel\EventSourcing\Metadata\Subscriber;

use Patchlevel\EventSourcing\Metadata\MetadataException;

use function get_debug_type;
use function sprintf;

final class SubscriptionIdWrongType extends MetadataException
{
/** @param class-string $subscriberClass */
public function __construct(string $subscriberClass, mixed $subscriptionId)
{
parent::__construct(
sprintf(
'The subscription id defined for the subscriber "%s" has a wrong type. The Id needs to be a string, "%s" given.',
$subscriberClass,
get_debug_type($subscriptionId)
),
);
}
}
1 change: 1 addition & 0 deletions src/Subscription/Subscriber/SubscriberHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Patchlevel\EventSourcing\Metadata\Subscriber\SubscriberMetadata;
use Patchlevel\EventSourcing\Metadata\Subscriber\SubscriberMetadataFactory;

/** @deprecated since 3.15.0 will be removed with 4.0.0 */
final class SubscriberHelper
{
public function __construct(
Expand Down
1 change: 1 addition & 0 deletions src/Subscription/Subscriber/SubscriberUtil.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Patchlevel\EventSourcing\Metadata\Subscriber\AttributeSubscriberMetadataFactory;
use Patchlevel\EventSourcing\Metadata\Subscriber\SubscriberMetadataFactory;

/** @deprecated since 3.15.0 will be removed with 4.0.0, use #[SubscriberId] instead */
trait SubscriberUtil
{
private static SubscriberMetadataFactory|null $metadataFactory = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,19 @@
use Patchlevel\EventSourcing\Attribute\Projector;
use Patchlevel\EventSourcing\Attribute\Setup;
use Patchlevel\EventSourcing\Attribute\Subscribe;
use Patchlevel\EventSourcing\Attribute\SubscriptionId;
use Patchlevel\EventSourcing\Attribute\Teardown;
use Patchlevel\EventSourcing\Subscription\Subscriber\BatchableSubscriber;
use Patchlevel\EventSourcing\Subscription\Subscriber\SubscriberUtil;
use Patchlevel\EventSourcing\Tests\Benchmark\BasicImplementation\Events\NameChanged;
use Patchlevel\EventSourcing\Tests\Benchmark\BasicImplementation\Events\ProfileCreated;

#[Projector('profile')]
use function sprintf;

#[Projector]
final class BatchProfileProjector implements BatchableSubscriber
{
use SubscriberUtil;
#[SubscriptionId]
private const TABLE_NAME = 'projection_profile';

/** @var array<string, string> */
private array $nameChanged = [];
Expand All @@ -30,20 +33,23 @@ public function __construct(
#[Setup]
public function create(): void
{
$this->connection->executeStatement("CREATE TABLE IF NOT EXISTS {$this->table()} (id VARCHAR PRIMARY KEY, name VARCHAR);");
$this->connection->executeStatement(sprintf(
'CREATE TABLE IF NOT EXISTS %s (id VARCHAR PRIMARY KEY, name VARCHAR);',
self::TABLE_NAME,
));
}

#[Teardown]
public function drop(): void
{
$this->connection->executeStatement("DROP TABLE IF EXISTS {$this->table()};");
$this->connection->executeStatement(sprintf('DROP TABLE IF EXISTS %s;', self::TABLE_NAME));
}

#[Subscribe(ProfileCreated::class)]
public function onProfileCreated(ProfileCreated $profileCreated): void
{
$this->connection->insert(
$this->table(),
self::TABLE_NAME,
[
'id' => $profileCreated->profileId->toString(),
'name' => $profileCreated->name,
Expand All @@ -57,11 +63,6 @@ public function onNameChanged(NameChanged $nameChanged): void
$this->nameChanged[$nameChanged->profileId->toString()] = $nameChanged->name;
}

public function table(): string
{
return 'projection_' . $this->subscriberId();
}

public function beginBatch(): void
{
$this->nameChanged = [];
Expand All @@ -73,7 +74,7 @@ public function commitBatch(): void
$this->connection->transactional(function (): void {
foreach ($this->nameChanged as $profileId => $name) {
$this->connection->update(
$this->table(),
self::TABLE_NAME,
['name' => $name],
['id' => $profileId],
);
Expand Down
25 changes: 13 additions & 12 deletions tests/Benchmark/BasicImplementation/Projection/ProfileProjector.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,19 @@
use Patchlevel\EventSourcing\Attribute\Projector;
use Patchlevel\EventSourcing\Attribute\Setup;
use Patchlevel\EventSourcing\Attribute\Subscribe;
use Patchlevel\EventSourcing\Attribute\SubscriptionId;
use Patchlevel\EventSourcing\Attribute\Teardown;
use Patchlevel\EventSourcing\Subscription\Subscriber\SubscriberUtil;
use Patchlevel\EventSourcing\Tests\Benchmark\BasicImplementation\Events\NameChanged;
use Patchlevel\EventSourcing\Tests\Benchmark\BasicImplementation\Events\ProfileCreated;
use Patchlevel\EventSourcing\Tests\Benchmark\BasicImplementation\Query\QueryProfileName;

use function sprintf;

#[Projector('profile')]
final class ProfileProjector
{
use SubscriberUtil;
#[SubscriptionId]
private const TABLE_NAME = 'projection_profile';

public function __construct(
private Connection $connection,
Expand All @@ -28,20 +31,23 @@ public function __construct(
#[Setup]
public function create(): void
{
$this->connection->executeStatement("CREATE TABLE IF NOT EXISTS {$this->table()} (id VARCHAR PRIMARY KEY, name VARCHAR);");
$this->connection->executeStatement(sprintf(
'CREATE TABLE IF NOT EXISTS %s (id VARCHAR PRIMARY KEY, name VARCHAR);',
self::TABLE_NAME,
));
}

#[Teardown]
public function drop(): void
{
$this->connection->executeStatement("DROP TABLE IF EXISTS {$this->table()};");
$this->connection->executeStatement(sprintf('DROP TABLE IF EXISTS %s;', self::TABLE_NAME));
}

#[Subscribe(ProfileCreated::class)]
public function onProfileCreated(ProfileCreated $profileCreated): void
{
$this->connection->insert(
$this->table(),
self::TABLE_NAME,
[
'id' => $profileCreated->profileId->toString(),
'name' => $profileCreated->name,
Expand All @@ -53,7 +59,7 @@ public function onProfileCreated(ProfileCreated $profileCreated): void
public function onNameChanged(NameChanged $nameChanged): void
{
$this->connection->update(
$this->table(),
self::TABLE_NAME,
['name' => $nameChanged->name],
['id' => $nameChanged->profileId->toString()],
);
Expand All @@ -63,13 +69,8 @@ public function onNameChanged(NameChanged $nameChanged): void
public function getProfileName(QueryProfileName $queryProfileName): string
{
return $this->connection->fetchAssociative(
"SELECT name FROM {$this->table()} WHERE id = :id;",
sprintf('SELECT name FROM %s WHERE id = :id;', self::TABLE_NAME),
['id' => $queryProfileName->id->toString()],
)['name'];
}

public function table(): string
{
return 'projection_' . $this->subscriberId();
}
}
Loading
Loading