Lines 89.65% 26 / 29
Methods 62.50% 5 / 8
Classes 0.00% 0 / 1
Name Lines Methods CRAP
 setDefaultTtl 100.00% 1 / 1 100.00% 1 / 1 1
 getDefaultTtl 100.00% 1 / 1 100.00% 1 / 1 1
 resolveTtl 100.00% 1 / 1 100.00% 1 / 1 1
 validateKey 100.00% 2 / 2 100.00% 1 / 1 3
 expiryFromTtl 85.71% 6 / 7 0.00% 0 / 1 4.05
 getMultiple 100.00% 5 / 5 100.00% 1 / 1 2
 setMultiple 83.33% 5 / 6 0.00% 0 / 1 3.04
 deleteMultiple 83.33% 5 / 6 0.00% 0 / 1 3.04
27abstract class Cache implements CacheInterface
28{
29    /**
30     * Default TTL in seconds applied when a {@see set()} call omits one.
31     *
32     * `null` means "no default" — an omitted TTL caches forever, matching the
33     * PSR-16 semantics. Set via {@see setDefaultTtl()}.
34     *
35     * @var int|null
36     */
37    private ?int $defaultTtl = null;
38
39    /**
40     * Set the default TTL applied when a {@see set()} call omits one.
41     *
42     * @param int|null $ttl Default TTL in seconds, or null for no default
43     * @return void
44     */
45    public function setDefaultTtl(?int $ttl): void
46    {
47        $this->defaultTtl = $ttl;
48    }
49
50    /**
51     * Get the configured default TTL.
52     *
53     * @return int|null Default TTL in seconds, or null when none is set
54     */
55    public function getDefaultTtl(): ?int
56    {
57        return $this->defaultTtl;
58    }
59
60    /**
61     * Resolve the effective TTL for a {@see set()} call.
62     *
63     * Returns the caller-supplied TTL, or the configured default when none
64     * was given.
65     *
66     * @param null|int|DateInterval $ttl The caller-supplied TTL
67     * @return null|int|DateInterval The effective TTL
68     */
69    protected function resolveTtl(null|int|DateInterval $ttl): null|int|DateInterval
70    {
71        return $ttl ?? $this->defaultTtl;
72    }
73
74    /**
75     * Validate a cache key.
76     *
77     * Keys must be strings; anything else is rejected so callers get a
78     * consistent {@see InvalidArgumentException} rather than a TypeError.
79     *
80     * The 512-character cap is well above the PSR-16 minimum of 64 and is
81     * bounded to keep cache metadata from being abused. The character set
82     * `[A-Za-z0-9_.]` matches the PSR-16 required set; the reserved
83     * characters `{}()/\@:` are rejected.
84     *
85     * @param mixed $key The key to validate
86     * @return void
87     * @throws InvalidArgumentException If the key is not a legal value
88     */
89    protected function validateKey(mixed $key): void
90    {
91        if (!is_string($key) || preg_match('/^[A-Za-z0-9_.]{1,512}$/', $key) !== 1) {
92            throw new InvalidArgumentException($key);
93        }
94    }
95
96    /**
97     * Convert a TTL into an absolute expiry timestamp.
98     *
99     * @param null|int|DateInterval $ttl The TTL to convert
100     * @return int|null Absolute unix timestamp of expiry, or null for "forever"
101     */
102    protected function expiryFromTtl(null|int|DateInterval $ttl): ?int
103    {
104        if ($ttl === null) {
105            return null;
106        }
107
108        if ($ttl instanceof DateInterval) {
109            $ttl = (new \DateTimeImmutable())->add($ttl)->getTimestamp() - time();
110        }
111
112        if ($ttl <= 0) {
113            // Already expired: return a timestamp in the past so the item is
114            // treated as stale and removed rather than stored.
115            return time() - 1;
116        }
117
118        return time() + $ttl;
119    }
120
121    /**
122     * @inheritDoc
123     */
124    public function getMultiple(iterable $keys, mixed $default = null): iterable
125    {
126        $result = [];
127
128        foreach ($keys as $key) {
129            $this->validateKey($key);
130            $result[$key] = $this->get($key, $default);
131        }
132
133        return $result;
134    }
135
136    /**
137     * @inheritDoc
138     */
139    public function setMultiple(iterable $values, null|int|DateInterval $ttl = null): bool
140    {
141        $success = true;
142
143        foreach ($values as $key => $value) {
144            $this->validateKey($key);
145            if (!$this->set($key, $value, $ttl)) {
146                $success = false;
147            }
148        }
149
150        return $success;
151    }
152
153    /**
154     * @inheritDoc
155     */
156    public function deleteMultiple(iterable $keys): bool
157    {
158        $success = true;
159
160        foreach ($keys as $key) {
161            $this->validateKey($key);
162            if (!$this->delete($key)) {
163                $success = false;
164            }
165        }
166
167        return $success;
168    }
169}