Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
97.33% covered (success)
97.33%
73 / 75
88.89% covered (warning)
88.89%
8 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
MemcachedCache
97.33% covered (success)
97.33%
73 / 75
88.89% covered (warning)
88.89%
8 / 9
22
0.00% covered (danger)
0.00%
0 / 1
 __destruct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 initialize
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 validateConfigs
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 get
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
2
 getValue
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 set
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
2
 delete
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
2
 flush
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 connect
93.33% covered (success)
93.33%
28 / 30
0.00% covered (danger)
0.00%
0 / 1
6.01
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\Log\LogLevel;
13use Memcached;
14use OutOfBoundsException;
15use RuntimeException;
16
17/**
18 * Class MemcachedCache.
19 *
20 * @package cache
21 */
22class MemcachedCache extends Cache
23{
24    protected Memcached $memcached;
25    /**
26     * Memcached Cache handler configurations.
27     *
28     * @var array<string,mixed>
29     */
30    protected array $configs = [
31        'servers' => [
32            [
33                'host' => '127.0.0.1',
34                'port' => 11211,
35                'weight' => 0,
36            ],
37        ],
38        'options' => [
39            Memcached::OPT_BINARY_PROTOCOL => true,
40        ],
41    ];
42
43    public function __destruct()
44    {
45        $this->memcached->quit();
46    }
47
48    protected function initialize() : void
49    {
50        $this->validateConfigs();
51        $this->connect();
52    }
53
54    protected function validateConfigs() : void
55    {
56        foreach ($this->configs['servers'] as $index => $config) {
57            if (empty($config['host'])) {
58                throw new OutOfBoundsException(
59                    "Memcached host config empty on server '{$index}'"
60                );
61            }
62        }
63    }
64
65    public function get(string $key) : mixed
66    {
67        if (isset($this->debugCollector)) {
68            $start = \microtime(true);
69            return $this->addDebugGet(
70                $key,
71                $start,
72                $this->getValue($key)
73            );
74        }
75        return $this->getValue($key);
76    }
77
78    protected function getValue(string $key) : mixed
79    {
80        $key = $this->memcached->get($this->renderKey($key));
81        return $key === false && $this->memcached->getResultCode() === Memcached::RES_NOTFOUND
82            ? null
83            : $key;
84    }
85
86    public function set(string $key, mixed $value, int $ttl = null) : bool
87    {
88        if (isset($this->debugCollector)) {
89            $start = \microtime(true);
90            return $this->addDebugSet(
91                $key,
92                $ttl,
93                $start,
94                $value,
95                $this->memcached->set($this->renderKey($key), $value, $this->makeTtl($ttl))
96            );
97        }
98        return $this->memcached->set($this->renderKey($key), $value, $this->makeTtl($ttl));
99    }
100
101    public function delete(string $key) : bool
102    {
103        if (isset($this->debugCollector)) {
104            $start = \microtime(true);
105            return $this->addDebugDelete(
106                $key,
107                $start,
108                $this->memcached->delete($this->renderKey($key))
109            );
110        }
111        return $this->memcached->delete($this->renderKey($key));
112    }
113
114    public function flush() : bool
115    {
116        if (isset($this->debugCollector)) {
117            $start = \microtime(true);
118            return $this->addDebugFlush(
119                $start,
120                $this->memcached->flush()
121            );
122        }
123        return $this->memcached->flush();
124    }
125
126    protected function connect() : void
127    {
128        $this->configs['options'][Memcached::OPT_SERIALIZER] = match ($this->serializer) {
129            Serializer::IGBINARY => Memcached::SERIALIZER_IGBINARY,
130            Serializer::JSON => Memcached::SERIALIZER_JSON,
131            Serializer::JSON_ARRAY => Memcached::SERIALIZER_JSON_ARRAY,
132            Serializer::MSGPACK => Memcached::SERIALIZER_MSGPACK,
133            default => Memcached::SERIALIZER_PHP,
134        };
135        $this->memcached = new Memcached();
136        $pool = [];
137        foreach ($this->configs['servers'] as $server) {
138            $host = $server['host'] . ':' . ($server['port'] ?? 11211);
139            if (\in_array($host, $pool, true)) {
140                $this->log(
141                    'Cache (memcached): Server pool already has ' . $host,
142                    LogLevel::DEBUG
143                );
144                continue;
145            }
146            $result = $this->memcached->addServer(
147                $server['host'],
148                $server['port'] ?? 11211,
149                $server['weight'] ?? 0
150            );
151            if ($result === false) {
152                $this->log("Cache (memcached): Could not add {$host} to server pool");
153            }
154            $pool[] = $host;
155        }
156        $result = $this->memcached->setOptions($this->configs['options']);
157        if ($result === false) {
158            $this->log('Cache (memcached): ' . $this->memcached->getLastErrorMessage());
159        }
160        if ( ! $this->memcached->getStats()) {
161            throw new RuntimeException('Cache (memcached): Could not connect to any server');
162        }
163    }
164}