diff --git a/src/MonitorCacheKeys.php b/src/MonitorCacheKeys.php index 10be8d4..e928eb0 100644 --- a/src/MonitorCacheKeys.php +++ b/src/MonitorCacheKeys.php @@ -11,18 +11,17 @@ use Psr\Cache\InvalidArgumentException; /* We must not store the key-list item or values within this object, * because it can change from outside this object instance. */ final readonly class MonitorCacheKeys implements CacheItemPoolInterface { - /* TODO store clean/dirty status in the key-list, so that in PersistCache, - * we can only need to update/remove specific items that changed, - * instead of the full list */ private const KEY_LIST = '__key_list'; - private const IS_DIRTY = '__is_dirty'; + private const CHANGE_LIST = '__chg_list'; + public const UPDATED = 1; + public const REMOVED = 2; private CacheItemPoolInterface $cache; /** @throws InvalidArgumentException */ public function __construct(CacheItemPoolInterface $cache) { $this->cache = $cache; - $items = $cache->getItems([self::KEY_LIST, self::IS_DIRTY]); + $items = $cache->getItems([self::KEY_LIST, self::CHANGE_LIST]); foreach ($items as $item) { if ( ! $item->isHit()) { $this->initialize(); @@ -34,11 +33,11 @@ final readonly class MonitorCacheKeys implements CacheItemPoolInterface { /** @throws InvalidArgumentException */ private function initialize(): void { $keyList = $this->cache->getItem(self::KEY_LIST); - $isDirty = $this->cache->getItem(self::IS_DIRTY); + $changeList = $this->cache->getItem(self::CHANGE_LIST); $keyList->set([]); - $isDirty->set(false); + $changeList->set([]); $this->cache->saveDeferred($keyList); - $this->cache->saveDeferred($isDirty); + $this->cache->saveDeferred($changeList); $this->cache->commit(); } @@ -49,22 +48,24 @@ final readonly class MonitorCacheKeys implements CacheItemPoolInterface { } /** @throws InvalidArgumentException */ - public function isDirty(): bool { - $isDirty = $this->cache->getItem(self::IS_DIRTY); - return $isDirty->get() ?? false; + public function getChanges(): array { + $changeList = $this->cache->getItem(self::CHANGE_LIST); + return $changeList->get() ?? []; } /** @throws InvalidArgumentException */ public function markClean(): void { - $isDirty = $this->cache->getItem(self::IS_DIRTY); - $isDirty->set(false); - $this->cache->save($isDirty); + $changeList = $this->cache->getItem(self::CHANGE_LIST); + $changeList->set([]); + $this->cache->save($changeList); } public function getItem(string $key): CacheItemInterface { return $this->cache->getItem($key); } + /** @return CacheItemInterface[] + * @throws InvalidArgumentException */ public function getItems(array $keys = []): iterable { return $this->cache->getItems($keys); } @@ -86,20 +87,14 @@ final readonly class MonitorCacheKeys implements CacheItemPoolInterface { } public function deleteItem(string $key): bool { - if ($key === self::KEY_LIST || $key === self::IS_DIRTY) { - throw new OutOfBoundsException( - 'Can not delete the private key list or is dirty flag' - ); - } + $this->isValid($key); $keyList = $this->cache->getItem(self::KEY_LIST); - $isDirty = $this->cache->getItem(self::IS_DIRTY); $keyValues = $keyList->get(); if (isset($keyValues[$key])) { unset($keyValues[$key]); $keyList->set($keyValues); - $isDirty->set(true); $this->cache->saveDeferred($keyList); - $this->cache->saveDeferred($isDirty); + $this->logChange($key, MonitorCacheKeys::REMOVED); $this->cache->commit(); } @@ -107,25 +102,17 @@ final readonly class MonitorCacheKeys implements CacheItemPoolInterface { } public function deleteItems(array $keys): bool { - if (in_array(self::KEY_LIST, $keys, true) || - in_array(self::IS_DIRTY, $keys, true) - ) { - throw new OutOfBoundsException( - 'Can not delete the private key list or is dirty flag' - ); - } + $this->allValid($keys); $keyList = $this->cache->getItem(self::KEY_LIST); - $isDirty = $this->cache->getItem(self::IS_DIRTY); $keyValues = $keyList->get(); foreach ($keys as $key) { if (isset($keyValues[$key])) { unset($keyValues[$key]); - $isDirty->set(true); + $this->logChange($key, MonitorCacheKeys::REMOVED); } } $keyList->set($keyValues); $this->cache->saveDeferred($keyList); - $this->cache->saveDeferred($isDirty); $this->cache->commit(); return $this->cache->deleteItems($keys); @@ -143,25 +130,48 @@ final readonly class MonitorCacheKeys implements CacheItemPoolInterface { return $this->cache->saveDeferred($item); } - /** @throws InvalidArgumentException */ - private function update(CacheItemInterface $item) { - if ($item->getKey() === self::KEY_LIST || $item->getKey() === self::IS_DIRTY) { - throw new OutOfBoundsException( - 'Can not alter the private key list or is dirty flag' - ); - } - $keyList = $this->cache->getItem(self::KEY_LIST); - $isDirty = $this->cache->getItem(self::IS_DIRTY); - $keyValues = $keyList->get(); - $keyValues[$item->getKey()] = true; - $keyList->set($keyValues); - $isDirty->set(true); - $this->cache->saveDeferred($keyList); - $this->cache->saveDeferred($isDirty); - $this->cache->commit(); - } - public function commit(): bool { return $this->cache->commit(); } + + /** @throws InvalidArgumentException|OutOfBoundsException */ + private function update(CacheItemInterface $item) { + $this->isValid($item->getKey()); + $keyList = $this->cache->getItem(self::KEY_LIST); + $keyValues = $keyList->get(); + $keyValues[$item->getKey()] = true; + $keyList->set($keyValues); + $this->logChange($item->getKey()); + $this->cache->saveDeferred($keyList); + $this->cache->commit(); + } + + /** @throws OutOfBoundsException */ + private function isValid(string $key): void { + if ($key === self::KEY_LIST || $key === self::CHANGE_LIST) { + throw new OutOfBoundsException( + 'Can not modify the private key or change lists' + ); + } + } + + /** @throws OutOfBoundsException */ + private function allValid(array $keys): void { + if (in_array(self::KEY_LIST, $keys, true) || + in_array(self::CHANGE_LIST, $keys, true) + ) { + throw new OutOfBoundsException( + 'Can not modify the private key or change lists' + ); + } + } + + /** @throws InvalidArgumentException */ + private function logChange(string $key, int $code = MonitorCacheKeys::UPDATED): void { + $changeList = $this->cache->getItem(self::CHANGE_LIST); + $changeValues = $changeList->get(); + $changeValues[$key] = $code; + $changeList->set($changeValues); + $this->cache->saveDeferred($changeList); + } } diff --git a/src/PersistCache.php b/src/PersistCache.php index f637f55..9bc56d2 100644 --- a/src/PersistCache.php +++ b/src/PersistCache.php @@ -36,13 +36,17 @@ final readonly class PersistCache { /** @throws InvalidArgumentException */ public function persist(): void { - /* we only need to persist the caches if they contain changes */ - if ($this->sessionCache->isDirty()) { + /* we only need to persist the changes made to the cache (if any) */ + $changes = $this->sessionCache->getChanges(); + if ($changes) { $this->sessionCache->markClean(); - $items = $this->sessionCache->getItems($this->sessionCache->getKeys()); - $this->sessionStorage->clear(); + $items = $this->sessionCache->getItems(array_keys($changes)); foreach ($items as $item) { - $this->sessionStorage->saveDeferred($item); + if (($changes[$item->getKey()] ?? null) === MonitorCacheKeys::REMOVED) { + $this->sessionStorage->deleteItem($item->getKey()); + } else { + $this->sessionStorage->saveDeferred($item); + } } $this->sessionStorage->commit(); }