Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 76
0.00% covered (danger)
0.00%
0 / 18
CRAP
0.00% covered (danger)
0.00%
0 / 1
MemCached
0.00% covered (danger)
0.00%
0 / 76
0.00% covered (danger)
0.00%
0 / 18
2162
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 connect
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 set
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
56
 add
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
56
 get
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 delete
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 exists
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 increment
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 decrement
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 rename
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 updateExpire
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 flush
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 flushAll
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 replace
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 stats
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
6
 getThreshold
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 __destruct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 close
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2/**
3 * Jingga
4 *
5 * PHP Version 8.1
6 *
7 * @package   phpOMS\DataStorage\Cache\Connection
8 * @copyright Dennis Eichhorn
9 * @license   OMS License 2.0
10 * @version   1.0.0
11 * @link      https://jingga.app
12 */
13declare(strict_types=1);
14
15namespace phpOMS\DataStorage\Cache\Connection;
16
17use phpOMS\Contract\SerializableInterface;
18use phpOMS\DataStorage\Cache\CacheStatus;
19use phpOMS\DataStorage\Cache\CacheType;
20use phpOMS\DataStorage\Cache\Exception\InvalidConnectionConfigException;
21
22/**
23 * Memcache class.
24 *
25 * @package phpOMS\DataStorage\Cache\Connection
26 * @license OMS License 2.0
27 * @link    https://jingga.app
28 * @since   1.0.0
29 */
30final class MemCached extends ConnectionAbstract
31{
32    /**
33     * Delimiter for cache meta data
34     *
35     * @var string
36     * @since 1.0.0
37     */
38    private const DELIM = '$';
39
40    /**
41     * {@inheritdoc}
42     */
43    protected string $type = CacheType::MEMCACHED;
44
45    /**
46     * Only cache if data is larger than threshold (0-100).
47     *
48     * @var int
49     * @since 1.0.0
50     */
51    private int $threshold = 0;
52
53    /**
54     * Constructor.
55     *
56     * @param array{host:string, port:int} $data Cache data
57     *
58     * @since 1.0.0
59     */
60    public function __construct(array $data)
61    {
62        $this->con = new \Memcached();
63        $this->connect($data);
64    }
65
66    /**
67     * Connect to cache
68     *
69     * @param null|array{host:string, port:int} $data Cache data
70     *
71     * @return void
72     *
73     * @throws InvalidConnectionConfigException
74     *
75     * @since 1.0.0
76     */
77    public function connect(array $data = null) : void
78    {
79        $this->dbdata = isset($data) ? $data : $this->dbdata;
80
81        if (!isset($this->dbdata['host'], $this->dbdata['port'])) {
82            $this->status = CacheStatus::FAILURE;
83            throw new InvalidConnectionConfigException((string) \json_encode($this->dbdata));
84        }
85
86        $this->con->addServer($this->dbdata['host'], $this->dbdata['port']);
87
88        $this->status = CacheStatus::OK;
89    }
90
91    /**
92     * {@inheritdoc}
93     *
94     * @throws \InvalidArgumentException
95     */
96    public function set(int | string $key, mixed $value, int $expire = -1) : void
97    {
98        if ($this->status !== CacheStatus::OK) {
99            return;
100        }
101
102        if (!(\is_scalar($value) || $value === null || \is_array($value) || $value instanceof \JsonSerializable || $value instanceof SerializableInterface)) {
103            throw new \InvalidArgumentException();
104        }
105
106        $this->con->set((string) $key, $value, \max($expire, 0));
107    }
108
109    /**
110     * {@inheritdoc}
111     *
112     * @throws \InvalidArgumentException
113     */
114    public function add(int | string $key, mixed $value, int $expire = -1) : bool
115    {
116        if ($this->status !== CacheStatus::OK) {
117            return false;
118        }
119
120        if (!(\is_scalar($value) || $value === null || \is_array($value) || $value instanceof \JsonSerializable || $value instanceof SerializableInterface)) {
121            throw new \InvalidArgumentException();
122        }
123
124        return $this->con->add((string) $key, $value, \max($expire, 0));
125    }
126
127    /**
128     * {@inheritdoc}
129     */
130    public function get(int | string $key, int $expire = -1) : mixed
131    {
132        if ($this->status !== CacheStatus::OK) {
133            return null;
134        }
135
136        $result = $this->con->get((string) $key);
137
138        return $this->con->getResultCode() !== \Memcached::RES_SUCCESS ? null : $result;
139    }
140
141    /**
142     * {@inheritdoc}
143     */
144    public function delete(int | string $key, int $expire = -1) : bool
145    {
146        if ($this->status !== CacheStatus::OK) {
147            return false;
148        }
149
150        $result = $this->con->delete((string) $key);
151
152        return $this->con->getResultCode() === \Memcached::RES_NOTFOUND ? true : $result;
153    }
154
155    /**
156     * {@inheritdoc}
157     */
158    public function exists(int | string $key, int $expire = -1) : bool
159    {
160        if ($this->status !== CacheStatus::OK) {
161            return false;
162        }
163
164        return $this->con->get((string) $key) !== false;
165    }
166
167    /**
168     * {@inheritdoc}
169     */
170    public function increment(int | string $key, int $value = 1) : bool
171    {
172        if ($this->status !== CacheStatus::OK) {
173            return false;
174        }
175
176        return $this->con->increment((string) $key, $value) !== false;
177    }
178
179    /**
180     * {@inheritdoc}
181     */
182    public function decrement(int | string $key, int $value = 1) : bool
183    {
184        if ($this->status !== CacheStatus::OK) {
185            return false;
186        }
187
188        return $this->con->decrement((string) $key, $value) !== false;
189    }
190
191    /**
192     * {@inheritdoc}
193     */
194    public function rename(int | string $old, int | string $new, int $expire = -1) : bool
195    {
196        if ($this->status !== CacheStatus::OK) {
197            return false;
198        }
199
200        $value = $this->get((string) $old);
201        $this->set((string) $new, $value, $expire);
202        $this->delete((string) $old);
203
204        return true;
205    }
206
207    /**
208     * Parse cached value
209     *
210     * @param int    $type      Cached value type
211     * @param string $raw       Cached value
212     * @param int    $expireEnd Value end position
213     *
214     * @return mixed
215     *
216     * @since 1.0.0
217     */
218    /*
219    private function reverseValue(int $type, string $raw, int $expireEnd) : mixed
220    {
221        switch ($type) {
222            case CacheValueType::_INT:
223                return (int) \substr($raw, $expireEnd + 1);
224            case CacheValueType::_FLOAT:
225                return (float) \substr($raw, $expireEnd + 1);
226            case CacheValueType::_BOOL:
227                return (bool) \substr($raw, $expireEnd + 1);
228            case CacheValueType::_STRING:
229                return \substr($raw, $expireEnd + 1);
230            case CacheValueType::_ARRAY:
231                $array = \substr($raw, $expireEnd + 1);
232                return \json_decode($array === false ? '[]' : $array, true);
233            case CacheValueType::_NULL:
234                return null;
235            case CacheValueType::_JSONSERIALIZABLE:
236                $namespaceStart = (int) \strpos($raw, self::DELIM, $expireEnd);
237                $namespaceEnd   = (int) \strpos($raw, self::DELIM, $namespaceStart + 1);
238                $namespace      = \substr($raw, $namespaceStart + 1, $namespaceEnd - $namespaceStart - 1);
239
240                if ($namespace === false) {
241                    return null; // @codeCoverageIgnore
242                }
243
244                return new $namespace();
245            case CacheValueType::_SERIALIZABLE:
246                $namespaceStart = (int) \strpos($raw, self::DELIM, $expireEnd);
247                $namespaceEnd   = (int) \strpos($raw, self::DELIM, $namespaceStart + 1);
248                $namespace      = \substr($raw, $namespaceStart + 1, $namespaceEnd - $namespaceStart - 1);
249
250                if ($namespace === false) {
251                    return null; // @codeCoverageIgnore
252                }
253
254                $obj = new $namespace();
255                $obj->unserialize(\substr($raw, $namespaceEnd + 1));
256
257                return $obj;
258            default:
259                return null; // @codeCoverageIgnore
260        }
261    }
262    */
263
264    /**
265     * {@inheritdoc}
266     */
267    public function updateExpire(int | string $key, int $expire = -1) : bool
268    {
269        if ($this->status !== CacheStatus::OK) {
270            return false;
271        }
272
273        if ($expire > 0) {
274            $this->con->touch((string) $key, $expire);
275        }
276
277        return true;
278    }
279
280    /**
281     * {@inheritdoc}
282     */
283    public function flush(int $expire = 0) : bool
284    {
285        return $this->status === CacheStatus::OK;
286    }
287
288    /**
289     * {@inheritdoc}
290     */
291    public function flushAll() : bool
292    {
293        if ($this->status !== CacheStatus::OK) {
294            return false;
295        }
296
297        $this->con->flush();
298
299        return true;
300    }
301
302    /**
303     * {@inheritdoc}
304     */
305    public function replace(int | string $key, mixed $value, int $expire = -1) : bool
306    {
307        if ($this->status !== CacheStatus::OK) {
308            return false;
309        }
310
311        return $this->con->replace((string) $key, $value, \max($expire, 0));
312    }
313
314    /**
315     * {@inheritdoc}
316     */
317    public function stats() : array
318    {
319        if ($this->status !== CacheStatus::OK) {
320            return [];
321        }
322
323        $stat = $this->con->getStats();
324        $temp = \reset($stat);
325
326        $stats           = [];
327        $stats['status'] = $this->status;
328        $stats['count']  = $temp['curr_items'];
329        $stats['size']   = $temp['bytes'];
330
331        return $stats;
332    }
333
334    /**
335     * {@inheritdoc}
336     */
337    public function getThreshold() : int
338    {
339        return $this->threshold;
340    }
341
342    /**
343     * Destructor.
344     *
345     * @since 1.0.0
346     */
347    public function __destruct()
348    {
349        $this->close();
350    }
351
352    /**
353     * Closing cache.
354     *
355     * @return void
356     *
357     * @since 1.0.0
358     */
359    public function close() : void
360    {
361        if ($this->con !== null) {
362            $this->con->quit();
363        }
364
365        parent::close();
366    }
367}