Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
55 / 55
100.00% covered (success)
100.00%
11 / 11
CRAP
100.00% covered (success)
100.00%
1 / 1
CacheItemPool
100.00% covered (success)
100.00%
55 / 55
100.00% covered (success)
100.00%
11 / 11
25
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __destruct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 getItem
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 getItems
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 hasItem
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 deleteItem
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 deleteItems
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 clear
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 save
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
4
 saveDeferred
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 commit
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2declare(strict_types=1);
3/**
4 * Pop PHP Framework (https://www.popphp.org/)
5 *
6 * @link       https://github.com/popphp/popphp-framework
7 * @author     Nick Sagona, III <nick@popphp.org>
8 * @copyright  Copyright (c) 2009-2026 Nick Sagona, III
9 * @license    https://www.popphp.org/license     New BSD License
10 */
11
12/**
13 * @namespace
14 */
15namespace Pop\Cache\Psr6;
16
17use Pop\Cache\Adapter;
18use Pop\Cache\InvalidArgumentException;
19use Pop\Cache\ValidatesKey;
20
21/**
22 * PSR-6 cache item pool class
23 *
24 * @category   Pop
25 * @package    Pop\Cache
26 * @author     Nick Sagona, III <nick@popphp.org>
27 * @copyright  Copyright (c) 2009-2026 Nick Sagona, III
28 * @license    https://www.popphp.org/license     New BSD License
29 * @version    5.0.0
30 */
31class CacheItemPool implements \Psr\Cache\CacheItemPoolInterface
32{
33
34    /**
35     * Traits
36     */
37    use ValidatesKey;
38
39    /**
40     * Cache adapter
41     * @var Adapter\AdapterInterface
42     */
43    protected Adapter\AdapterInterface $adapter;
44
45    /**
46     * Deferred cache items, keyed by item key
47     * @var array
48     */
49    protected array $deferred = [];
50
51    /**
52     * Constructor
53     *
54     * Instantiate the cache item pool object
55     *
56     * @param  Adapter\AdapterInterface $adapter
57     */
58    public function __construct(Adapter\AdapterInterface $adapter)
59    {
60        $this->adapter = $adapter;
61    }
62
63    /**
64     * Destructor
65     *
66     * Ensure any deferred cache items are persisted before the pool is destroyed, per PSR-6 ยง1.4
67     * ("A Pool MUST ensure that any deferred cache items are eventually persisted"). A destructor
68     * must never let an exception escape, so failures here are swallowed.
69     *
70     * @return void
71     */
72    public function __destruct()
73    {
74        try {
75            $this->commit();
76        } catch (\Throwable $e) {
77            // Destructors must not throw
78        }
79    }
80
81    /**
82     * Get a cache item by key
83     *
84     * @param  string $key
85     * @throws InvalidArgumentException
86     * @return CacheItem
87     */
88    public function getItem(string $key): CacheItem
89    {
90        $this->validateKey($key);
91
92        if (isset($this->deferred[$key])) {
93            return $this->deferred[$key];
94        }
95
96        $miss  = new \stdClass();
97        $value = $this->adapter->getItem($key, $miss);
98
99        return ($value === $miss) ? new CacheItem($key, null, false) : new CacheItem($key, $value, true);
100    }
101
102    /**
103     * Get multiple cache items by their keys
104     *
105     * @param  array $keys
106     * @throws InvalidArgumentException
107     * @return iterable
108     */
109    public function getItems(array $keys = []): iterable
110    {
111        foreach ($keys as $key) {
112            $this->validateKey($key);
113        }
114
115        $items = [];
116        foreach ($keys as $key) {
117            $items[$key] = $this->getItem($key);
118        }
119
120        return $items;
121    }
122
123    /**
124     * Determine if the cache contains an item for the given key
125     *
126     * @param  string $key
127     * @throws InvalidArgumentException
128     * @return bool
129     */
130    public function hasItem(string $key): bool
131    {
132        $this->validateKey($key);
133
134        if (isset($this->deferred[$key])) {
135            return true;
136        }
137
138        return $this->adapter->hasItem($key);
139    }
140
141    /**
142     * Delete an item from the cache
143     *
144     * @param  string $key
145     * @throws InvalidArgumentException
146     * @return bool
147     */
148    public function deleteItem(string $key): bool
149    {
150        $this->validateKey($key);
151        $this->adapter->deleteItem($key);
152        unset($this->deferred[$key]);
153
154        return true;
155    }
156
157    /**
158     * Delete multiple items from the cache
159     *
160     * @param  array $keys
161     * @throws InvalidArgumentException
162     * @return bool
163     */
164    public function deleteItems(array $keys): bool
165    {
166        foreach ($keys as $key) {
167            $this->validateKey($key);
168        }
169
170        foreach ($keys as $key) {
171            $this->deleteItem($key);
172        }
173
174        return true;
175    }
176
177    /**
178     * Clear all stored values from cache
179     *
180     * @return bool
181     */
182    public function clear(): bool
183    {
184        $this->adapter->clear();
185        $this->deferred = [];
186
187        return true;
188    }
189
190    /**
191     * Persist a cache item immediately
192     *
193     * A resolved expiration of zero or negative seconds deletes the item instead of caching it
194     * permanently, since the underlying adapter's saveItem() treats a TTL of 0 as "never expires" โ€”
195     * the opposite of what an already-expired item should do.
196     *
197     * @param  \Psr\Cache\CacheItemInterface $item
198     * @throws InvalidArgumentException
199     * @return bool
200     */
201    public function save(\Psr\Cache\CacheItemInterface $item): bool
202    {
203        if (!($item instanceof CacheItem)) {
204            throw new InvalidArgumentException(
205                'Error: The cache item must be an instance of ' . CacheItem::class . '.'
206            );
207        }
208
209        $this->validateKey($item->getKey());
210
211        $seconds = $item->getExpirationSeconds();
212
213        if (($seconds !== null) && ($seconds <= 0)) {
214            $this->adapter->deleteItem($item->getKey());
215        } else {
216            $this->adapter->saveItem($item->getKey(), $item->get(), $seconds);
217        }
218
219        unset($this->deferred[$item->getKey()]);
220
221        return true;
222    }
223
224    /**
225     * Queue a cache item to be persisted later
226     *
227     * @param  \Psr\Cache\CacheItemInterface $item
228     * @throws InvalidArgumentException
229     * @return bool
230     */
231    public function saveDeferred(\Psr\Cache\CacheItemInterface $item): bool
232    {
233        if (!($item instanceof CacheItem)) {
234            throw new InvalidArgumentException(
235                'Error: The cache item must be an instance of ' . CacheItem::class . '.'
236            );
237        }
238
239        $this->validateKey($item->getKey());
240        $this->deferred[$item->getKey()] = $item;
241
242        return true;
243    }
244
245    /**
246     * Persist all deferred cache items
247     *
248     * @return bool
249     */
250    public function commit(): bool
251    {
252        $result = true;
253        $items  = $this->deferred;
254
255        foreach ($items as $item) {
256            $result = $this->save($item) && $result;
257        }
258
259        $this->deferred = [];
260
261        return $result;
262    }
263
264}