From 5d505e165211d436a3c20711dcb013a63adf5272 Mon Sep 17 00:00:00 2001 From: Martin Linzmayer Date: Wed, 10 Dec 2025 14:24:19 +0100 Subject: [PATCH 01/10] fix(cache): add support for cache with namespaces --- ...TagAwareCacheAdapterForV3WithNamespace.php | 68 +++++++++++++++++++ src/aliases.php | 7 +- .../Controller/NamespacedCacheController.php | 35 ++++++++++ tests/End2End/App/routing.yml | 4 ++ tests/End2End/App/tracing.yml | 6 ++ tests/End2End/TracingCacheEnd2EndTest.php | 25 +++++++ 6 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 src/Tracing/Cache/TraceableTagAwareCacheAdapterForV3WithNamespace.php create mode 100644 tests/End2End/App/Controller/NamespacedCacheController.php diff --git a/src/Tracing/Cache/TraceableTagAwareCacheAdapterForV3WithNamespace.php b/src/Tracing/Cache/TraceableTagAwareCacheAdapterForV3WithNamespace.php new file mode 100644 index 00000000..a7ce3300 --- /dev/null +++ b/src/Tracing/Cache/TraceableTagAwareCacheAdapterForV3WithNamespace.php @@ -0,0 +1,68 @@ + + */ + use TraceableCacheAdapterTrait; + + /** + * @param HubInterface $hub The current hub + * @param TagAwareAdapterInterface $decoratedAdapter The decorated cache adapter + */ + public function __construct(HubInterface $hub, TagAwareAdapterInterface $decoratedAdapter) + { + $this->hub = $hub; + $this->decoratedAdapter = $decoratedAdapter; + } + + /** + * {@inheritdoc} + * + * @param mixed[] $metadata + */ + public function get(string $key, callable $callback, ?float $beta = null, ?array &$metadata = null): mixed + { + return $this->traceGet($key, $callback, $beta, $metadata); + } + + /** + * {@inheritdoc} + */ + public function invalidateTags(array $tags): bool + { + return $this->traceFunction('cache.invalidate_tags', function () use ($tags): bool { + return $this->decoratedAdapter->invalidateTags($tags); + }); + } + + public function withSubNamespace(string $namespace): static + { + if (!$this->decoratedAdapter instanceof NamespacedPoolInterface) { + throw new \BadMethodCallException(\sprintf('The %s::withSubNamespace() method is not supported because the decorated adapter does not implement the "%s" interface.', self::class, NamespacedPoolInterface::class)); + } + + $clone = clone $this; + $clone->decoratedAdapter = $this->decoratedAdapter->withSubNamespace($namespace); + + return $clone; + } +} diff --git a/src/aliases.php b/src/aliases.php index e0433bcb..8a99b419 100644 --- a/src/aliases.php +++ b/src/aliases.php @@ -13,6 +13,7 @@ use Sentry\SentryBundle\Tracing\Cache\TraceableTagAwareCacheAdapter; use Sentry\SentryBundle\Tracing\Cache\TraceableTagAwareCacheAdapterForV2; use Sentry\SentryBundle\Tracing\Cache\TraceableTagAwareCacheAdapterForV3; +use Sentry\SentryBundle\Tracing\Cache\TraceableTagAwareCacheAdapterForV3WithNamespace; use Sentry\SentryBundle\Tracing\Doctrine\DBAL\TracingDriver; use Sentry\SentryBundle\Tracing\Doctrine\DBAL\TracingDriverConnection; use Sentry\SentryBundle\Tracing\Doctrine\DBAL\TracingDriverConnectionFactory; @@ -53,7 +54,11 @@ class_alias(TraceableCacheAdapterForV3::class, TraceableCacheAdapter::class); } if (!class_exists(TraceableTagAwareCacheAdapter::class, false)) { - class_alias(TraceableTagAwareCacheAdapterForV3::class, TraceableTagAwareCacheAdapter::class); + if (interface_exists(NamespacedPoolInterface::class)) { + class_alias(TraceableTagAwareCacheAdapterForV3WithNamespace::class, TraceableTagAwareCacheAdapter::class); + } else { + class_alias(TraceableTagAwareCacheAdapterForV3::class, TraceableTagAwareCacheAdapter::class); + } } } else { if (!class_exists(TraceableCacheAdapter::class, false)) { diff --git a/tests/End2End/App/Controller/NamespacedCacheController.php b/tests/End2End/App/Controller/NamespacedCacheController.php new file mode 100644 index 00000000..2a09d85a --- /dev/null +++ b/tests/End2End/App/Controller/NamespacedCacheController.php @@ -0,0 +1,35 @@ +cache = $cache; + } + + public function populateNamespacedCache(): Response + { + $namespaced = $this->cache->withSubNamespace('tests'); + + $namespaced->get('namespaced-key', function (ItemInterface $item) { + $item->tag(['a tag name']); + + return 'namespaced-value'; + }); + + return new Response(); + } +} diff --git a/tests/End2End/App/routing.yml b/tests/End2End/App/routing.yml index caaa9f5d..0303d467 100644 --- a/tests/End2End/App/routing.yml +++ b/tests/End2End/App/routing.yml @@ -70,6 +70,10 @@ psr_tracing_cache_delete: path: /tracing/cache/psr/delete defaults: { _controller: 'Sentry\SentryBundle\Tests\End2End\App\Controller\PsrTracingCacheController::testDelete' } +namespaced_tracing_cache_populate: + path: /tracing/cache/namespaced/populate + defaults: { _controller: 'Sentry\SentryBundle\Tests\End2End\App\Controller\NamespacedCacheController::populateNamespacedCache' } + just_logging: path: /just-logging defaults: { _controller: 'Sentry\SentryBundle\Tests\End2End\App\Controller\LoggingController::justLogging' } diff --git a/tests/End2End/App/tracing.yml b/tests/End2End/App/tracing.yml index f54e14e5..e38dbf95 100644 --- a/tests/End2End/App/tracing.yml +++ b/tests/End2End/App/tracing.yml @@ -23,3 +23,9 @@ services: autowire: true tags: - { name: controller.service_arguments } + + Sentry\SentryBundle\Tests\End2End\App\Controller\NamespacedCacheController: + arguments: + $cache: '@cache.app.taggable' + tags: + - { name: controller.service_arguments } diff --git a/tests/End2End/TracingCacheEnd2EndTest.php b/tests/End2End/TracingCacheEnd2EndTest.php index 064abf98..bfef8ecb 100644 --- a/tests/End2End/TracingCacheEnd2EndTest.php +++ b/tests/End2End/TracingCacheEnd2EndTest.php @@ -223,4 +223,29 @@ public function testPsrCacheDelete(): void $this->assertEquals('cache.remove', $span->getOp()); $this->assertNull($span->getData('cache.item_size')); } + + public function testNamespacedTagAwareCache(): void + { + if (!interface_exists(\Symfony\Contracts\Cache\NamespacedPoolInterface::class)) { + $this->markTestSkipped('Namespaced caches are not supported by this Symfony version.'); + } + + $client = static::createClient(['debug' => false]); + + $client->request('GET', '/tracing/cache/namespaced/populate'); + $this->assertSame(200, $client->getResponse()->getStatusCode()); + + $this->assertCount(1, StubTransport::$events); + $event = StubTransport::$events[0]; + + $cacheGetSpans = array_values(array_filter($event->getSpans(), static function ($span) { + return 'cache.get' === $span->getOp(); + })); + $this->assertNotEmpty($cacheGetSpans); + + $cachePutSpans = array_filter($event->getSpans(), static function ($span) { + return 'cache.put' === $span->getOp(); + }); + $this->assertNotEmpty($cachePutSpans); + } } From 512c89db83b7744b82fa28394b420aeab0739c49 Mon Sep 17 00:00:00 2001 From: Martin Linzmayer Date: Wed, 10 Dec 2025 14:36:08 +0100 Subject: [PATCH 02/10] skip when cache has no namespaces --- tests/End2End/TracingCacheEnd2EndTest.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/End2End/TracingCacheEnd2EndTest.php b/tests/End2End/TracingCacheEnd2EndTest.php index bfef8ecb..a9ea0d38 100644 --- a/tests/End2End/TracingCacheEnd2EndTest.php +++ b/tests/End2End/TracingCacheEnd2EndTest.php @@ -230,9 +230,16 @@ public function testNamespacedTagAwareCache(): void $this->markTestSkipped('Namespaced caches are not supported by this Symfony version.'); } - $client = static::createClient(['debug' => false]); + $client = static::createClient(['debug' => true]); + $cache = static::getContainer()->get('cache.app.taggable'); + + // make sure that the configured taggable cache supports namespaces before running this test + if (!$cache instanceof \Symfony\Contracts\Cache\NamespacedPoolInterface) { + $this->markTestSkipped('The configured tag-aware cache pool does not support namespaces.'); + } $client->request('GET', '/tracing/cache/namespaced/populate'); + $this->assertSame('', $client->getResponse()->getContent()); $this->assertSame(200, $client->getResponse()->getStatusCode()); $this->assertCount(1, StubTransport::$events); From dd0e6f0c76de33df23cb9bdf92f34d4eff93c6d5 Mon Sep 17 00:00:00 2001 From: Martin Linzmayer Date: Wed, 10 Dec 2025 14:40:57 +0100 Subject: [PATCH 03/10] fix --- tests/End2End/TracingCacheEnd2EndTest.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/End2End/TracingCacheEnd2EndTest.php b/tests/End2End/TracingCacheEnd2EndTest.php index a9ea0d38..e6e1f738 100644 --- a/tests/End2End/TracingCacheEnd2EndTest.php +++ b/tests/End2End/TracingCacheEnd2EndTest.php @@ -230,7 +230,7 @@ public function testNamespacedTagAwareCache(): void $this->markTestSkipped('Namespaced caches are not supported by this Symfony version.'); } - $client = static::createClient(['debug' => true]); + $client = static::createClient(['debug' => false]); $cache = static::getContainer()->get('cache.app.taggable'); // make sure that the configured taggable cache supports namespaces before running this test @@ -239,7 +239,6 @@ public function testNamespacedTagAwareCache(): void } $client->request('GET', '/tracing/cache/namespaced/populate'); - $this->assertSame('', $client->getResponse()->getContent()); $this->assertSame(200, $client->getResponse()->getStatusCode()); $this->assertCount(1, StubTransport::$events); From 0caf67deb4c3df2e7125836f9e70a80e31987106 Mon Sep 17 00:00:00 2001 From: Martin Linzmayer Date: Thu, 11 Dec 2025 10:24:14 +0100 Subject: [PATCH 04/10] mocked tests --- .../TraceableTagAwareCacheAdapterTest.php | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/tests/Tracing/Cache/TraceableTagAwareCacheAdapterTest.php b/tests/Tracing/Cache/TraceableTagAwareCacheAdapterTest.php index d856601e..7c0d7a7e 100644 --- a/tests/Tracing/Cache/TraceableTagAwareCacheAdapterTest.php +++ b/tests/Tracing/Cache/TraceableTagAwareCacheAdapterTest.php @@ -5,10 +5,16 @@ namespace Sentry\SentryBundle\Tests\Tracing\Cache; use Sentry\SentryBundle\Tracing\Cache\TraceableTagAwareCacheAdapter; +use Sentry\SentryBundle\Tracing\Cache\TraceableTagAwareCacheAdapterForV3WithNamespace; use Sentry\Tracing\Transaction; use Sentry\Tracing\TransactionContext; use Symfony\Component\Cache\Adapter\AdapterInterface; use Symfony\Component\Cache\Adapter\TagAwareAdapterInterface; +use Symfony\Contracts\Cache\NamespacedPoolInterface; + +interface TagAwareNamespacedPoolInterface extends TagAwareAdapterInterface, NamespacedPoolInterface +{ +} /** * @phpstan-extends AbstractTraceableCacheAdapterTest @@ -42,6 +48,49 @@ public function testInvalidateTags(): void $this->assertNotNull($spans[1]->getEndTimestamp()); } + public function testWithSubNamespaceThrowsWhenNotNamespaced(): void + { + if (!interface_exists(NamespacedPoolInterface::class)) { + $this->markTestSkipped('Namespaced caches are not supported by this Symfony version.'); + } + + $decoratedAdapter = $this->createMock(TagAwareAdapterInterface::class); + $adapter = new TraceableTagAwareCacheAdapterForV3WithNamespace($this->hub, $decoratedAdapter); + + $this->expectException(\BadMethodCallException::class); + $this->expectExceptionMessage('withSubNamespace() method is not supported'); + + $adapter->withSubNamespace('foo'); + } + + public function testWithSubNamespaceReturnsNamespacedAdapter(): void + { + if (!interface_exists(NamespacedPoolInterface::class)) { + $this->markTestSkipped('Namespaced caches are not supported by this Symfony version.'); + } + + $decoratedAdapter = $this->createMock(TagAwareNamespacedPoolInterface::class); + $namespacedAdapter = $this->createMock(TagAwareNamespacedPoolInterface::class); + + $decoratedAdapter + ->expects($this->once()) + ->method('withSubNamespace') + ->with('foo') + ->willReturn($namespacedAdapter); + + $adapter = new TraceableTagAwareCacheAdapterForV3WithNamespace($this->hub, $decoratedAdapter); + + $result = $adapter->withSubNamespace('foo'); + + $this->assertInstanceOf(NamespacedPoolInterface::class, $result); + $this->assertNotSame($adapter, $result); + + $ref = new \ReflectionProperty($result, 'decoratedAdapter'); + $ref->setAccessible(true); + + $this->assertSame($namespacedAdapter, $ref->getValue($result)); + } + /** * {@inheritdoc} */ From 47a97729a803cbc7b96f6603ad6c4be8114490fa Mon Sep 17 00:00:00 2001 From: Martin Linzmayer Date: Thu, 11 Dec 2025 10:29:18 +0100 Subject: [PATCH 05/10] guard for test --- tests/Tracing/Cache/TraceableTagAwareCacheAdapterTest.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/Tracing/Cache/TraceableTagAwareCacheAdapterTest.php b/tests/Tracing/Cache/TraceableTagAwareCacheAdapterTest.php index 7c0d7a7e..0503dca6 100644 --- a/tests/Tracing/Cache/TraceableTagAwareCacheAdapterTest.php +++ b/tests/Tracing/Cache/TraceableTagAwareCacheAdapterTest.php @@ -12,8 +12,10 @@ use Symfony\Component\Cache\Adapter\TagAwareAdapterInterface; use Symfony\Contracts\Cache\NamespacedPoolInterface; -interface TagAwareNamespacedPoolInterface extends TagAwareAdapterInterface, NamespacedPoolInterface -{ +if (interface_exists(NamespacedPoolInterface::class)) { + interface TagAwareNamespacedPoolInterface extends TagAwareAdapterInterface, NamespacedPoolInterface + { + } } /** From 1f30a50fa96f69bdf1038ea943ec9e4200d07506 Mon Sep 17 00:00:00 2001 From: Martin Linzmayer Date: Thu, 11 Dec 2025 10:41:01 +0100 Subject: [PATCH 06/10] alias guard --- src/aliases.php | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/aliases.php b/src/aliases.php index 8a99b419..2c98f103 100644 --- a/src/aliases.php +++ b/src/aliases.php @@ -9,11 +9,9 @@ use Sentry\SentryBundle\Tracing\Cache\TraceableCacheAdapter; use Sentry\SentryBundle\Tracing\Cache\TraceableCacheAdapterForV2; use Sentry\SentryBundle\Tracing\Cache\TraceableCacheAdapterForV3; -use Sentry\SentryBundle\Tracing\Cache\TraceableCacheAdapterForV3WithNamespace; use Sentry\SentryBundle\Tracing\Cache\TraceableTagAwareCacheAdapter; use Sentry\SentryBundle\Tracing\Cache\TraceableTagAwareCacheAdapterForV2; use Sentry\SentryBundle\Tracing\Cache\TraceableTagAwareCacheAdapterForV3; -use Sentry\SentryBundle\Tracing\Cache\TraceableTagAwareCacheAdapterForV3WithNamespace; use Sentry\SentryBundle\Tracing\Doctrine\DBAL\TracingDriver; use Sentry\SentryBundle\Tracing\Doctrine\DBAL\TracingDriverConnection; use Sentry\SentryBundle\Tracing\Doctrine\DBAL\TracingDriverConnectionFactory; @@ -40,22 +38,21 @@ use Symfony\Component\Cache\DoctrineProvider; use Symfony\Component\HttpClient\HttpClient; use Symfony\Component\HttpClient\Response\StreamableInterface; -use Symfony\Contracts\Cache\NamespacedPoolInterface; use Symfony\Contracts\HttpClient\HttpClientInterface; if (interface_exists(AdapterInterface::class)) { if (!class_exists(DoctrineProvider::class, false) && version_compare(\PHP_VERSION, '8.0.0', '>=')) { if (!class_exists(TraceableCacheAdapter::class, false)) { - if (interface_exists(NamespacedPoolInterface::class)) { - class_alias(TraceableCacheAdapterForV3WithNamespace::class, TraceableCacheAdapter::class); + if (interface_exists('Symfony\\Contracts\\Cache\\NamespacedPoolInterface')) { + class_alias('Sentry\\SentryBundle\\Tracing\\Cache\\TraceableCacheAdapterForV3WithNamespace', TraceableCacheAdapter::class); } else { class_alias(TraceableCacheAdapterForV3::class, TraceableCacheAdapter::class); } } if (!class_exists(TraceableTagAwareCacheAdapter::class, false)) { - if (interface_exists(NamespacedPoolInterface::class)) { - class_alias(TraceableTagAwareCacheAdapterForV3WithNamespace::class, TraceableTagAwareCacheAdapter::class); + if (interface_exists('Symfony\\Contracts\\Cache\\NamespacedPoolInterface')) { + class_alias('Sentry\\SentryBundle\\Tracing\\Cache\\TraceableTagAwareCacheAdapterForV3WithNamespace', TraceableTagAwareCacheAdapter::class); } else { class_alias(TraceableTagAwareCacheAdapterForV3::class, TraceableTagAwareCacheAdapter::class); } From d4eae9df3d97eab603ac4075bf329d57a9e6e826 Mon Sep 17 00:00:00 2001 From: Martin Linzmayer Date: Thu, 11 Dec 2025 11:42:57 +0100 Subject: [PATCH 07/10] add namespace attribute --- ...raceableCacheAdapterForV3WithNamespace.php | 8 ++ .../Cache/TraceableCacheAdapterTrait.php | 50 +++++++++--- ...TagAwareCacheAdapterForV3WithNamespace.php | 8 ++ .../TraceableTagAwareCacheAdapterTest.php | 76 +++++++++++++++---- 4 files changed, 116 insertions(+), 26 deletions(-) diff --git a/src/Tracing/Cache/TraceableCacheAdapterForV3WithNamespace.php b/src/Tracing/Cache/TraceableCacheAdapterForV3WithNamespace.php index 843d8d2a..97d51a97 100644 --- a/src/Tracing/Cache/TraceableCacheAdapterForV3WithNamespace.php +++ b/src/Tracing/Cache/TraceableCacheAdapterForV3WithNamespace.php @@ -24,6 +24,11 @@ final class TraceableCacheAdapterForV3WithNamespace implements AdapterInterface, */ use TraceableCacheAdapterTrait; + /** + * @var string|null + */ + private $namespace; + /** * @param HubInterface $hub The current hub * @param AdapterInterface $decoratedAdapter The decorated cache adapter @@ -54,6 +59,9 @@ public function withSubNamespace(string $namespace): static $clone = clone $this; $clone->decoratedAdapter = $this->decoratedAdapter->withSubNamespace($namespace); + $clone->namespace = null === $this->namespace + ? $namespace + : \sprintf('%s.%s', $this->namespace, $namespace); return $clone; } diff --git a/src/Tracing/Cache/TraceableCacheAdapterTrait.php b/src/Tracing/Cache/TraceableCacheAdapterTrait.php index 42a80292..a3b03e5d 100644 --- a/src/Tracing/Cache/TraceableCacheAdapterTrait.php +++ b/src/Tracing/Cache/TraceableCacheAdapterTrait.php @@ -200,16 +200,23 @@ private function traceFunction(string $spanOperation, \Closure $callback, ?strin try { $result = $callback(); - // Necessary for static analysis. Otherwise, the TResult type is assumed to be CacheItemInterface. - if (!$result instanceof CacheItemInterface) { - return $result; + $data = []; + + if ($result instanceof CacheItemInterface) { + $data['cache.hit'] = $result->isHit(); + if ($result->isHit()) { + $data['cache.item_size'] = static::getCacheItemSize($result->get()); + } + } + + $namespace = $this->getCacheNamespace(); + if (null !== $namespace) { + $data['cache.namespace'] = $namespace; } - $data = ['cache.hit' => $result->isHit()]; - if ($result->isHit()) { - $data['cache.item_size'] = static::getCacheItemSize($result->get()); + if ([] !== $data) { + $span->setData($data); } - $span->setData($data); return $result; } finally { @@ -282,10 +289,15 @@ private function traceGet(string $key, callable $callback, ?float $beta = null, $now = microtime(true); - $getSpan->setData([ + $getData = [ 'cache.hit' => !$wasMiss, 'cache.item_size' => self::getCacheItemSize($value), - ]); + ]; + $namespace = $this->getCacheNamespace(); + if (null !== $namespace) { + $getData['cache.namespace'] = $namespace; + } + $getSpan->setData($getData); // If we got a timestamp here we know that we missed if (null !== $saveStartTimestamp) { @@ -296,9 +308,13 @@ private function traceGet(string $key, callable $callback, ?float $beta = null, ->setDescription(urldecode($key)); $saveSpan = $parentSpan->startChild($saveContext); $saveSpan->setStartTimestamp($saveStartTimestamp); - $saveSpan->setData([ + $saveData = [ 'cache.item_size' => self::getCacheItemSize($value), - ]); + ]; + if (null !== $namespace) { + $saveData['cache.namespace'] = $namespace; + } + $saveSpan->setData($saveData); $saveSpan->finish($now); } else { $getSpan->finish(); @@ -343,4 +359,16 @@ private function setCallbackWrapper(callable $callback, string $key): callable return $callback($this->decoratedAdapter->getItem($key)); }; } + + /** + * @return string|null + */ + protected function getCacheNamespace(): ?string + { + if (!\property_exists($this, 'namespace')) { + return null; + } + + return $this->namespace; + } } diff --git a/src/Tracing/Cache/TraceableTagAwareCacheAdapterForV3WithNamespace.php b/src/Tracing/Cache/TraceableTagAwareCacheAdapterForV3WithNamespace.php index a7ce3300..92f0b1fa 100644 --- a/src/Tracing/Cache/TraceableTagAwareCacheAdapterForV3WithNamespace.php +++ b/src/Tracing/Cache/TraceableTagAwareCacheAdapterForV3WithNamespace.php @@ -24,6 +24,11 @@ final class TraceableTagAwareCacheAdapterForV3WithNamespace implements TagAwareA */ use TraceableCacheAdapterTrait; + /** + * @var string|null + */ + private $namespace; + /** * @param HubInterface $hub The current hub * @param TagAwareAdapterInterface $decoratedAdapter The decorated cache adapter @@ -62,6 +67,9 @@ public function withSubNamespace(string $namespace): static $clone = clone $this; $clone->decoratedAdapter = $this->decoratedAdapter->withSubNamespace($namespace); + $clone->namespace = null === $this->namespace + ? $namespace + : \sprintf('%s.%s', $this->namespace, $namespace); return $clone; } diff --git a/tests/Tracing/Cache/TraceableTagAwareCacheAdapterTest.php b/tests/Tracing/Cache/TraceableTagAwareCacheAdapterTest.php index 0503dca6..4f8a5204 100644 --- a/tests/Tracing/Cache/TraceableTagAwareCacheAdapterTest.php +++ b/tests/Tracing/Cache/TraceableTagAwareCacheAdapterTest.php @@ -9,15 +9,11 @@ use Sentry\Tracing\Transaction; use Sentry\Tracing\TransactionContext; use Symfony\Component\Cache\Adapter\AdapterInterface; +use Symfony\Component\Cache\Adapter\ArrayAdapter; +use Symfony\Component\Cache\Adapter\TagAwareAdapter; use Symfony\Component\Cache\Adapter\TagAwareAdapterInterface; use Symfony\Contracts\Cache\NamespacedPoolInterface; -if (interface_exists(NamespacedPoolInterface::class)) { - interface TagAwareNamespacedPoolInterface extends TagAwareAdapterInterface, NamespacedPoolInterface - { - } -} - /** * @phpstan-extends AbstractTraceableCacheAdapterTest */ @@ -71,14 +67,8 @@ public function testWithSubNamespaceReturnsNamespacedAdapter(): void $this->markTestSkipped('Namespaced caches are not supported by this Symfony version.'); } - $decoratedAdapter = $this->createMock(TagAwareNamespacedPoolInterface::class); - $namespacedAdapter = $this->createMock(TagAwareNamespacedPoolInterface::class); - - $decoratedAdapter - ->expects($this->once()) - ->method('withSubNamespace') - ->with('foo') - ->willReturn($namespacedAdapter); + $decoratedAdapter = new TagAwareAdapter(new ArrayAdapter(), new ArrayAdapter()); + $namespacedAdapter = $decoratedAdapter->withSubNamespace('foo'); $adapter = new TraceableTagAwareCacheAdapterForV3WithNamespace($this->hub, $decoratedAdapter); @@ -90,7 +80,63 @@ public function testWithSubNamespaceReturnsNamespacedAdapter(): void $ref = new \ReflectionProperty($result, 'decoratedAdapter'); $ref->setAccessible(true); - $this->assertSame($namespacedAdapter, $ref->getValue($result)); + $this->assertEquals($namespacedAdapter, $ref->getValue($result)); + } + + public function testNamespaceIsAddedToSpanData(): void + { + if (!interface_exists(NamespacedPoolInterface::class)) { + $this->markTestSkipped('Namespaced caches are not supported by this Symfony version.'); + } + + $transaction = new Transaction(new TransactionContext(), $this->hub); + $transaction->initSpanRecorder(); + + $this->hub->expects($this->any()) + ->method('getSpan') + ->willReturn($transaction); + + $decoratedAdapter = new TagAwareAdapter(new ArrayAdapter(), new ArrayAdapter()); + $adapter = new TraceableTagAwareCacheAdapterForV3WithNamespace($this->hub, $decoratedAdapter); + + $namespaced = $adapter->withSubNamespace('foo')->withSubNamespace('bar'); + + $namespaced->delete('example'); + $this->assertNotNull($transaction->getSpanRecorder()); + + $spans = $transaction->getSpanRecorder()->getSpans(); + + $this->assertCount(2, $spans); + $this->assertSame('cache.remove', $spans[1]->getOp()); + $this->assertSame('foo.bar', $spans[1]->getData()['cache.namespace']); + } + + public function testSingleNamespaceIsAddedToSpanData(): void + { + if (!interface_exists(NamespacedPoolInterface::class)) { + $this->markTestSkipped('Namespaced caches are not supported by this Symfony version.'); + } + + $transaction = new Transaction(new TransactionContext(), $this->hub); + $transaction->initSpanRecorder(); + + $this->hub->expects($this->any()) + ->method('getSpan') + ->willReturn($transaction); + + $decoratedAdapter = new TagAwareAdapter(new ArrayAdapter(), new ArrayAdapter()); + $adapter = new TraceableTagAwareCacheAdapterForV3WithNamespace($this->hub, $decoratedAdapter); + + $namespaced = $adapter->withSubNamespace('foo'); + + $namespaced->delete('example'); + $this->assertNotNull($transaction->getSpanRecorder()); + + $spans = $transaction->getSpanRecorder()->getSpans(); + + $this->assertCount(2, $spans); + $this->assertSame('cache.remove', $spans[1]->getOp()); + $this->assertSame('foo', $spans[1]->getData()['cache.namespace']); } /** From 5b264eeedfb51aaa1413f32b4bee683c5936c3cb Mon Sep 17 00:00:00 2001 From: Martin Linzmayer Date: Thu, 11 Dec 2025 11:46:48 +0100 Subject: [PATCH 08/10] wip --- .../Cache/TraceableCacheAdapterForV3WithNamespace.php | 5 ----- src/Tracing/Cache/TraceableCacheAdapterTrait.php | 9 +++++---- .../TraceableTagAwareCacheAdapterForV3WithNamespace.php | 5 ----- 3 files changed, 5 insertions(+), 14 deletions(-) diff --git a/src/Tracing/Cache/TraceableCacheAdapterForV3WithNamespace.php b/src/Tracing/Cache/TraceableCacheAdapterForV3WithNamespace.php index 97d51a97..74417394 100644 --- a/src/Tracing/Cache/TraceableCacheAdapterForV3WithNamespace.php +++ b/src/Tracing/Cache/TraceableCacheAdapterForV3WithNamespace.php @@ -24,11 +24,6 @@ final class TraceableCacheAdapterForV3WithNamespace implements AdapterInterface, */ use TraceableCacheAdapterTrait; - /** - * @var string|null - */ - private $namespace; - /** * @param HubInterface $hub The current hub * @param AdapterInterface $decoratedAdapter The decorated cache adapter diff --git a/src/Tracing/Cache/TraceableCacheAdapterTrait.php b/src/Tracing/Cache/TraceableCacheAdapterTrait.php index a3b03e5d..43620daa 100644 --- a/src/Tracing/Cache/TraceableCacheAdapterTrait.php +++ b/src/Tracing/Cache/TraceableCacheAdapterTrait.php @@ -34,6 +34,11 @@ trait TraceableCacheAdapterTrait */ private $decoratedAdapter; + /** + * @var string|null + */ + protected $namespace; + /** * {@inheritdoc} */ @@ -365,10 +370,6 @@ private function setCallbackWrapper(callable $callback, string $key): callable */ protected function getCacheNamespace(): ?string { - if (!\property_exists($this, 'namespace')) { - return null; - } - return $this->namespace; } } diff --git a/src/Tracing/Cache/TraceableTagAwareCacheAdapterForV3WithNamespace.php b/src/Tracing/Cache/TraceableTagAwareCacheAdapterForV3WithNamespace.php index 92f0b1fa..62535279 100644 --- a/src/Tracing/Cache/TraceableTagAwareCacheAdapterForV3WithNamespace.php +++ b/src/Tracing/Cache/TraceableTagAwareCacheAdapterForV3WithNamespace.php @@ -24,11 +24,6 @@ final class TraceableTagAwareCacheAdapterForV3WithNamespace implements TagAwareA */ use TraceableCacheAdapterTrait; - /** - * @var string|null - */ - private $namespace; - /** * @param HubInterface $hub The current hub * @param TagAwareAdapterInterface $decoratedAdapter The decorated cache adapter From b757490bb2d35b2ce9ddf9b97d1c03bc1e54026d Mon Sep 17 00:00:00 2001 From: Martin Linzmayer Date: Thu, 11 Dec 2025 11:54:27 +0100 Subject: [PATCH 09/10] test guard --- .../Tracing/Cache/TraceableTagAwareCacheAdapterTest.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/Tracing/Cache/TraceableTagAwareCacheAdapterTest.php b/tests/Tracing/Cache/TraceableTagAwareCacheAdapterTest.php index 4f8a5204..481d0eea 100644 --- a/tests/Tracing/Cache/TraceableTagAwareCacheAdapterTest.php +++ b/tests/Tracing/Cache/TraceableTagAwareCacheAdapterTest.php @@ -68,6 +68,9 @@ public function testWithSubNamespaceReturnsNamespacedAdapter(): void } $decoratedAdapter = new TagAwareAdapter(new ArrayAdapter(), new ArrayAdapter()); + if (!\method_exists($decoratedAdapter, 'withSubNamespace')) { + $this->markTestSkipped('TagAwareAdapter::withSubNamespace() is not available in this Symfony version.'); + } $namespacedAdapter = $decoratedAdapter->withSubNamespace('foo'); $adapter = new TraceableTagAwareCacheAdapterForV3WithNamespace($this->hub, $decoratedAdapter); @@ -97,6 +100,9 @@ public function testNamespaceIsAddedToSpanData(): void ->willReturn($transaction); $decoratedAdapter = new TagAwareAdapter(new ArrayAdapter(), new ArrayAdapter()); + if (!\method_exists($decoratedAdapter, 'withSubNamespace')) { + $this->markTestSkipped('TagAwareAdapter::withSubNamespace() is not available in this Symfony version.'); + } $adapter = new TraceableTagAwareCacheAdapterForV3WithNamespace($this->hub, $decoratedAdapter); $namespaced = $adapter->withSubNamespace('foo')->withSubNamespace('bar'); @@ -125,6 +131,9 @@ public function testSingleNamespaceIsAddedToSpanData(): void ->willReturn($transaction); $decoratedAdapter = new TagAwareAdapter(new ArrayAdapter(), new ArrayAdapter()); + if (!\method_exists($decoratedAdapter, 'withSubNamespace')) { + $this->markTestSkipped('TagAwareAdapter::withSubNamespace() is not available in this Symfony version.'); + } $adapter = new TraceableTagAwareCacheAdapterForV3WithNamespace($this->hub, $decoratedAdapter); $namespaced = $adapter->withSubNamespace('foo'); From 0fb15819293462992ee3353fc2efed5030edc143 Mon Sep 17 00:00:00 2001 From: Martin Linzmayer Date: Thu, 11 Dec 2025 11:55:24 +0100 Subject: [PATCH 10/10] CS --- tests/Tracing/Cache/TraceableTagAwareCacheAdapterTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Tracing/Cache/TraceableTagAwareCacheAdapterTest.php b/tests/Tracing/Cache/TraceableTagAwareCacheAdapterTest.php index 481d0eea..f15b66eb 100644 --- a/tests/Tracing/Cache/TraceableTagAwareCacheAdapterTest.php +++ b/tests/Tracing/Cache/TraceableTagAwareCacheAdapterTest.php @@ -68,7 +68,7 @@ public function testWithSubNamespaceReturnsNamespacedAdapter(): void } $decoratedAdapter = new TagAwareAdapter(new ArrayAdapter(), new ArrayAdapter()); - if (!\method_exists($decoratedAdapter, 'withSubNamespace')) { + if (!method_exists($decoratedAdapter, 'withSubNamespace')) { $this->markTestSkipped('TagAwareAdapter::withSubNamespace() is not available in this Symfony version.'); } $namespacedAdapter = $decoratedAdapter->withSubNamespace('foo'); @@ -100,7 +100,7 @@ public function testNamespaceIsAddedToSpanData(): void ->willReturn($transaction); $decoratedAdapter = new TagAwareAdapter(new ArrayAdapter(), new ArrayAdapter()); - if (!\method_exists($decoratedAdapter, 'withSubNamespace')) { + if (!method_exists($decoratedAdapter, 'withSubNamespace')) { $this->markTestSkipped('TagAwareAdapter::withSubNamespace() is not available in this Symfony version.'); } $adapter = new TraceableTagAwareCacheAdapterForV3WithNamespace($this->hub, $decoratedAdapter); @@ -131,7 +131,7 @@ public function testSingleNamespaceIsAddedToSpanData(): void ->willReturn($transaction); $decoratedAdapter = new TagAwareAdapter(new ArrayAdapter(), new ArrayAdapter()); - if (!\method_exists($decoratedAdapter, 'withSubNamespace')) { + if (!method_exists($decoratedAdapter, 'withSubNamespace')) { $this->markTestSkipped('TagAwareAdapter::withSubNamespace() is not available in this Symfony version.'); } $adapter = new TraceableTagAwareCacheAdapterForV3WithNamespace($this->hub, $decoratedAdapter);