From 00fa63411a45a2be59744faa392da9dcbc2cec51 Mon Sep 17 00:00:00 2001 From: Daniel Badura Date: Sun, 4 Oct 2026 23:39:23 +0200 Subject: [PATCH] Fix cache keys of the metadata factories The PSR-6 and PSR-16 metadata factories of aggregates, events and subscribers used the class name as cache key. Class names contain a backslash, which is a reserved character in PSR-6 and PSR-16, so the factories failed with any compliant cache like symfony/cache. The class name is now hashed and prefixed per metadata type, so the factories can also share one cache. A cache hit is only accepted if it holds the expected metadata, for aggregates it also has to belong to the requested class. --- composer.json | 1 + composer.lock | 181 +++++++++++++++++- .../Psr16AggregateRootMetadataFactory.php | 8 +- .../Psr6AggregateRootMetadataFactory.php | 8 +- src/Metadata/CacheKey.php | 35 ++++ .../Event/Psr16EventMetadataFactory.php | 8 +- .../Event/Psr6EventMetadataFactory.php | 10 +- .../Psr16SubscriberMetadataFactory.php | 8 +- .../Psr6SubscriberMetadataFactory.php | 10 +- .../Psr16AggregateRootMetadataFactoryTest.php | 81 ++++++++ .../Psr6AggregateRootMetadataFactoryTest.php | 82 ++++++++ tests/Unit/Metadata/CacheKeyTest.php | 40 ++++ .../Event/Psr16EventMetadataFactoryTest.php | 55 ++++++ .../Event/Psr6EventMetadataFactoryTest.php | 56 ++++++ .../Psr16SubscriberMetadataFactoryTest.php | 55 ++++++ .../Psr6SubscriberMetadataFactoryTest.php | 56 ++++++ 16 files changed, 668 insertions(+), 26 deletions(-) create mode 100644 src/Metadata/CacheKey.php create mode 100644 tests/Unit/Metadata/Aggregate/Psr16AggregateRootMetadataFactoryTest.php create mode 100644 tests/Unit/Metadata/Aggregate/Psr6AggregateRootMetadataFactoryTest.php create mode 100644 tests/Unit/Metadata/CacheKeyTest.php create mode 100644 tests/Unit/Metadata/Event/Psr16EventMetadataFactoryTest.php create mode 100644 tests/Unit/Metadata/Event/Psr6EventMetadataFactoryTest.php create mode 100644 tests/Unit/Metadata/Subscriber/Psr16SubscriberMetadataFactoryTest.php create mode 100644 tests/Unit/Metadata/Subscriber/Psr6SubscriberMetadataFactoryTest.php diff --git a/composer.json b/composer.json index 2aeb68832..f485c90d3 100644 --- a/composer.json +++ b/composer.json @@ -58,6 +58,7 @@ "phpstan/phpstan": "^2.1.11", "phpstan/phpstan-phpunit": "^2.0", "phpunit/phpunit": "^11.5.15", + "symfony/cache": "^6.4.0 || ^7.0.0 || ^8.0.0", "symfony/messenger": "^5.4.31 || ^6.4.0 || ^7.0.1 || ^8.0.0", "symfony/var-dumper": "^5.4.29 || ^6.4.0 || ^7.0.0 || ^8.0.0", "wnx/commonmark-markdown-renderer": "^1.5.0" diff --git a/composer.lock b/composer.lock index 23429ca17..c3102bb43 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "040555186133771a1ea827cd0aade8d4", + "content-hash": "7771c889d0fd6204373e2edc397d4839", "packages": [ { "name": "brick/math", @@ -6760,6 +6760,185 @@ ], "time": "2024-10-20T05:08:20+00:00" }, + { + "name": "symfony/cache", + "version": "v8.0.15", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache.git", + "reference": "3e9c898a1c2d78661676befcc2f9dd610c56c9dd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache/zipball/3e9c898a1c2d78661676befcc2f9dd610c56c9dd", + "reference": "3e9c898a1c2d78661676befcc2f9dd610c56c9dd", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "psr/cache": "^2.0|^3.0", + "psr/log": "^1.1|^2|^3", + "symfony/cache-contracts": "^3.6", + "symfony/service-contracts": "^2.5|^3", + "symfony/var-exporter": "^7.4|^8.0" + }, + "conflict": { + "ext-redis": "<6.1", + "ext-relay": "<0.12.1" + }, + "provide": { + "psr/cache-implementation": "2.0|3.0", + "psr/simple-cache-implementation": "1.0|2.0|3.0", + "symfony/cache-implementation": "1.1|2.0|3.0" + }, + "require-dev": { + "cache/integration-tests": "dev-master", + "doctrine/dbal": "^4.3", + "predis/predis": "^1.1|^2.0", + "psr/simple-cache": "^1.0|^2.0|^3.0", + "symfony/clock": "^7.4|^8.0", + "symfony/config": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/filesystem": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/messenger": "^7.4|^8.0", + "symfony/var-dumper": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Cache\\": "" + }, + "classmap": [ + "Traits/ValueWrapper.php" + ], + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides extended PSR-6, PSR-16 (and tags) implementations", + "homepage": "https://symfony.com", + "keywords": [ + "caching", + "psr6" + ], + "support": { + "source": "https://github.com/symfony/cache/tree/v8.0.15" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-07-29T04:09:38+00:00" + }, + { + "name": "symfony/cache-contracts", + "version": "v3.7.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache-contracts.git", + "reference": "9789738bc19af1106dc54d6afba9a0b467516cf2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/9789738bc19af1106dc54d6afba9a0b467516cf2", + "reference": "9789738bc19af1106dc54d6afba9a0b467516cf2", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/cache": "^3.0" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Cache\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to caching", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/cache-contracts/tree/v3.7.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-06-05T06:23:12+00:00" + }, { "name": "symfony/clock", "version": "v8.0.8", diff --git a/src/Metadata/AggregateRoot/Psr16AggregateRootMetadataFactory.php b/src/Metadata/AggregateRoot/Psr16AggregateRootMetadataFactory.php index 5db42085e..8304558c2 100644 --- a/src/Metadata/AggregateRoot/Psr16AggregateRootMetadataFactory.php +++ b/src/Metadata/AggregateRoot/Psr16AggregateRootMetadataFactory.php @@ -5,6 +5,7 @@ namespace Patchlevel\EventSourcing\Metadata\AggregateRoot; use Patchlevel\EventSourcing\Aggregate\AggregateRoot; +use Patchlevel\EventSourcing\Metadata\CacheKey; use Psr\SimpleCache\CacheInterface; final class Psr16AggregateRootMetadataFactory implements AggregateRootMetadataFactory @@ -24,16 +25,15 @@ public function __construct( */ public function metadata(string $aggregate): AggregateRootMetadata { - /** @var ?AggregateRootMetadata $metadata */ - $metadata = $this->cache->get($aggregate); + $metadata = $this->cache->get(CacheKey::forAggregateRoot($aggregate)); - if ($metadata !== null) { + if ($metadata instanceof AggregateRootMetadata && $metadata->className === $aggregate) { return $metadata; } $metadata = $this->aggregateRootMetadataFactory->metadata($aggregate); - $this->cache->set($aggregate, $metadata); + $this->cache->set(CacheKey::forAggregateRoot($aggregate), $metadata); return $metadata; } diff --git a/src/Metadata/AggregateRoot/Psr6AggregateRootMetadataFactory.php b/src/Metadata/AggregateRoot/Psr6AggregateRootMetadataFactory.php index d5ea25238..107fcee91 100644 --- a/src/Metadata/AggregateRoot/Psr6AggregateRootMetadataFactory.php +++ b/src/Metadata/AggregateRoot/Psr6AggregateRootMetadataFactory.php @@ -5,6 +5,7 @@ namespace Patchlevel\EventSourcing\Metadata\AggregateRoot; use Patchlevel\EventSourcing\Aggregate\AggregateRoot; +use Patchlevel\EventSourcing\Metadata\CacheKey; use Psr\Cache\CacheItemPoolInterface; final class Psr6AggregateRootMetadataFactory implements AggregateRootMetadataFactory @@ -24,13 +25,14 @@ public function __construct( */ public function metadata(string $aggregate): AggregateRootMetadata { - $item = $this->cache->getItem($aggregate); + $item = $this->cache->getItem(CacheKey::forAggregateRoot($aggregate)); if ($item->isHit()) { - /** @var AggregateRootMetadata $data */ $data = $item->get(); - return $data; + if ($data instanceof AggregateRootMetadata && $data->className === $aggregate) { + return $data; + } } $metadata = $this->aggregateRootMetadataFactory->metadata($aggregate); diff --git a/src/Metadata/CacheKey.php b/src/Metadata/CacheKey.php new file mode 100644 index 000000000..7f0ec5e7c --- /dev/null +++ b/src/Metadata/CacheKey.php @@ -0,0 +1,35 @@ +cache->get($event); + $metadata = $this->cache->get(CacheKey::forEvent($event)); - if ($metadata !== null) { + if ($metadata instanceof EventMetadata) { return $metadata; } $metadata = $this->eventMetadataFactory->metadata($event); - $this->cache->set($event, $metadata); + $this->cache->set(CacheKey::forEvent($event), $metadata); return $metadata; } diff --git a/src/Metadata/Event/Psr6EventMetadataFactory.php b/src/Metadata/Event/Psr6EventMetadataFactory.php index b8090af67..eadaa343d 100644 --- a/src/Metadata/Event/Psr6EventMetadataFactory.php +++ b/src/Metadata/Event/Psr6EventMetadataFactory.php @@ -4,10 +4,9 @@ namespace Patchlevel\EventSourcing\Metadata\Event; +use Patchlevel\EventSourcing\Metadata\CacheKey; use Psr\Cache\CacheItemPoolInterface; -use function assert; - final class Psr6EventMetadataFactory implements EventMetadataFactory { public function __construct( @@ -19,13 +18,14 @@ public function __construct( /** @param class-string $event */ public function metadata(string $event): EventMetadata { - $item = $this->cache->getItem($event); + $item = $this->cache->getItem(CacheKey::forEvent($event)); if ($item->isHit()) { $data = $item->get(); - assert($data instanceof EventMetadata); - return $data; + if ($data instanceof EventMetadata) { + return $data; + } } $metadata = $this->eventMetadataFactory->metadata($event); diff --git a/src/Metadata/Subscriber/Psr16SubscriberMetadataFactory.php b/src/Metadata/Subscriber/Psr16SubscriberMetadataFactory.php index e17fd9209..6636aa57e 100644 --- a/src/Metadata/Subscriber/Psr16SubscriberMetadataFactory.php +++ b/src/Metadata/Subscriber/Psr16SubscriberMetadataFactory.php @@ -4,6 +4,7 @@ namespace Patchlevel\EventSourcing\Metadata\Subscriber; +use Patchlevel\EventSourcing\Metadata\CacheKey; use Psr\SimpleCache\CacheInterface; final class Psr16SubscriberMetadataFactory implements SubscriberMetadataFactory @@ -17,16 +18,15 @@ public function __construct( /** @param class-string $subscriber */ public function metadata(string $subscriber): SubscriberMetadata { - /** @var ?SubscriberMetadata $metadata */ - $metadata = $this->cache->get($subscriber); + $metadata = $this->cache->get(CacheKey::forSubscriber($subscriber)); - if ($metadata !== null) { + if ($metadata instanceof SubscriberMetadata) { return $metadata; } $metadata = $this->subscriberMetadataFactory->metadata($subscriber); - $this->cache->set($subscriber, $metadata); + $this->cache->set(CacheKey::forSubscriber($subscriber), $metadata); return $metadata; } diff --git a/src/Metadata/Subscriber/Psr6SubscriberMetadataFactory.php b/src/Metadata/Subscriber/Psr6SubscriberMetadataFactory.php index 305e689ec..066d65bf8 100644 --- a/src/Metadata/Subscriber/Psr6SubscriberMetadataFactory.php +++ b/src/Metadata/Subscriber/Psr6SubscriberMetadataFactory.php @@ -4,10 +4,9 @@ namespace Patchlevel\EventSourcing\Metadata\Subscriber; +use Patchlevel\EventSourcing\Metadata\CacheKey; use Psr\Cache\CacheItemPoolInterface; -use function assert; - final class Psr6SubscriberMetadataFactory implements SubscriberMetadataFactory { public function __construct( @@ -19,13 +18,14 @@ public function __construct( /** @param class-string $subscriber */ public function metadata(string $subscriber): SubscriberMetadata { - $item = $this->cache->getItem($subscriber); + $item = $this->cache->getItem(CacheKey::forSubscriber($subscriber)); if ($item->isHit()) { $data = $item->get(); - assert($data instanceof SubscriberMetadata); - return $data; + if ($data instanceof SubscriberMetadata) { + return $data; + } } $metadata = $this->subscriberMetadataFactory->metadata($subscriber); diff --git a/tests/Unit/Metadata/Aggregate/Psr16AggregateRootMetadataFactoryTest.php b/tests/Unit/Metadata/Aggregate/Psr16AggregateRootMetadataFactoryTest.php new file mode 100644 index 000000000..0787fa608 --- /dev/null +++ b/tests/Unit/Metadata/Aggregate/Psr16AggregateRootMetadataFactoryTest.php @@ -0,0 +1,81 @@ +createMock(AggregateRootMetadataFactory::class); + $innerFactory->expects(self::once()) + ->method('metadata') + ->with(Profile::class) + ->willReturn($metadata); + + $cache = new Psr16Cache(new ArrayAdapter()); + $factory = new Psr16AggregateRootMetadataFactory($innerFactory, $cache); + + self::assertSame($metadata, $factory->metadata(Profile::class)); + self::assertTrue($cache->has(CacheKey::forAggregateRoot(Profile::class))); + self::assertEquals($metadata, $factory->metadata(Profile::class)); + } + + public function testMetadataIgnoresEntryOfOtherAggregate(): void + { + $metadata = new AggregateRootMetadata( + Profile::class, + 'profile', + 'id', + [], + [], + false, + null, + ); + $otherMetadata = new AggregateRootMetadata( + Profile2::class, + 'profile2', + 'id', + [], + [], + false, + null, + ); + + $innerFactory = $this->createMock(AggregateRootMetadataFactory::class); + $innerFactory->expects(self::once()) + ->method('metadata') + ->with(Profile::class) + ->willReturn($metadata); + + $cache = new Psr16Cache(new ArrayAdapter()); + $cache->set(CacheKey::forAggregateRoot(Profile::class), $otherMetadata); + + $factory = new Psr16AggregateRootMetadataFactory($innerFactory, $cache); + + self::assertSame($metadata, $factory->metadata(Profile::class)); + } +} diff --git a/tests/Unit/Metadata/Aggregate/Psr6AggregateRootMetadataFactoryTest.php b/tests/Unit/Metadata/Aggregate/Psr6AggregateRootMetadataFactoryTest.php new file mode 100644 index 000000000..931706923 --- /dev/null +++ b/tests/Unit/Metadata/Aggregate/Psr6AggregateRootMetadataFactoryTest.php @@ -0,0 +1,82 @@ +createMock(AggregateRootMetadataFactory::class); + $innerFactory->expects(self::once()) + ->method('metadata') + ->with(Profile::class) + ->willReturn($metadata); + + $cache = new ArrayAdapter(); + $factory = new Psr6AggregateRootMetadataFactory($innerFactory, $cache); + + self::assertSame($metadata, $factory->metadata(Profile::class)); + self::assertTrue($cache->hasItem(CacheKey::forAggregateRoot(Profile::class))); + self::assertEquals($metadata, $factory->metadata(Profile::class)); + } + + public function testMetadataIgnoresEntryOfOtherAggregate(): void + { + $metadata = new AggregateRootMetadata( + Profile::class, + 'profile', + 'id', + [], + [], + false, + null, + ); + $otherMetadata = new AggregateRootMetadata( + Profile2::class, + 'profile2', + 'id', + [], + [], + false, + null, + ); + + $innerFactory = $this->createMock(AggregateRootMetadataFactory::class); + $innerFactory->expects(self::once()) + ->method('metadata') + ->with(Profile::class) + ->willReturn($metadata); + + $cache = new ArrayAdapter(); + $item = $cache->getItem(CacheKey::forAggregateRoot(Profile::class)); + $item->set($otherMetadata); + $cache->save($item); + + $factory = new Psr6AggregateRootMetadataFactory($innerFactory, $cache); + + self::assertSame($metadata, $factory->metadata(Profile::class)); + } +} diff --git a/tests/Unit/Metadata/CacheKeyTest.php b/tests/Unit/Metadata/CacheKeyTest.php new file mode 100644 index 000000000..2194a6b04 --- /dev/null +++ b/tests/Unit/Metadata/CacheKeyTest.php @@ -0,0 +1,40 @@ +createMock(EventMetadataFactory::class); + $innerFactory->expects(self::once()) + ->method('metadata') + ->with(EmailChanged::class) + ->willReturn($metadata); + + $cache = new Psr16Cache(new ArrayAdapter()); + $factory = new Psr16EventMetadataFactory($innerFactory, $cache); + + self::assertSame($metadata, $factory->metadata(EmailChanged::class)); + self::assertTrue($cache->has(CacheKey::forEvent(EmailChanged::class))); + self::assertEquals($metadata, $factory->metadata(EmailChanged::class)); + } + + public function testMetadataIgnoresInvalidEntry(): void + { + $metadata = new EventMetadata('email_changed'); + + $innerFactory = $this->createMock(EventMetadataFactory::class); + $innerFactory->expects(self::once()) + ->method('metadata') + ->with(EmailChanged::class) + ->willReturn($metadata); + + $cache = new Psr16Cache(new ArrayAdapter()); + $cache->set(CacheKey::forEvent(EmailChanged::class), 'invalid'); + + $factory = new Psr16EventMetadataFactory($innerFactory, $cache); + + self::assertSame($metadata, $factory->metadata(EmailChanged::class)); + } +} diff --git a/tests/Unit/Metadata/Event/Psr6EventMetadataFactoryTest.php b/tests/Unit/Metadata/Event/Psr6EventMetadataFactoryTest.php new file mode 100644 index 000000000..58d084bf1 --- /dev/null +++ b/tests/Unit/Metadata/Event/Psr6EventMetadataFactoryTest.php @@ -0,0 +1,56 @@ +createMock(EventMetadataFactory::class); + $innerFactory->expects(self::once()) + ->method('metadata') + ->with(EmailChanged::class) + ->willReturn($metadata); + + $cache = new ArrayAdapter(); + $factory = new Psr6EventMetadataFactory($innerFactory, $cache); + + self::assertSame($metadata, $factory->metadata(EmailChanged::class)); + self::assertTrue($cache->hasItem(CacheKey::forEvent(EmailChanged::class))); + self::assertEquals($metadata, $factory->metadata(EmailChanged::class)); + } + + public function testMetadataIgnoresInvalidEntry(): void + { + $metadata = new EventMetadata('email_changed'); + + $innerFactory = $this->createMock(EventMetadataFactory::class); + $innerFactory->expects(self::once()) + ->method('metadata') + ->with(EmailChanged::class) + ->willReturn($metadata); + + $cache = new ArrayAdapter(); + $item = $cache->getItem(CacheKey::forEvent(EmailChanged::class)); + $item->set('invalid'); + $cache->save($item); + + $factory = new Psr6EventMetadataFactory($innerFactory, $cache); + + self::assertSame($metadata, $factory->metadata(EmailChanged::class)); + } +} diff --git a/tests/Unit/Metadata/Subscriber/Psr16SubscriberMetadataFactoryTest.php b/tests/Unit/Metadata/Subscriber/Psr16SubscriberMetadataFactoryTest.php new file mode 100644 index 000000000..40422df4e --- /dev/null +++ b/tests/Unit/Metadata/Subscriber/Psr16SubscriberMetadataFactoryTest.php @@ -0,0 +1,55 @@ +createMock(SubscriberMetadataFactory::class); + $innerFactory->expects(self::once()) + ->method('metadata') + ->with(BatchingSubscriber::class) + ->willReturn($metadata); + + $cache = new Psr16Cache(new ArrayAdapter()); + $factory = new Psr16SubscriberMetadataFactory($innerFactory, $cache); + + self::assertSame($metadata, $factory->metadata(BatchingSubscriber::class)); + self::assertTrue($cache->has(CacheKey::forSubscriber(BatchingSubscriber::class))); + self::assertEquals($metadata, $factory->metadata(BatchingSubscriber::class)); + } + + public function testMetadataIgnoresInvalidEntry(): void + { + $metadata = new SubscriberMetadata('batching'); + + $innerFactory = $this->createMock(SubscriberMetadataFactory::class); + $innerFactory->expects(self::once()) + ->method('metadata') + ->with(BatchingSubscriber::class) + ->willReturn($metadata); + + $cache = new Psr16Cache(new ArrayAdapter()); + $cache->set(CacheKey::forSubscriber(BatchingSubscriber::class), 'invalid'); + + $factory = new Psr16SubscriberMetadataFactory($innerFactory, $cache); + + self::assertSame($metadata, $factory->metadata(BatchingSubscriber::class)); + } +} diff --git a/tests/Unit/Metadata/Subscriber/Psr6SubscriberMetadataFactoryTest.php b/tests/Unit/Metadata/Subscriber/Psr6SubscriberMetadataFactoryTest.php new file mode 100644 index 000000000..eed408f26 --- /dev/null +++ b/tests/Unit/Metadata/Subscriber/Psr6SubscriberMetadataFactoryTest.php @@ -0,0 +1,56 @@ +createMock(SubscriberMetadataFactory::class); + $innerFactory->expects(self::once()) + ->method('metadata') + ->with(BatchingSubscriber::class) + ->willReturn($metadata); + + $cache = new ArrayAdapter(); + $factory = new Psr6SubscriberMetadataFactory($innerFactory, $cache); + + self::assertSame($metadata, $factory->metadata(BatchingSubscriber::class)); + self::assertTrue($cache->hasItem(CacheKey::forSubscriber(BatchingSubscriber::class))); + self::assertEquals($metadata, $factory->metadata(BatchingSubscriber::class)); + } + + public function testMetadataIgnoresInvalidEntry(): void + { + $metadata = new SubscriberMetadata('batching'); + + $innerFactory = $this->createMock(SubscriberMetadataFactory::class); + $innerFactory->expects(self::once()) + ->method('metadata') + ->with(BatchingSubscriber::class) + ->willReturn($metadata); + + $cache = new ArrayAdapter(); + $item = $cache->getItem(CacheKey::forSubscriber(BatchingSubscriber::class)); + $item->set('invalid'); + $cache->save($item); + + $factory = new Psr6SubscriberMetadataFactory($innerFactory, $cache); + + self::assertSame($metadata, $factory->metadata(BatchingSubscriber::class)); + } +}