RedisFactory.php 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. <?php
  2. /**
  3. * @copyright Copyright (c) 2016, ownCloud, Inc.
  4. *
  5. * @author Alejandro Varela <epma01@gmail.com>
  6. * @author Christoph Wurst <christoph@winzerhof-wurst.at>
  7. * @author Jörn Friedrich Dreyer <jfd@butonic.de>
  8. * @author Morris Jobke <hey@morrisjobke.de>
  9. * @author Robin Appelman <robin@icewind.nl>
  10. * @author Robin McCorkell <robin@mccorkell.me.uk>
  11. *
  12. * @license AGPL-3.0
  13. *
  14. * This code is free software: you can redistribute it and/or modify
  15. * it under the terms of the GNU Affero General Public License, version 3,
  16. * as published by the Free Software Foundation.
  17. *
  18. * This program is distributed in the hope that it will be useful,
  19. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  20. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  21. * GNU Affero General Public License for more details.
  22. *
  23. * You should have received a copy of the GNU Affero General Public License, version 3,
  24. * along with this program. If not, see <http://www.gnu.org/licenses/>
  25. *
  26. */
  27. namespace OC;
  28. class RedisFactory {
  29. public const REDIS_MINIMAL_VERSION = '3.1.3';
  30. public const REDIS_EXTRA_PARAMETERS_MINIMAL_VERSION = '5.3.0';
  31. /** @var \Redis|\RedisCluster */
  32. private $instance;
  33. /** @var SystemConfig */
  34. private $config;
  35. /**
  36. * RedisFactory constructor.
  37. *
  38. * @param SystemConfig $config
  39. */
  40. public function __construct(SystemConfig $config) {
  41. $this->config = $config;
  42. }
  43. private function create() {
  44. $isCluster = in_array('redis.cluster', $this->config->getKeys(), true);
  45. $config = $isCluster
  46. ? $this->config->getValue('redis.cluster', [])
  47. : $this->config->getValue('redis', []);
  48. if ($isCluster && !class_exists('RedisCluster')) {
  49. throw new \Exception('Redis Cluster support is not available');
  50. }
  51. if (isset($config['timeout'])) {
  52. $timeout = $config['timeout'];
  53. } else {
  54. $timeout = 0.0;
  55. }
  56. if (isset($config['read_timeout'])) {
  57. $readTimeout = $config['read_timeout'];
  58. } else {
  59. $readTimeout = 0.0;
  60. }
  61. $auth = null;
  62. if (isset($config['password']) && $config['password'] !== '') {
  63. if (isset($config['user']) && $config['user'] !== '') {
  64. $auth = [$config['user'], $config['password']];
  65. } else {
  66. $auth = $config['password'];
  67. }
  68. }
  69. // # TLS support
  70. // # https://github.com/phpredis/phpredis/issues/1600
  71. $connectionParameters = $this->getSslContext($config);
  72. // cluster config
  73. if ($isCluster) {
  74. if (!isset($config['seeds'])) {
  75. throw new \Exception('Redis cluster config is missing the "seeds" attribute');
  76. }
  77. // Support for older phpredis versions not supporting connectionParameters
  78. if ($connectionParameters !== null) {
  79. $this->instance = new \RedisCluster(null, $config['seeds'], $timeout, $readTimeout, false, $auth, $connectionParameters);
  80. } else {
  81. $this->instance = new \RedisCluster(null, $config['seeds'], $timeout, $readTimeout, false, $auth);
  82. }
  83. if (isset($config['failover_mode'])) {
  84. $this->instance->setOption(\RedisCluster::OPT_SLAVE_FAILOVER, $config['failover_mode']);
  85. }
  86. } else {
  87. $this->instance = new \Redis();
  88. if (isset($config['host'])) {
  89. $host = $config['host'];
  90. } else {
  91. $host = '127.0.0.1';
  92. }
  93. if (isset($config['port'])) {
  94. $port = $config['port'];
  95. } elseif ($host[0] !== '/') {
  96. $port = 6379;
  97. } else {
  98. $port = null;
  99. }
  100. // Support for older phpredis versions not supporting connectionParameters
  101. if ($connectionParameters !== null) {
  102. // Non-clustered redis requires connection parameters to be wrapped inside `stream`
  103. $connectionParameters = [
  104. 'stream' => $this->getSslContext($config)
  105. ];
  106. $this->instance->connect($host, $port, $timeout, null, 0, $readTimeout, $connectionParameters);
  107. } else {
  108. $this->instance->connect($host, $port, $timeout, null, 0, $readTimeout);
  109. }
  110. // Auth if configured
  111. if ($auth !== null) {
  112. $this->instance->auth($auth);
  113. }
  114. if (isset($config['dbindex'])) {
  115. $this->instance->select($config['dbindex']);
  116. }
  117. }
  118. }
  119. /**
  120. * Get the ssl context config
  121. *
  122. * @param array $config the current config
  123. * @return array|null
  124. * @throws \UnexpectedValueException
  125. */
  126. private function getSslContext($config) {
  127. if (isset($config['ssl_context'])) {
  128. if (!$this->isConnectionParametersSupported()) {
  129. throw new \UnexpectedValueException(\sprintf(
  130. 'php-redis extension must be version %s or higher to support ssl context',
  131. self::REDIS_EXTRA_PARAMETERS_MINIMAL_VERSION
  132. ));
  133. }
  134. return $config['ssl_context'];
  135. }
  136. return null;
  137. }
  138. public function getInstance() {
  139. if (!$this->isAvailable()) {
  140. throw new \Exception('Redis support is not available');
  141. }
  142. if (!$this->instance instanceof \Redis) {
  143. $this->create();
  144. }
  145. return $this->instance;
  146. }
  147. public function isAvailable(): bool {
  148. return \extension_loaded('redis') &&
  149. \version_compare(\phpversion('redis'), self::REDIS_MINIMAL_VERSION, '>=');
  150. }
  151. /**
  152. * Php redis does support configurable extra parameters since version 5.3.0, see: https://github.com/phpredis/phpredis#connect-open.
  153. * We need to check if the current version supports extra connection parameters, otherwise the connect method will throw an exception
  154. *
  155. * @return boolean
  156. */
  157. private function isConnectionParametersSupported(): bool {
  158. return \extension_loaded('redis') &&
  159. \version_compare(\phpversion('redis'), self::REDIS_EXTRA_PARAMETERS_MINIMAL_VERSION, '>=');
  160. }
  161. }