Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
97.96% covered (success)
97.96%
48 / 49
91.67% covered (success)
91.67%
11 / 12
CRAP
0.00% covered (danger)
0.00%
0 / 1
Apc
97.96% covered (success)
97.96%
48 / 49
91.67% covered (success)
91.67%
11 / 12
25
0.00% covered (danger)
0.00%
0 / 1
 __construct
75.00% covered (success)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
 getInfo
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getItemTtl
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 saveItem
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 getItem
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
6
 hasItem
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 deleteItem
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 clear
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 destroy
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 incrementItem
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
3
 decrementItem
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
3
 fetchVersion
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
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\Adapter;
16
17use Pop\Cache\Clock;
18
19/**
20 * APC cache adapter class
21 *
22 * @category   Pop
23 * @package    Pop\Cache
24 * @author     Nick Sagona, III <nick@popphp.org>
25 * @copyright  Copyright (c) 2009-2026 Nick Sagona, III
26 * @license    https://www.popphp.org/license     New BSD License
27 * @version    5.0.0
28 */
29class Apc extends AbstractAdapter
30{
31
32    /**
33     * Traits
34     */
35    use NamespacedVersionedKeys;
36
37    /**
38     * Cache namespace
39     * @var string
40     */
41    protected string $namespace = 'pop_cache';
42
43    /**
44     * Constructor
45     *
46     * Instantiate the APC cache object
47     *
48     * @param  int $ttl
49     * @param  string $namespace
50     * @param  Clock\ClockInterface $clock
51     * @throws Exception
52     */
53    public function __construct(
54        int $ttl = 0, string $namespace = 'pop_cache', Clock\ClockInterface $clock = new Clock\SystemClock()
55    )
56    {
57        parent::__construct($ttl, $clock);
58        if (!function_exists('apcu_cache_info')) {
59            throw new Exception('Error: APCu is not available.');
60        }
61        $this->namespace = $namespace;
62    }
63
64    /**
65     * Method to get the current APC info.
66     *
67     * @return array
68     */
69    public function getInfo(): array
70    {
71        return apcu_cache_info();
72    }
73
74    /**
75     * Get the time-to-live for an item in cache
76     *
77     * @param  string $id
78     * @param  int    $default
79     * @return int
80     */
81    public function getItemTtl(string $id, int $default = 0): int
82    {
83        $cacheValue = apcu_fetch($this->key($id));
84        $ttl        = $default;
85
86        if (is_array($cacheValue) && array_key_exists('ttl', $cacheValue)) {
87            $ttl = $cacheValue['ttl'];
88        }
89
90        return $ttl;
91    }
92
93    /**
94     * Save an item to cache
95     *
96     * @param  string $id
97     * @param  mixed  $value
98     * @param  ?int   $ttl
99     * @return Apc
100     */
101    public function saveItem(string $id, mixed $value, ?int $ttl = null): Apc
102    {
103        $cacheValue = [
104            'start' => $this->clock->now(),
105            'ttl'   => ($ttl !== null) ? $ttl : $this->ttl,
106            'value' => $value
107        ];
108
109        apcu_store($this->key($id), $cacheValue, $cacheValue['ttl']);
110
111        return $this;
112    }
113
114    /**
115     * Get an item from cache
116     *
117     * @param  string $id
118     * @param  mixed  $default
119     * @return mixed
120     */
121    public function getItem(string $id, mixed $default = false): mixed
122    {
123        $cacheValue = apcu_fetch($this->key($id));
124        $value      = $default;
125
126        if (is_array($cacheValue) && array_key_exists('start', $cacheValue) &&
127            array_key_exists('ttl', $cacheValue) && array_key_exists('value', $cacheValue)) {
128            if ($this->isFresh($cacheValue)) {
129                $value = $cacheValue['value'];
130            } else {
131                $this->deleteItem($id);
132            }
133        }
134
135        return $value;
136    }
137
138    /**
139     * Determine if the item exist in cache
140     *
141     * @param  string $id
142     * @return bool
143     */
144    public function hasItem(string $id): bool
145    {
146        return ($this->getItem($id) !== false);
147    }
148
149    /**
150     * Delete a value in cache
151     *
152     * @param  string $id
153     * @return Apc
154     */
155    public function deleteItem(string $id): Apc
156    {
157        apcu_delete($this->key($id));
158        return $this;
159    }
160
161    /**
162     * Clear all stored values from cache
163     *
164     * @return Apc
165     */
166    public function clear(): Apc
167    {
168        apcu_store($this->versionKey(), $this->resolveVersion() + 1, 0);
169        return $this;
170    }
171
172    /**
173     * Destroy cache resource
174     *
175     * @return Apc
176     */
177    public function destroy(): Apc
178    {
179        $this->clear();
180        return $this;
181    }
182
183    /**
184     * Atomically increment a counter in cache, creating it at $initial if it doesn't exist
185     *
186     * Stored as a raw scalar via apcu_add()/apcu_inc(), bypassing the start/ttl/value envelope used by
187     * saveItem()/getItem() entirely — a counter key and a saveItem()-managed key are two incompatible
188     * storage formats on this adapter, and a counter is not readable via getItem(). apcu_inc() has no
189     * initial-value parameter of its own, so apcu_add() atomically seeds the key at $initial (only the
190     * caller that finds the key missing succeeds) before apcu_inc() unconditionally applies $amount — this
191     * two-call sequence stays race-free under concurrency. $ttl is honored only when the counter is first
192     * created by apcu_add(); a later call does not refresh an existing counter's expiry.
193     *
194     * @param  string $id
195     * @param  int    $amount
196     * @param  int    $initial
197     * @param  ?int   $ttl
198     * @throws Exception
199     * @return int
200     */
201    public function incrementItem(string $id, int $amount = 1, int $initial = 0, ?int $ttl = null): int
202    {
203        $key = $this->key($id);
204        $ttl = ($ttl !== null) ? $ttl : $this->ttl;
205
206        apcu_add($key, $initial, $ttl);
207
208        $success = null;
209        $result  = apcu_inc($key, $amount, $success, $ttl);
210
211        if ($success === false) {
212            throw new Exception('Error: The value at that key is not numeric.');
213        }
214
215        return $result;
216    }
217
218    /**
219     * Atomically decrement a counter in cache, creating it at $initial if it doesn't exist
220     *
221     * Stored as a raw scalar via apcu_add()/apcu_dec(), bypassing the start/ttl/value envelope used by
222     * saveItem()/getItem() entirely — a counter key and a saveItem()-managed key are two incompatible
223     * storage formats on this adapter, and a counter is not readable via getItem(). See incrementItem() for
224     * the apcu_add()+apcu_dec() atomicity rationale. Unlike Memcached::decrement(), APCu allows the result
225     * to go negative.
226     *
227     * @param  string $id
228     * @param  int    $amount
229     * @param  int    $initial
230     * @param  ?int   $ttl
231     * @throws Exception
232     * @return int
233     */
234    public function decrementItem(string $id, int $amount = 1, int $initial = 0, ?int $ttl = null): int
235    {
236        $key = $this->key($id);
237        $ttl = ($ttl !== null) ? $ttl : $this->ttl;
238
239        apcu_add($key, $initial, $ttl);
240
241        $success = null;
242        $result  = apcu_dec($key, $amount, $success, $ttl);
243
244        if ($success === false) {
245            throw new Exception('Error: The value at that key is not numeric.');
246        }
247
248        return $result;
249    }
250
251    /**
252     * Fetch the raw version value from APCu, or false if it isn't set
253     *
254     * @param  string $key
255     * @return mixed
256     */
257    protected function fetchVersion(string $key): mixed
258    {
259        return apcu_fetch($key);
260    }
261
262}