diff --git a/Makefile b/Makefile index a836f002..79259706 100644 --- a/Makefile +++ b/Makefile @@ -18,11 +18,11 @@ cs: vendor .PHONY: phpstan phpstan: vendor ## run phpstan static code analyser - vendor/bin/phpstan analyse + php -d memory_limit=312M vendor/bin/phpstan analyse .PHONY: phpstan-baseline phpstan-baseline: vendor ## run phpstan static code analyser - vendor/bin/phpstan analyse --generate-baseline + php -d memory_limit=312M vendor/bin/phpstan analyse --generate-baseline .PHONY: psalm psalm: vendor ## run psalm static code analyser diff --git a/baseline.xml b/baseline.xml index c5358ae7..cdf4ed2f 100644 --- a/baseline.xml +++ b/baseline.xml @@ -369,6 +369,13 @@ + + + + + + + diff --git a/src/Subscription/Engine/DefaultSubscriptionEngine.php b/src/Subscription/Engine/DefaultSubscriptionEngine.php index 8e352dee..4613bac9 100644 --- a/src/Subscription/Engine/DefaultSubscriptionEngine.php +++ b/src/Subscription/Engine/DefaultSubscriptionEngine.php @@ -62,7 +62,7 @@ public function setup(SubscriptionEngineCriteria|null $criteria = null, bool $sk groups: $criteria->groups, status: [Status::New], ), - function (array $subscriptions) use ($skipBooting): Result { + function (SubscriptionCollection $subscriptions) use ($skipBooting): Result { if (count($subscriptions) === 0) { $this->logger?->info('Subscription Engine: No subscriptions to setup, finish setup.'); @@ -171,14 +171,14 @@ public function boot( groups: $criteria->groups, status: [Status::Booting], ), - function ($subscriptions) use ($limit): ProcessedResult { + function (SubscriptionCollection $subscriptions) use ($limit): ProcessedResult { if (count($subscriptions) === 0) { $this->logger?->info('Subscription Engine: No subscriptions in booting status, finish booting.'); return new ProcessedResult(0, true); } - $startIndex = $this->lowestSubscriptionPosition($subscriptions); + $startIndex = $subscriptions->lowestPosition(); $this->logger?->debug( sprintf( @@ -198,6 +198,8 @@ function ($subscriptions) use ($limit): ProcessedResult { ); foreach ($stream as $message) { + $messageCounter++; + $index = $stream->index(); if ($index === null) { @@ -205,10 +207,6 @@ function ($subscriptions) use ($limit): ProcessedResult { } foreach ($subscriptions as $subscription) { - if (!$subscription->isBooting()) { - continue; - } - if ($subscription->position() >= $index) { $this->logger?->debug( sprintf( @@ -229,9 +227,17 @@ function ($subscriptions) use ($limit): ProcessedResult { } $errors[] = $error; - } - $messageCounter++; + $subscriptions->remove($subscription); + + if (count($subscriptions) === 0) { + $this->logger?->info( + 'Subscription Engine: No subscriptions in booting status, finish booting.', + ); + + break 2; + } + } $this->logger?->debug( sprintf( @@ -265,6 +271,8 @@ function ($subscriptions) use ($limit): ProcessedResult { if ($error) { $errors[] = $error; + + $subscriptions->remove($subscription); } $this->subscriptionManager->update($subscription); @@ -275,10 +283,6 @@ function ($subscriptions) use ($limit): ProcessedResult { $this->logger?->debug('Subscription Engine: End of stream for booting has been reached.'); foreach ($subscriptions as $subscription) { - if (!$subscription->isBooting()) { - continue; - } - if ($subscription->runMode() === RunMode::Once) { $subscription->finished(); $this->subscriptionManager->update($subscription); @@ -340,14 +344,14 @@ public function run( groups: $criteria->groups, status: [Status::Active], ), - function (array $subscriptions) use ($limit): ProcessedResult { + function (SubscriptionCollection $subscriptions) use ($limit): ProcessedResult { if (count($subscriptions) === 0) { $this->logger?->info('Subscription Engine: No subscriptions to process, finish processing.'); return new ProcessedResult(0, true); } - $startIndex = $this->lowestSubscriptionPosition($subscriptions); + $startIndex = $subscriptions->lowestPosition(); $this->logger?->debug( sprintf( @@ -366,6 +370,8 @@ function (array $subscriptions) use ($limit): ProcessedResult { $stream = $this->messageStore->load($criteria); foreach ($stream as $message) { + $messageCounter++; + $index = $stream->index(); if ($index === null) { @@ -373,10 +379,6 @@ function (array $subscriptions) use ($limit): ProcessedResult { } foreach ($subscriptions as $subscription) { - if (!$subscription->isActive()) { - continue; - } - if ($subscription->position() >= $index) { $this->logger?->debug( sprintf( @@ -397,9 +399,17 @@ function (array $subscriptions) use ($limit): ProcessedResult { } $errors[] = $error; - } - $messageCounter++; + $subscriptions->remove($subscription); + + if (count($subscriptions) === 0) { + $this->logger?->info( + 'Subscription Engine: No subscriptions in booting status, finish booting.', + ); + + break 2; + } + } $this->logger?->debug(sprintf( 'Subscription Engine: Current event stream position: %s', @@ -427,6 +437,8 @@ function (array $subscriptions) use ($limit): ProcessedResult { if ($error) { $errors[] = $error; + + $subscriptions->remove($subscription); } $this->subscriptionManager->update($subscription); @@ -435,10 +447,6 @@ function (array $subscriptions) use ($limit): ProcessedResult { } foreach ($subscriptions as $subscription) { - if (!$subscription->isActive()) { - continue; - } - if ($subscription->runMode() !== RunMode::Once) { continue; } @@ -481,7 +489,7 @@ public function teardown(SubscriptionEngineCriteria|null $criteria = null): Resu groups: $criteria->groups, status: [Status::Detached], ), - function (array $subscriptions): Result { + function (SubscriptionCollection $subscriptions): Result { /** @var list $errors */ $errors = []; @@ -570,7 +578,7 @@ public function remove(SubscriptionEngineCriteria|null $criteria = null): Result ids: $criteria->ids, groups: $criteria->groups, ), - function (array $subscriptions): Result { + function (SubscriptionCollection $subscriptions): Result { /** @var list $errors */ $errors = []; @@ -662,7 +670,7 @@ public function reactivate(SubscriptionEngineCriteria|null $criteria = null): Re Status::Finished, ], ), - function (array $subscriptions): Result { + function (SubscriptionCollection $subscriptions): Result { foreach ($subscriptions as $subscription) { $subscriber = $this->subscriber($subscription->id()); @@ -725,7 +733,7 @@ public function pause(SubscriptionEngineCriteria|null $criteria = null): Result Status::Error, ], ), - function (array $subscriptions): Result { + function (SubscriptionCollection $subscriptions): Result { /** @var Subscription $subscription */ foreach ($subscriptions as $subscription) { $subscriber = $this->subscriber($subscription->id()); @@ -868,7 +876,7 @@ private function markDetachedSubscriptions(SubscriptionEngineCriteria $criteria) groups: $criteria->groups, status: [Status::Active, Status::Paused, Status::Finished], ), - function (array $subscriptions): void { + function (SubscriptionCollection $subscriptions): void { foreach ($subscriptions as $subscription) { $subscriber = $this->subscriber($subscription->id()); @@ -898,7 +906,7 @@ private function retrySubscriptions(SubscriptionEngineCriteria $criteria): void groups: $criteria->groups, status: [Status::Error], ), - function (array $subscriptions): void { + function (SubscriptionCollection $subscriptions): void { /** @var Subscription $subscription */ foreach ($subscriptions as $subscription) { $error = $subscription->subscriptionError(); @@ -941,7 +949,7 @@ private function discoverNewSubscriptions(): void { $this->subscriptionManager->findForUpdate( new SubscriptionCriteria(), - function (array $subscriptions): void { + function (SubscriptionCollection $subscriptions): void { $latestIndex = null; foreach ($this->subscriberRepository->all() as $subscriber) { @@ -986,26 +994,6 @@ private function latestIndex(): int return $stream->index() ?: 0; } - /** @param list $subscriptions */ - private function lowestSubscriptionPosition(array $subscriptions): int - { - $min = null; - - foreach ($subscriptions as $subscription) { - if ($min !== null && $subscription->position() >= $min) { - continue; - } - - $min = $subscription->position(); - } - - if ($min === null) { - return 0; - } - - return $min; - } - private function handleError(Subscription $subscription, Throwable $throwable): void { $subscription->error($throwable); diff --git a/src/Subscription/Engine/SubscriptionCollection.php b/src/Subscription/Engine/SubscriptionCollection.php new file mode 100644 index 00000000..f1ed60b5 --- /dev/null +++ b/src/Subscription/Engine/SubscriptionCollection.php @@ -0,0 +1,67 @@ + + */ +final class SubscriptionCollection implements IteratorAggregate, Countable +{ + /** @param list $subscriptions */ + public function __construct( + private array $subscriptions = [], + ) { + } + + /** @return Traversable */ + public function getIterator(): Traversable + { + yield from $this->subscriptions; + } + + public function remove(Subscription $subscription): void + { + $this->subscriptions = array_values( + array_filter( + $this->subscriptions, + static fn (Subscription $s) => $s !== $subscription, + ), + ); + } + + public function count(): int + { + return count($this->subscriptions); + } + + public function lowestPosition(): int + { + $min = null; + + foreach ($this->subscriptions as $subscription) { + if ($min !== null && $subscription->position() >= $min) { + continue; + } + + $min = $subscription->position(); + } + + if ($min === null) { + return 0; + } + + return $min; + } +} diff --git a/src/Subscription/Engine/SubscriptionManager.php b/src/Subscription/Engine/SubscriptionManager.php index 209a578c..96a36f9b 100644 --- a/src/Subscription/Engine/SubscriptionManager.php +++ b/src/Subscription/Engine/SubscriptionManager.php @@ -32,7 +32,7 @@ public function __construct( } /** - * @param Closure(list):T $closure + * @param Closure(SubscriptionCollection):T $closure * * @return T * @@ -42,7 +42,11 @@ public function findForUpdate(SubscriptionCriteria $criteria, Closure $closure): { if (!$this->subscriptionStore instanceof LockableSubscriptionStore) { try { - return $closure($this->subscriptionStore->find($criteria)); + return $closure( + new SubscriptionCollection( + $this->subscriptionStore->find($criteria), + ), + ); } finally { $this->flush(); } @@ -52,7 +56,11 @@ public function findForUpdate(SubscriptionCriteria $criteria, Closure $closure): /** @return T */ function () use ($closure, $criteria): mixed { try { - return $closure($this->subscriptionStore->find($criteria)); + return $closure( + new SubscriptionCollection( + $this->subscriptionStore->find($criteria), + ), + ); } finally { $this->flush(); } diff --git a/tests/Unit/Subscription/Engine/SubscriptionManagerTest.php b/tests/Unit/Subscription/Engine/SubscriptionManagerTest.php index 24c868af..d779ded8 100644 --- a/tests/Unit/Subscription/Engine/SubscriptionManagerTest.php +++ b/tests/Unit/Subscription/Engine/SubscriptionManagerTest.php @@ -13,6 +13,8 @@ use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; +use function iterator_to_array; + /** @covers \Patchlevel\EventSourcing\Subscription\Engine\SubscriptionManager */ final class SubscriptionManagerTest extends TestCase { @@ -107,7 +109,7 @@ public function testFind(): void $manager = new SubscriptionManager($store->reveal()); $result = $manager->find($criteria); - self::assertSame([$subscription], $result); + self::assertSame([$subscription], iterator_to_array($result)); } public function testFindForUpdateWithoutLock(): void @@ -126,7 +128,7 @@ public function testFindForUpdateWithoutLock(): void return $subscriptions; }); - self::assertSame([$subscription], $result); + self::assertSame([$subscription], iterator_to_array($result)); } public function testFindForUpdateWithLock(): void @@ -151,6 +153,6 @@ public function testFindForUpdateWithLock(): void return $subscriptions; }); - self::assertSame([$subscription], $result); + self::assertSame([$subscription], iterator_to_array($result)); } }