3 * Zend Framework (http://framework.zend.com/)
5 * @link http://github.com/zendframework/zf2 for the canonical source repository
6 * @copyright Copyright (c) 2005-2016 Zend Technologies USA Inc. (http://www.zend.com)
7 * @license http://framework.zend.com/license/new-bsd New BSD License
10 namespace Zend\Cache\Storage\Adapter
;
12 use Redis
as RedisResource
;
15 use Zend\Cache\Exception
;
16 use Zend\Stdlib\ArrayUtils
;
19 * This is a resource manager for redis
21 class RedisResourceManager
24 * Registered resources
28 protected $resources = [];
31 * Check if a resource exists
36 public function hasResource($id)
38 return isset($this->resources
[$id]);
42 * Get redis server version
44 * @param string $resourceId
46 * @throws Exception\RuntimeException
48 public function getVersion($resourceId)
50 // check resource id and initialize the resource
51 $this->getResource($resourceId);
53 return $this->resources
[$resourceId]['version'];
57 * Get redis major server version
59 * @param string $resourceId
61 * @throws Exception\RuntimeException
63 public function getMajorVersion($resourceId)
65 // check resource id and initialize the resource
66 $this->getResource($resourceId);
68 return (int) $this->resources
[$resourceId]['version'];
72 * Get redis server version
74 * @deprecated 2.2.2 Use getMajorVersion instead
78 * @throws Exception\RuntimeException
80 public function getMayorVersion($id)
82 return $this->getMajorVersion($id);
86 * Get redis resource database
91 public function getDatabase($id)
93 if (! $this->hasResource($id)) {
94 throw new Exception\
RuntimeException("No resource with id '{$id}'");
97 $resource = & $this->resources
[$id];
98 return $resource['database'];
102 * Get redis resource password
107 public function getPassword($id)
109 if (! $this->hasResource($id)) {
110 throw new Exception\
RuntimeException("No resource with id '{$id}'");
113 $resource = & $this->resources
[$id];
114 return $resource['password'];
118 * Gets a redis resource
121 * @return RedisResource
122 * @throws Exception\RuntimeException
124 public function getResource($id)
126 if (! $this->hasResource($id)) {
127 throw new Exception\
RuntimeException("No resource with id '{$id}'");
130 $resource = & $this->resources
[$id];
131 if ($resource['resource'] instanceof RedisResource
) {
132 //in case new server was set then connect
133 if (! $resource['initialized']) {
134 $this->connect($resource);
137 if (! $resource['version']) {
138 $info = $resource['resource']->info();
139 $resource['version'] = $info['redis_version'];
142 return $resource['resource'];
145 $redis = new RedisResource();
147 $resource['resource'] = $redis;
148 $this->connect($resource);
150 $this->normalizeLibOptions($resource['lib_options']);
152 foreach ($resource['lib_options'] as $k => $v) {
153 $redis->setOption($k, $v);
156 $info = $redis->info();
157 $resource['version'] = $info['redis_version'];
158 $this->resources
[$id]['resource'] = $redis;
165 * @throws Exception\RuntimeException
166 * @return array array('host' => <host>[, 'port' => <port>[, 'timeout' => <timeout>]])
168 public function getServer($id)
170 if (! $this->hasResource($id)) {
171 throw new Exception\
RuntimeException("No resource with id '{$id}'");
174 $resource = & $this->resources
[$id];
175 return $resource['server'];
179 * Normalize one server into the following format:
180 * array('host' => <host>[, 'port' => <port>[, 'timeout' => <timeout>]])
182 * @param string|array $server
184 * @throws Exception\InvalidArgumentException
186 protected function normalizeServer(&$server)
192 // convert a single server into an array
193 if ($server instanceof Traversable
) {
194 $server = ArrayUtils
::iteratorToArray($server);
197 if (is_array($server)) {
198 // array(<host>[, <port>[, <timeout>]])
199 if (isset($server[0])) {
200 $host = (string) $server[0];
201 $port = isset($server[1]) ?
(int) $server[1] : $port;
202 $timeout = isset($server[2]) ?
(int) $server[2] : $timeout;
205 // array('host' => <host>[, 'port' => <port>, ['timeout' => <timeout>]])
206 if (! isset($server[0]) && isset($server['host'])) {
207 $host = (string) $server['host'];
208 $port = isset($server['port']) ?
(int) $server['port'] : $port;
209 $timeout = isset($server['timeout']) ?
(int) $server['timeout'] : $timeout;
212 // parse server from URI host{:?port}
213 $server = trim($server);
214 if (strpos($server, '/') !== 0) {
215 //non unix domain socket connection
216 $server = parse_url($server);
218 $server = ['host' => $server];
221 throw new Exception\
InvalidArgumentException("Invalid server given");
224 $host = $server['host'];
225 $port = isset($server['port']) ?
(int) $server['port'] : $port;
226 $timeout = isset($server['timeout']) ?
(int) $server['timeout'] : $timeout;
230 throw new Exception\
InvalidArgumentException('Missing required server host');
236 'timeout' => $timeout,
241 * Extract password to be used on connection
243 * @param mixed $resource
244 * @param mixed $serverUri
246 * @return string|null
248 protected function extractPassword($resource, $serverUri)
250 if (! empty($resource['password'])) {
251 return $resource['password'];
254 if (! is_string($serverUri)) {
258 // parse server from URI host{:?port}
259 $server = trim($serverUri);
261 if (strpos($server, '/') === 0) {
265 //non unix domain socket connection
266 $server = parse_url($server);
268 return isset($server['pass']) ?
$server['pass'] : null;
272 * Connects to redis server
275 * @param array & $resource
278 * @throws Exception\RuntimeException
280 protected function connect(array & $resource)
282 $server = $resource['server'];
283 $redis = $resource['resource'];
284 if ($resource['persistent_id'] !== '') {
285 //connect or reuse persistent connection
286 $success = $redis->pconnect(
290 $resource['persistent_id']
292 } elseif ($server['port']) {
293 $success = $redis->connect($server['host'], $server['port'], $server['timeout']);
294 } elseif ($server['timeout']) {
295 //connect through unix domain socket
296 $success = $redis->connect($server['host'], $server['timeout']);
298 $success = $redis->connect($server['host']);
302 throw new Exception\
RuntimeException('Could not estabilish connection with Redis instance');
305 $resource['initialized'] = true;
306 if ($resource['password']) {
307 $redis->auth($resource['password']);
309 $redis->select($resource['database']);
316 * @param array|Traversable|RedisResource $resource
317 * @return RedisResourceManager Fluent interface
319 public function setResource($id, $resource)
322 //TODO: how to get back redis connection info from resource?
324 'persistent_id' => '',
330 'initialized' => false,
333 if (! $resource instanceof RedisResource
) {
334 if ($resource instanceof Traversable
) {
335 $resource = ArrayUtils
::iteratorToArray($resource);
336 } elseif (! is_array($resource)) {
337 throw new Exception\
InvalidArgumentException(
338 'Resource must be an instance of an array or Traversable'
342 $resource = array_merge($defaults, $resource);
343 // normalize and validate params
344 $this->normalizePersistentId($resource['persistent_id']);
346 // #6495 note: order is important here, as `normalizeServer` applies destructive
347 // transformations on $resource['server']
348 $resource['password'] = $this->extractPassword($resource, $resource['server']);
350 $this->normalizeServer($resource['server']);
352 //there are two ways of determining if redis is already initialized
353 //with connect function:
355 //2) checking undocumented property socket which is available only
356 //after successful connect
357 $resource = array_merge(
360 'resource' => $resource,
361 'initialized' => isset($resource->socket
),
365 $this->resources
[$id] = $resource;
373 * @return RedisResourceManager Fluent interface
375 public function removeResource($id)
377 unset($this->resources
[$id]);
382 * Set the persistent id
385 * @param string $persistentId
386 * @return RedisResourceManager Fluent interface
387 * @throws Exception\RuntimeException
389 public function setPersistentId($id, $persistentId)
391 if (! $this->hasResource($id)) {
392 return $this->setResource($id, [
393 'persistent_id' => $persistentId
397 $resource = & $this->resources
[$id];
398 if ($resource['resource'] instanceof RedisResource
&& $resource['initialized']) {
399 throw new Exception\
RuntimeException(
400 "Can't change persistent id of resource {$id} after initialization"
404 $this->normalizePersistentId($persistentId);
405 $resource['persistent_id'] = $persistentId;
411 * Get the persistent id
415 * @throws Exception\RuntimeException
417 public function getPersistentId($id)
419 if (! $this->hasResource($id)) {
420 throw new Exception\
RuntimeException("No resource with id '{$id}'");
423 $resource = & $this->resources
[$id];
425 return $resource['persistent_id'];
429 * Normalize the persistent id
431 * @param string $persistentId
433 protected function normalizePersistentId(& $persistentId)
435 $persistentId = (string) $persistentId;
442 * @param array $libOptions
443 * @return RedisResourceManager Fluent interface
445 public function setLibOptions($id, array $libOptions)
447 if (! $this->hasResource($id)) {
448 return $this->setResource($id, [
449 'lib_options' => $libOptions
453 $resource = & $this->resources
[$id];
455 $resource['lib_options'] = $libOptions;
457 if (! $resource['resource'] instanceof RedisResource
) {
461 $this->normalizeLibOptions($libOptions);
462 $redis = & $resource['resource'];
464 if (method_exists($redis, 'setOptions')) {
465 $redis->setOptions($libOptions);
467 foreach ($libOptions as $key => $value) {
468 $redis->setOption($key, $value);
480 * @throws Exception\RuntimeException
482 public function getLibOptions($id)
484 if (! $this->hasResource($id)) {
485 throw new Exception\
RuntimeException("No resource with id '{$id}'");
488 $resource = & $this->resources
[$id];
490 if ($resource['resource'] instanceof RedisResource
) {
492 $reflection = new ReflectionClass('Redis');
493 $constants = $reflection->getConstants();
494 foreach ($constants as $constName => $constValue) {
495 if (substr($constName, 0, 4) == 'OPT_') {
496 $libOptions[$constValue] = $resource['resource']->getOption($constValue);
501 return $resource['lib_options'];
505 * Set one Redis option
508 * @param string|int $key
509 * @param mixed $value
510 * @return RedisResourceManager Fluent interface
512 public function setLibOption($id, $key, $value)
514 return $this->setLibOptions($id, [$key => $value]);
518 * Get one Redis option
521 * @param string|int $key
523 * @throws Exception\RuntimeException
525 public function getLibOption($id, $key)
527 if (! $this->hasResource($id)) {
528 throw new Exception\
RuntimeException("No resource with id '{$id}'");
531 $this->normalizeLibOptionKey($key);
532 $resource = & $this->resources
[$id];
534 if ($resource['resource'] instanceof RedisResource
) {
535 return $resource['resource']->getOption($key);
538 return isset($resource['lib_options'][$key]) ?
$resource['lib_options'][$key] : null;
542 * Normalize Redis options
544 * @param array|Traversable $libOptions
545 * @throws Exception\InvalidArgumentException
547 protected function normalizeLibOptions(& $libOptions)
549 if (! is_array($libOptions) && ! ($libOptions instanceof Traversable
)) {
550 throw new Exception\
InvalidArgumentException(
551 "Lib-Options must be an array or an instance of Traversable"
556 foreach ($libOptions as $key => $value) {
557 $this->normalizeLibOptionKey($key);
558 $result[$key] = $value;
561 $libOptions = $result;
565 * Convert option name into it's constant value
567 * @param string|int $key
568 * @throws Exception\InvalidArgumentException
570 protected function normalizeLibOptionKey(& $key)
572 // convert option name into it's constant value
573 if (is_string($key)) {
574 $const = 'Redis::OPT_' . str_replace([' ', '-'], '_', strtoupper($key));
575 if (! defined($const)) {
576 throw new Exception\
InvalidArgumentException("Unknown redis option '{$key}' ({$const})");
578 $key = constant($const);
587 * Server can be described as follows:
588 * - URI: /path/to/sock.sock
589 * - Assoc: array('host' => <host>[, 'port' => <port>[, 'timeout' => <timeout>]])
590 * - List: array(<host>[, <port>, [, <timeout>]])
593 * @param string|array $server
594 * @return RedisResourceManager
596 public function setServer($id, $server)
598 if (! $this->hasResource($id)) {
599 return $this->setResource($id, [
604 $this->normalizeServer($server);
606 $resource = & $this->resources
[$id];
607 $resource['password'] = $this->extractPassword($resource, $server);
609 if ($resource['resource'] instanceof RedisResource
) {
610 $resourceParams = ['server' => $server];
612 if (! empty($resource['password'])) {
613 $resourceParams['password'] = $resource['password'];
616 $this->setResource($id, $resourceParams);
618 $resource['server'] = $server;
628 * @param string $password
629 * @return RedisResource
631 public function setPassword($id, $password)
633 if (! $this->hasResource($id)) {
634 return $this->setResource($id, [
635 'password' => $password,
639 $resource = & $this->resources
[$id];
640 $resource['password'] = $password;
641 $resource['initialized'] = false;
646 * Set redis database number
649 * @param int $database
650 * @return RedisResourceManager
652 public function setDatabase($id, $database)
654 $database = (int) $database;
656 if (! $this->hasResource($id)) {
657 return $this->setResource($id, [
658 'database' => $database,
662 $resource = & $this->resources
[$id];
663 if ($resource['resource'] instanceof RedisResource
&& $resource['initialized']) {
664 $resource['resource']->select($database);
667 $resource['database'] = $database;