composer package updates
[openemr.git] / vendor / zendframework / zend-cache / src / Storage / Adapter / RedisResourceManager.php
blob1943f288b1d160787b028825f23e3bd0d5df0480
1 <?php
2 /**
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
8 */
10 namespace Zend\Cache\Storage\Adapter;
12 use Redis as RedisResource;
13 use ReflectionClass;
14 use Traversable;
15 use Zend\Cache\Exception;
16 use Zend\Stdlib\ArrayUtils;
18 /**
19 * This is a resource manager for redis
21 class RedisResourceManager
23 /**
24 * Registered resources
26 * @var array
28 protected $resources = [];
30 /**
31 * Check if a resource exists
33 * @param string $id
34 * @return bool
36 public function hasResource($id)
38 return isset($this->resources[$id]);
41 /**
42 * Get redis server version
44 * @param string $resourceId
45 * @return string
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'];
56 /**
57 * Get redis major server version
59 * @param string $resourceId
60 * @return int
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'];
71 /**
72 * Get redis server version
74 * @deprecated 2.2.2 Use getMajorVersion instead
76 * @param string $id
77 * @return int
78 * @throws Exception\RuntimeException
80 public function getMayorVersion($id)
82 return $this->getMajorVersion($id);
85 /**
86 * Get redis resource database
88 * @param string $id
89 * @return string
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
104 * @param string $id
105 * @return string
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
120 * @param string $id
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;
159 return $redis;
163 * Get server
164 * @param string $id
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)
188 $host = null;
189 $port = null;
190 $timeout = 0;
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;
211 } else {
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);
217 } else {
218 $server = ['host' => $server];
220 if (! $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;
229 if (! $host) {
230 throw new Exception\InvalidArgumentException('Missing required server host');
233 $server = [
234 'host' => $host,
235 'port' => $port,
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)) {
255 return;
258 // parse server from URI host{:?port}
259 $server = trim($serverUri);
261 if (strpos($server, '/') === 0) {
262 return;
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
277 * @return null
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(
287 $server['host'],
288 $server['port'],
289 $server['timeout'],
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']);
297 } else {
298 $success = $redis->connect($server['host']);
301 if (! $success) {
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']);
313 * Set a resource
315 * @param string $id
316 * @param array|Traversable|RedisResource $resource
317 * @return RedisResourceManager Fluent interface
319 public function setResource($id, $resource)
321 $id = (string) $id;
322 //TODO: how to get back redis connection info from resource?
323 $defaults = [
324 'persistent_id' => '',
325 'lib_options' => [],
326 'server' => [],
327 'password' => '',
328 'database' => 0,
329 'resource' => null,
330 'initialized' => false,
331 'version' => 0,
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']);
351 } else {
352 //there are two ways of determining if redis is already initialized
353 //with connect function:
354 //1) pinging server
355 //2) checking undocumented property socket which is available only
356 //after successful connect
357 $resource = array_merge(
358 $defaults,
360 'resource' => $resource,
361 'initialized' => isset($resource->socket),
365 $this->resources[$id] = $resource;
366 return $this;
370 * Remove a resource
372 * @param string $id
373 * @return RedisResourceManager Fluent interface
375 public function removeResource($id)
377 unset($this->resources[$id]);
378 return $this;
382 * Set the persistent id
384 * @param string $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;
407 return $this;
411 * Get the persistent id
413 * @param string $id
414 * @return string
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;
439 * Set Redis options
441 * @param string $id
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) {
458 return $this;
461 $this->normalizeLibOptions($libOptions);
462 $redis = & $resource['resource'];
464 if (method_exists($redis, 'setOptions')) {
465 $redis->setOptions($libOptions);
466 } else {
467 foreach ($libOptions as $key => $value) {
468 $redis->setOption($key, $value);
472 return $this;
476 * Get Redis options
478 * @param string $id
479 * @return array
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) {
491 $libOptions = [];
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);
499 return $libOptions;
501 return $resource['lib_options'];
505 * Set one Redis option
507 * @param string $id
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
520 * @param string $id
521 * @param string|int $key
522 * @return mixed
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"
555 $result = [];
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);
579 } else {
580 $key = (int) $key;
585 * Set server
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>]])
592 * @param string $id
593 * @param string|array $server
594 * @return RedisResourceManager
596 public function setServer($id, $server)
598 if (! $this->hasResource($id)) {
599 return $this->setResource($id, [
600 'server' => $server
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);
617 } else {
618 $resource['server'] = $server;
621 return $this;
625 * Set redis password
627 * @param string $id
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;
642 return $this;
646 * Set redis database number
648 * @param string $id
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;
669 return $this;