NamespacedCache.php

PHP

Path: src/Cache/NamespacedCache.php

<?php

namespace mini\Cache;

use Psr\SimpleCache\CacheInterface;

/**
 * Namespaced cache proxy
 *
 * Wraps another cache implementation and prefixes all keys with a namespace.
 * This allows logical separation of cache entries without separate cache instances.
 */
class NamespacedCache implements CacheInterface
{
    private \Psr\SimpleCache\CacheInterface $cache;
    private string $namespace;
    private string $separator;

    public function __construct(CacheInterface $cache, string $namespace, string $separator = ':')
    {
        $this->cache = $cache;
        $this->namespace = $namespace;
        $this->separator = $separator;
    }

    /**
     * Prefix key with namespace
     */
    private function prefixKey(string $key): string
    {
        return $this->namespace . $this->separator . $key;
    }

    /**
     * Prefix multiple keys with namespace
     */
    private function prefixKeys(iterable $keys): array
    {
        $prefixedKeys = [];
        foreach ($keys as $key) {
            $prefixedKeys[] = $this->prefixKey($key);
        }
        return $prefixedKeys;
    }

    /**
     * Remove namespace prefix from results
     */
    private function unprefixResults(iterable $results): array
    {
        $unprefixed = [];
        $prefixLength = strlen($this->namespace . $this->separator);

        foreach ($results as $key => $value) {
            $originalKey = substr($key, $prefixLength);
            $unprefixed[$originalKey] = $value;
        }

        return $unprefixed;
    }

    public function get(string $key, mixed $default = null): mixed
    {
        return $this->cache->get($this->prefixKey($key), $default);
    }

    public function set(string $key, mixed $value, null|int|\DateInterval $ttl = null): bool
    {
        return $this->cache->set($this->prefixKey($key), $value, $ttl);
    }

    public function delete(string $key): bool
    {
        return $this->cache->delete($this->prefixKey($key));
    }

    public function clear(): bool
    {
        // For namespaced cache, we can't clear everything as that would affect other namespaces
        // Instead, we would need to delete all keys with our prefix
        // This is a limitation of the simple approach, but keeps it lightweight

        // Note: This is not a full implementation as it would require scanning all keys
        // For a production system, you might want to track namespaced keys separately
        throw new \LogicException('Clear operation not supported on namespaced cache. Use the root cache instance to clear all entries.');
    }

    public function getMultiple(iterable $keys, mixed $default = null): iterable
    {
        $prefixedKeys = $this->prefixKeys($keys);
        $results = $this->cache->getMultiple($prefixedKeys, $default);

        // Map results back to original keys
        $mappedResults = [];
        $originalKeys = is_array($keys) ? $keys : iterator_to_array($keys);
        $prefixLength = strlen($this->namespace . $this->separator);

        foreach ($results as $prefixedKey => $value) {
            $originalKey = substr($prefixedKey, $prefixLength);
            $mappedResults[$originalKey] = $value;
        }

        return $mappedResults;
    }

    public function setMultiple(iterable $values, null|int|\DateInterval $ttl = null): bool
    {
        $prefixedValues = [];
        foreach ($values as $key => $value) {
            $prefixedValues[$this->prefixKey($key)] = $value;
        }

        return $this->cache->setMultiple($prefixedValues, $ttl);
    }

    public function deleteMultiple(iterable $keys): bool
    {
        $prefixedKeys = $this->prefixKeys($keys);
        return $this->cache->deleteMultiple($prefixedKeys);
    }

    public function has(string $key): bool
    {
        return $this->cache->has($this->prefixKey($key));
    }

    /**
     * Get the namespace for this cache instance
     */
    public function getNamespace(): string
    {
        return $this->namespace;
    }

    /**
     * Get the underlying cache instance
     */
    public function getUnderlyingCache(): \Psr\SimpleCache\CacheInterface
    {
        return $this->cache;
    }
}