From 1a93c74815a21896fd07802cd76a87936da71db9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Wed, 26 Aug 2026 09:45:49 +0200 Subject: [PATCH] fix(cache): resolve all cached brands before deleting collection key in BrandCache reset (F-2026-0002) --- src/Infra/Cache/BrandCache.php | 10 ++++++++-- storage/auditor-findings.json | 6 +++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/Infra/Cache/BrandCache.php b/src/Infra/Cache/BrandCache.php index 217c6e0..3a2c811 100644 --- a/src/Infra/Cache/BrandCache.php +++ b/src/Infra/Cache/BrandCache.php @@ -9,6 +9,7 @@ use Domain\Models\ValueObjects\Brand\ArcadiaId; use Domain\Models\ValueObjects\Brand\BrandCode; use Domain\Models\ValueObjects\Brand\BrandId; use Domain\Models\ValueObjects\Brand\BrandName; +use Psr\Log\LoggerInterface; use Psr\SimpleCache\CacheInterface; use Ramsey\Uuid\Uuid; @@ -20,7 +21,7 @@ final readonly class BrandCache public function __construct( private CacheInterface $cacheStore, - private \Psr\Log\LoggerInterface $logger, + private LoggerInterface $logger, ) { } @@ -30,6 +31,7 @@ final readonly class BrandCache return $this->fromCacheData($this->cacheStore->get($this->idKey($id))); } catch (\Throwable $e) { $this->logger->warning('BrandCache::getById failed', ['exception' => $e->getMessage()]); + return null; } } @@ -40,6 +42,7 @@ final readonly class BrandCache return $this->fromCacheData($this->cacheStore->get($this->codeKey($code))); } catch (\Throwable $e) { $this->logger->warning('BrandCache::getByCode failed', ['exception' => $e->getMessage()]); + return null; } } @@ -50,6 +53,7 @@ final readonly class BrandCache return $this->fromCacheData($this->cacheStore->get($this->arcadiaKey($arcadiaId))); } catch (\Throwable $e) { $this->logger->warning('BrandCache::getByArcadiaId failed', ['exception' => $e->getMessage()]); + return null; } } @@ -81,6 +85,7 @@ final readonly class BrandCache return $brands; } catch (\Throwable $e) { $this->logger->warning('BrandCache::getAll failed', ['exception' => $e->getMessage()]); + return null; } } @@ -127,9 +132,10 @@ final readonly class BrandCache public function reset(): void { try { + $all = $this->getAll(); + $this->cacheStore->delete(self::KEY_ALL); - $all = $this->getAll(); if ($all !== null) { foreach ($all as $brand) { $this->cacheStore->delete($this->idKey($brand->id())); diff --git a/storage/auditor-findings.json b/storage/auditor-findings.json index 3e1861f..83de34d 100644 --- a/storage/auditor-findings.json +++ b/storage/auditor-findings.json @@ -46,7 +46,7 @@ "domain": "conventions", "severity": "medium", "confidence": "confirmed", - "status": "open", + "status": "fixed", "summary": "BrandCache::reset() deletes KEY_ALL before calling getAll(), which causes getAll() to immediately return null and leaves individual brand cache entries intact.", "why_it_matters": "When BrandCreated or BrandRenamed domain events fire, BrandCache::reset() fails to invalidate individual brand cache entries (brand:id:*, brand:code:*, brand:arcadia:*), serving stale brand data for up to 24 hours.", "evidence": [ @@ -64,8 +64,8 @@ ], "symbol": "Infra\\Cache\\BrandCache::reset", "recommendation": "Retrieve $all = $this->getAll() before deleting self::KEY_ALL so individual brand keys can be successfully resolved and deleted.", - "remediation": "In BrandCache::reset(), move $all = $this->getAll() to the beginning of the method before $this->cacheStore->delete(self::KEY_ALL).", - "verification_notes": "Confirmed in BrandCache.php: lines 130-134 clearly show self::KEY_ALL being deleted prior to getAll() invocation.", + "remediation": "In BrandCache::reset(), moved $all = $this->getAll() before $this->cacheStore->delete(self::KEY_ALL).", + "verification_notes": "Fixed in BrandCache.php and verified with BrandCacheTest.", "metadata": { "priority": "p1", "subsystem": "CFG"