1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
namespace Linio\Component\Cache\Adapter; |
6
|
|
|
|
7
|
|
|
use Linio\Component\Cache\Exception\InvalidConfigurationException; |
8
|
|
|
use Linio\Component\Cache\Exception\KeyNotFoundException; |
9
|
|
|
use Redis; |
10
|
|
|
|
11
|
|
|
class PhpredisAdapter extends AbstractAdapter implements AdapterInterface |
12
|
|
|
{ |
13
|
|
|
/** |
14
|
|
|
* @var Redis |
15
|
|
|
*/ |
16
|
|
|
protected $client; |
17
|
|
|
|
18
|
|
|
/** |
19
|
|
|
* @var int |
20
|
|
|
*/ |
21
|
|
|
protected $ttl; |
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* @var array |
25
|
|
|
*/ |
26
|
|
|
protected $config; |
27
|
|
|
|
28
|
|
|
public function __construct(array $config = [], bool $lazy = true) |
29
|
|
|
{ |
30
|
|
|
if (!extension_loaded('redis')) { |
31
|
|
|
throw new InvalidConfigurationException('PhpRedisAdapter requires "phpredis" extension. See https://github.com/phpredis/phpredis.'); |
32
|
|
|
} |
33
|
|
|
|
34
|
|
|
$this->config = $config; |
35
|
|
|
|
36
|
|
|
if (!$lazy) { |
37
|
|
|
$this->getClient(); |
38
|
|
|
} |
39
|
|
|
} |
40
|
|
|
|
41
|
|
|
protected function getClient(): Redis |
42
|
|
|
{ |
43
|
|
|
if (!$this->client instanceof Redis) { |
44
|
|
|
$this->createClient($this->config); |
45
|
|
|
} |
46
|
|
|
|
47
|
|
|
return $this->client; |
48
|
|
|
} |
49
|
|
|
|
50
|
|
View Code Duplication |
public function get(string $key) |
51
|
|
|
{ |
52
|
|
|
$result = $this->getClient()->get($key); |
53
|
|
|
|
54
|
|
|
if ($result === false && !$this->getClient()->exists($key)) { |
55
|
|
|
throw new KeyNotFoundException(); |
56
|
|
|
} |
57
|
|
|
|
58
|
|
|
return $result; |
59
|
|
|
} |
60
|
|
|
|
61
|
|
View Code Duplication |
public function getMulti(array $keys): array |
62
|
|
|
{ |
63
|
|
|
$result = $this->getClient()->mGet($keys); |
64
|
|
|
$values = []; |
65
|
|
|
|
66
|
|
|
foreach ($keys as $index => $key) { |
67
|
|
|
if ($result[$index]) { |
68
|
|
|
$values[$key] = $result[$index]; |
69
|
|
|
} |
70
|
|
|
} |
71
|
|
|
|
72
|
|
|
return $values; |
73
|
|
|
} |
74
|
|
|
|
75
|
|
|
public function set(string $key, $value): bool |
76
|
|
|
{ |
77
|
|
View Code Duplication |
if ($this->ttl === null) { |
|
|
|
|
78
|
|
|
$result = $this->getClient()->set($key, $value); |
79
|
|
|
} else { |
80
|
|
|
$result = $this->getClient()->setex($key, $this->ttl, $value); |
81
|
|
|
} |
82
|
|
|
|
83
|
|
|
return (bool) $result; |
84
|
|
|
} |
85
|
|
|
|
86
|
|
|
public function setMulti(array $data): bool |
87
|
|
|
{ |
88
|
|
|
if ($this->ttl === null) { |
89
|
|
|
$result = $this->getClient()->mset($data); |
90
|
|
|
} else { |
91
|
|
|
$result = true; |
92
|
|
|
foreach ($data as $key => $value) { |
93
|
|
|
$result = $result && $this->client->setex($key, $this->ttl, $value); |
94
|
|
|
} |
95
|
|
|
} |
96
|
|
|
|
97
|
|
|
return (bool) $result; |
98
|
|
|
} |
99
|
|
|
|
100
|
|
|
public function contains(string $key): bool |
101
|
|
|
{ |
102
|
|
|
return $this->getClient()->exists($key); |
103
|
|
|
} |
104
|
|
|
|
105
|
|
|
public function delete(string $key): bool |
106
|
|
|
{ |
107
|
|
|
$this->client->delete($key); |
108
|
|
|
|
109
|
|
|
return true; |
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
public function deleteMulti(array $keys): bool |
113
|
|
|
{ |
114
|
|
|
$this->client->delete($keys); |
115
|
|
|
|
116
|
|
|
return true; |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
public function flush(): bool |
120
|
|
|
{ |
121
|
|
|
return (bool) $this->getClient()->flushDB(); |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
public function setClient(Redis $client) |
125
|
|
|
{ |
126
|
|
|
$this->client = $client; |
127
|
|
|
} |
128
|
|
|
|
129
|
|
|
protected function createClient(array $config) |
130
|
|
|
{ |
131
|
|
|
$params = $this->getConnectionParameters($config); |
132
|
|
|
$this->client = new Redis(); |
133
|
|
|
|
134
|
|
|
if ($params['connection_persistent']) { |
135
|
|
|
$connectionId = 1; |
136
|
|
|
|
137
|
|
|
if ($params['pool_size'] > 1) { |
138
|
|
|
$connectionId = mt_rand(1, $params['pool_size']); |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
$persistentId = sprintf('%s-%s-%s', $params['port'], $params['database'], $connectionId); |
142
|
|
|
$this->client->pconnect($params['host'], $params['port'], $params['timeout'] ?? 0, $persistentId, $params['retry_interval'] ?? 0); |
143
|
|
|
} else { |
144
|
|
|
$this->client->connect($params['host'], $params['port'], $params['timeout'] ?? 0, '', $params['retry_interval'] ?? 0); |
145
|
|
|
} |
146
|
|
|
|
147
|
|
|
if ($params['password']) { |
148
|
|
|
if (!$this->client->auth($params['password'])) { |
149
|
|
|
throw new InvalidConfigurationException(sprintf('Invalid password for phpredis adapter: %s:%s', $params['host'], $params['port'])); |
150
|
|
|
} |
151
|
|
|
} |
152
|
|
|
|
153
|
|
|
if ($params['database']) { |
154
|
|
|
$this->client->select($params['database']); |
155
|
|
|
} |
156
|
|
|
|
157
|
|
|
if ($params['serializer']) { |
158
|
|
|
switch ($params['serializer']) { |
159
|
|
|
case 'none': |
|
|
|
|
160
|
|
|
$this->client->setOption(Redis::OPT_SERIALIZER, (string) Redis::SERIALIZER_NONE); |
161
|
|
|
break; |
162
|
|
|
case 'php': |
|
|
|
|
163
|
|
|
$this->client->setOption(Redis::OPT_SERIALIZER, (string) Redis::SERIALIZER_PHP); |
164
|
|
|
break; |
165
|
|
|
case 'igbinary': |
|
|
|
|
166
|
|
|
if (!extension_loaded('igbinary')) { |
167
|
|
|
throw new InvalidConfigurationException('Serializer igbinary requires "igbinary" extension. See https://pecl.php.net/package/igbinary'); |
168
|
|
|
} |
169
|
|
|
|
170
|
|
|
if (!defined('Redis::SERIALIZER_IGBINARY')) { |
171
|
|
|
throw new InvalidConfigurationException('Serializer igbinary requires run extension compilation using configure with --enable-redis-igbinary'); |
172
|
|
|
} |
173
|
|
|
|
174
|
|
|
$this->client->setOption(Redis::OPT_SERIALIZER, (string) Redis::SERIALIZER_IGBINARY); |
175
|
|
|
break; |
176
|
|
|
} |
177
|
|
|
} |
178
|
|
|
|
179
|
|
|
$this->client->setOption(Redis::OPT_SCAN, (string) Redis::SCAN_NORETRY); |
180
|
|
|
|
181
|
|
|
if (isset($config['ttl'])) { |
182
|
|
|
$this->ttl = $config['ttl']; |
183
|
|
|
} |
184
|
|
|
|
185
|
|
|
if (isset($config['cache_not_found_keys'])) { |
186
|
|
|
$this->cacheNotFoundKeys = (bool) $config['cache_not_found_keys']; |
187
|
|
|
} |
188
|
|
|
|
189
|
|
|
if (isset($params['read_timeout'])) { |
190
|
|
|
$this->client->setOption(Redis::OPT_READ_TIMEOUT, (string) $params['read_timeout']); |
191
|
|
|
} |
192
|
|
|
} |
193
|
|
|
|
194
|
|
|
protected function getConnectionParameters(array $config): array |
195
|
|
|
{ |
196
|
|
|
$connectionParameters = []; |
197
|
|
|
$connectionParameters['host'] = $config['host'] ?? '127.0.0.1'; |
198
|
|
|
$connectionParameters['port'] = $config['port'] ?? 6379; |
199
|
|
|
$connectionParameters['password'] = $config['password'] ?? null; |
200
|
|
|
$connectionParameters['database'] = $config['database'] ?? 0; |
201
|
|
|
$connectionParameters['timeout'] = $config['timeout'] ?? null; |
202
|
|
|
$connectionParameters['read_timeout'] = $config['read_timeout'] ?? null; |
203
|
|
|
$connectionParameters['retry_interval'] = $config['retry_interval'] ?? null; |
204
|
|
|
$connectionParameters['serializer'] = $config['serializer'] ?? null; |
205
|
|
|
$connectionParameters['connection_persistent'] = $config['connection_persistent'] ?? false; |
206
|
|
|
$connectionParameters['pool_size'] = $config['pool_size'] ?? 1; |
207
|
|
|
|
208
|
|
|
return $connectionParameters; |
209
|
|
|
} |
210
|
|
|
|
211
|
|
|
public function setNamespace(string $namespace) |
212
|
|
|
{ |
213
|
|
|
$this->getClient()->setOption(Redis::OPT_PREFIX, $namespace . ':'); |
214
|
|
|
parent::setNamespace($namespace); |
215
|
|
|
} |
216
|
|
|
|
217
|
|
|
public function setTtl(int $ttl) |
218
|
|
|
{ |
219
|
|
|
$this->ttl = $ttl; |
220
|
|
|
} |
221
|
|
|
} |
222
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.