ApcuCache.php

PHP

Path: src/Cache/ApcuCache.php

<?php

namespace mini\Cache;

use Psr\SimpleCache\CacheInterface;

/**
 * APCu-backed PSR-16 SimpleCache implementation
 *
 * Stores cache data in APCu (user cache).
 * Requires APCu extension to be installed and enabled.
 */
class ApcuCache implements CacheInterface
{
    private string $prefix;

    public function __construct(string $prefix = 'mini:')
    {
        $this->prefix = $prefix;
    }

    /**
     * Prefix key to avoid collisions
     */
    private function prefixKey(string $key): string
    {
        return $this->prefix . $key;
    }

    /**
     * Validate cache key
     */
    private function validateKey(string $key): void
    {
        if (empty($key)) {
            throw new \InvalidArgumentException('Cache key cannot be empty');
        }

        // PSR-16 specifies these characters are not allowed: {}()/\@
        if (preg_match('/[{}()\/@\\\]/', $key)) {
            throw new \InvalidArgumentException('Cache key contains invalid characters: ' . $key);
        }
    }

    /**
     * Calculate TTL in seconds
     */
    private function calculateTtl(null|int|\DateInterval $ttl): int
    {
        if ($ttl === null) {
            return 0; // No expiration
        }

        if ($ttl instanceof \DateInterval) {
            $now = new \DateTime();
            $expires = $now->add($ttl);
            return $expires->getTimestamp() - time();
        }

        return $ttl;
    }

    public function get(string $key, mixed $default = null): mixed
    {
        $this->validateKey($key);
        $value = \apcu_fetch($this->prefixKey($key), $success);
        return $success ? $value : $default;
    }

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

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

    public function clear(): bool
    {
        // APCu doesn't support clearing by prefix, so we clear everything
        // This is a limitation but acceptable for development
        return \apcu_clear_cache();
    }

    public function getMultiple(iterable $keys, mixed $default = null): iterable
    {
        $result = [];
        foreach ($keys as $key) {
            $result[$key] = $this->get($key, $default);
        }
        return $result;
    }

    public function setMultiple(iterable $values, null|int|\DateInterval $ttl = null): bool
    {
        $success = true;
        foreach ($values as $key => $value) {
            if (!$this->set($key, $value, $ttl)) {
                $success = false;
            }
        }
        return $success;
    }

    public function deleteMultiple(iterable $keys): bool
    {
        $success = true;
        foreach ($keys as $key) {
            if (!$this->delete($key)) {
                $success = false;
            }
        }
        return $success;
    }

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