Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
98.92% covered (success)
98.92%
92 / 93
94.12% covered (success)
94.12%
16 / 17
CRAP
0.00% covered (danger)
0.00%
0 / 1
Cache
98.92% covered (success)
98.92%
92 / 93
94.12% covered (success)
94.12%
16 / 17
36
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 initialize
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 log
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 makeTtl
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 get
n/a
0 / 0
n/a
0 / 0
0
 getMulti
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 set
n/a
0 / 0
n/a
0 / 0
0
 setMulti
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 delete
n/a
0 / 0
n/a
0 / 0
0
 deleteMulti
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 flush
n/a
0 / 0
n/a
0 / 0
0
 increment
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 decrement
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 renderKey
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 serialize
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
5
 unserialize
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
5
 setDebugCollector
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 addDebugGet
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
2
 addDebugSet
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
2
 addDebugDelete
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
2
 addDebugFlush
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
2
1<?php declare(strict_types=1);
2/*
3 * This file is part of Aplus Framework Cache Library.
4 *
5 * (c) Natan Felles <natanfelles@gmail.com>
6 *
7 * For the full copyright and license information, please view the LICENSE
8 * file that was distributed with this source code.
9 */
10namespace Framework\Cache;
11
12use Framework\Cache\Debug\CacheCollector;
13use Framework\Debug\Debugger;
14use Framework\Log\Logger;
15use Framework\Log\LogLevel;
16use JetBrains\PhpStorm\Pure;
17
18/**
19 * Class Cache.
20 *
21 * @package cache
22 */
23abstract class Cache
24{
25    /**
26     * Driver specific configurations.
27     *
28     * @var array<string,mixed>
29     */
30    protected array $configs = [];
31    /**
32     * Keys prefix.
33     *
34     * @var string|null
35     */
36    protected ?string $prefix;
37    /**
38     * Data serializer.
39     *
40     * @var Serializer
41     */
42    protected Serializer $serializer;
43    /**
44     * The Logger instance if is set.
45     *
46     * @var Logger|null
47     */
48    protected ?Logger $logger;
49    /**
50     * The default Time To Live value.
51     *
52     * Used when set methods has the $ttl param as null.
53     *
54     * @var int
55     */
56    public int $defaultTtl = 60;
57    protected CacheCollector $debugCollector;
58
59    /**
60     * Cache constructor.
61     *
62     * @param array<string,mixed> $configs Driver specific configurations
63     * @param string|null $prefix Keys prefix
64     * @param Serializer $serializer Data serializer
65     */
66    public function __construct(
67        array $configs = [],
68        string $prefix = null,
69        Serializer $serializer = Serializer::PHP,
70        Logger $logger = null
71    ) {
72        if ($configs) {
73            $this->configs = \array_replace_recursive($this->configs, $configs);
74        }
75        $this->prefix = $prefix;
76        $this->serializer = $serializer;
77        $this->logger = $logger;
78        $this->initialize();
79    }
80
81    /**
82     * Initialize Cache handlers and configurations.
83     */
84    protected function initialize() : void
85    {
86    }
87
88    protected function log(
89        string $message,
90        LogLevel $level = LogLevel::ERROR
91    ) : void {
92        if (isset($this->logger)) {
93            $this->logger->log($level, $message);
94        }
95    }
96
97    /**
98     * Make the Time To Live value.
99     *
100     * @param int|null $seconds TTL value or null to use the default
101     *
102     * @return int The input $seconds or the $defaultTtl as integer
103     */
104    #[Pure]
105    protected function makeTtl(?int $seconds) : int
106    {
107        return $seconds ?? $this->defaultTtl;
108    }
109
110    /**
111     * Gets one item from the cache storage.
112     *
113     * @param string $key The item name
114     *
115     * @return mixed The item value or null if not found
116     */
117    abstract public function get(string $key) : mixed;
118
119    /**
120     * Gets multi items from the cache storage.
121     *
122     * @param array<int,string> $keys List of items names to get
123     *
124     * @return array<string,mixed> associative array with key names and respective values
125     */
126    public function getMulti(array $keys) : array
127    {
128        $values = [];
129        foreach ($keys as $key) {
130            $values[$key] = $this->get($key);
131        }
132        return $values;
133    }
134
135    /**
136     * Sets one item to the cache storage.
137     *
138     * @param string $key The item name
139     * @param mixed $value The item value
140     * @param int|null $ttl The Time To Live for the item or null to use the default
141     *
142     * @return bool TRUE if the item was set, FALSE if fail to set
143     */
144    abstract public function set(string $key, mixed $value, int $ttl = null) : bool;
145
146    /**
147     * Sets multi items to the cache storage.
148     *
149     * @param array<string,mixed> $data Associative array with key names and respective values
150     * @param int|null $ttl The Time To Live for all the items or null to use the default
151     *
152     * @return array<string,bool> associative array with key names and respective set status
153     */
154    public function setMulti(array $data, int $ttl = null) : array
155    {
156        foreach ($data as $key => &$value) {
157            $value = $this->set($key, $value, $ttl);
158        }
159        return $data;
160    }
161
162    /**
163     * Deletes one item from the cache storage.
164     *
165     * @param string $key the item name
166     *
167     * @return bool TRUE if the item was deleted, FALSE if fail to delete
168     */
169    abstract public function delete(string $key) : bool;
170
171    /**
172     * Deletes multi items from the cache storage.
173     *
174     * @param array<int,string> $keys List of items names to be deleted
175     *
176     * @return array<string,bool> associative array with key names and respective delete status
177     */
178    public function deleteMulti(array $keys) : array
179    {
180        $values = [];
181        foreach ($keys as $key) {
182            $values[$key] = $this->delete($key);
183        }
184        return $values;
185    }
186
187    /**
188     * Flush the cache storage.
189     *
190     * @return bool TRUE if all items are deleted, otherwise FALSE
191     */
192    abstract public function flush() : bool;
193
194    /**
195     * Increments the value of one item.
196     *
197     * @param string $key The item name
198     * @param int $offset The value to increment
199     * @param int|null $ttl The Time To Live for the item or null to use the default
200     *
201     * @return int The current item value
202     */
203    public function increment(string $key, int $offset = 1, int $ttl = null) : int
204    {
205        $offset = (int) \abs($offset);
206        $value = (int) $this->get($key);
207        $value = $value ? $value + $offset : $offset;
208        $this->set($key, $value, $ttl);
209        return $value;
210    }
211
212    /**
213     * Decrements the value of one item.
214     *
215     * @param string $key The item name
216     * @param int $offset The value to decrement
217     * @param int|null $ttl The Time To Live for the item or null to use the default
218     *
219     * @return int The current item value
220     */
221    public function decrement(string $key, int $offset = 1, int $ttl = null) : int
222    {
223        $offset = (int) \abs($offset);
224        $value = (int) $this->get($key);
225        $value = $value ? $value - $offset : -$offset;
226        $this->set($key, $value, $ttl);
227        return $value;
228    }
229
230    #[Pure]
231    protected function renderKey(string $key) : string
232    {
233        return $this->prefix . $key;
234    }
235
236    /**
237     * @param mixed $value
238     *
239     * @throws \JsonException
240     *
241     * @return string
242     */
243    protected function serialize(mixed $value) : string
244    {
245        if ($this->serializer === Serializer::IGBINARY) {
246            return \igbinary_serialize($value);
247        }
248        if ($this->serializer === Serializer::JSON
249            || $this->serializer === Serializer::JSON_ARRAY
250        ) {
251            return \json_encode($value, \JSON_THROW_ON_ERROR);
252        }
253        if ($this->serializer === Serializer::MSGPACK) {
254            return \msgpack_serialize($value);
255        }
256        return \serialize($value);
257    }
258
259    /**
260     * @param string $value
261     *
262     * @return mixed
263     */
264    protected function unserialize(string $value) : mixed
265    {
266        if ($this->serializer === Serializer::IGBINARY) {
267            return @\igbinary_unserialize($value);
268        }
269        if ($this->serializer === Serializer::JSON) {
270            return \json_decode($value);
271        }
272        if ($this->serializer === Serializer::JSON_ARRAY) {
273            return \json_decode($value, true);
274        }
275        if ($this->serializer === Serializer::MSGPACK) {
276            return \msgpack_unserialize($value);
277        }
278        return \unserialize($value, ['allowed_classes' => true]);
279    }
280
281    public function setDebugCollector(CacheCollector $debugCollector) : static
282    {
283        $this->debugCollector = $debugCollector;
284        $this->debugCollector->setInfo([
285            'class' => static::class,
286            'serializer' => $this->serializer->value,
287        ]);
288        return $this;
289    }
290
291    protected function addDebugGet(string $key, float $start, mixed $value) : mixed
292    {
293        $end = \microtime(true);
294        $this->debugCollector->addData([
295            'start' => $start,
296            'end' => $end,
297            'command' => 'GET',
298            'status' => $value === null ? 'FAIL' : 'OK',
299            'key' => $key,
300            'value' => Debugger::makeDebugValue($value),
301        ]);
302        return $value;
303    }
304
305    protected function addDebugSet(string $key, ?int $ttl, float $start, mixed $value, bool $status) : bool
306    {
307        $end = \microtime(true);
308        $this->debugCollector->addData([
309            'start' => $start,
310            'end' => $end,
311            'command' => 'SET',
312            'status' => $status ? 'OK' : 'FAIL',
313            'key' => $key,
314            'value' => Debugger::makeDebugValue($value),
315            'ttl' => $this->makeTtl($ttl),
316        ]);
317        return $status;
318    }
319
320    protected function addDebugDelete(string $key, float $start, bool $status) : bool
321    {
322        $end = \microtime(true);
323        $this->debugCollector->addData([
324            'start' => $start,
325            'end' => $end,
326            'command' => 'DELETE',
327            'status' => $status ? 'OK' : 'FAIL',
328            'key' => $key,
329        ]);
330        return $status;
331    }
332
333    protected function addDebugFlush(float $start, bool $status) : bool
334    {
335        $end = \microtime(true);
336        $this->debugCollector->addData([
337            'start' => $start,
338            'end' => $end,
339            'command' => 'FLUSH',
340            'status' => $status ? 'OK' : 'FAIL',
341        ]);
342        return $status;
343    }
344}