diff --git a/composer.json b/composer.json index d9e8e74..6871003 100644 --- a/composer.json +++ b/composer.json @@ -5,10 +5,12 @@ }, "require": { "php": ">=5.4.20", - "colinmollenhour/credis": "^1.13", + "colinmollenhour/credis": "^1.11.1", "twig/twig": "^3.0", "mustache/mustache": "^2.0", "smarty/smarty": "^3.0", - "phpfastcache/phpfastcache": "^7.0" + "phpfastcache/phpfastcache": "^7.0", + "colinmollenhour/php-redis-session-abstract": "~1.4.0", + "colinmollenhour/cache-backend-redis": "^1.17" } } diff --git a/system/session/Redis/AbstractCM/Handler.php b/system/session/Redis/AbstractCM/Handler.php new file mode 100644 index 0000000..23c8a63 --- /dev/null +++ b/system/session/Redis/AbstractCM/Handler.php @@ -0,0 +1,61 @@ +=')) { + $sessionId = self::SESSION_PREFIX . $id; + $this->_redis->pipeline() + ->select($this->_dbNum) + ->hMSet($sessionId, array( + 'data' => $this->_encodeData($data), + 'lock' => 0, // 0 so that next lock attempt will get 1 + )) + ->hIncrBy($sessionId, 'writes', 1) + ->expire($sessionId, min((int)$lifetime, (int)$this->_maxLifetime)) + ->exec(); + } else { + $sessionId = self::SESSION_PREFIX . $id; + $redis = $this->_redis; + $redis->select($this->_dbNum); + $redis->hMSet($sessionId, array( + 'data' => $this->_encodeData(serialize($data)), + 'lock' => 0, // 0 so that next lock attempt will get 1 + )); + $redis->hIncrBy($sessionId, 'writes', 1); + + $redis->expire($sessionId, min((int)$lifetime, (int)$this->_maxLifetime)); + //->exec(); + //$redis->exec(); + } + + } + + public function setName($name) + { + $this->_name = $name; + } +} diff --git a/system/session/Redis/Config.php b/system/session/Redis/Config.php new file mode 100644 index 0000000..1b2b2ae --- /dev/null +++ b/system/session/Redis/Config.php @@ -0,0 +1,271 @@ +engine = \Phacil\Framework\Registry::getInstance(); + $this->logLevel = \Phacil\Framework\Config::Debug() ? 6 : $this->logLevel; + $fg = []; + foreach (get_class_methods($this) as $key => $value) { + # code... + if ($value != '__construct' && $value != 'getLogLevel') + $fg[$value] = $this->$value(); + } + + return; + } + + /** + * + * {@inheritdoc} + */ + function getLogLevel() { + return $this->engine->config->get('session_redis_log_level') ?: $this->logLevel; + } + + /** + * {@inheritdoc} + */ + public function getHost() + { + return $this->engine->config->get('session_redis_dsn') ? : self::PARAM_HOST; + } + + /** + * {@inheritdoc} + */ + public function getPort() + { + return $this->engine->config->get('session_redis_port') ?: self::PARAM_PORT; + } + + /** + * {@inheritdoc} + */ + public function getDatabase() + { + return (string)$this->engine->config->get('session_redis_database') ?: self::PARAM_DATABASE; + } + + /** + * {@inheritdoc} + */ + public function getPassword() + { + return $this->engine->config->get('session_redis_password') ?: ''; + } + + /** + * {@inheritdoc} + */ + public function getTimeout() + { + return $this->engine->config->get('session_redis_timeout') ? : self::PARAM_TIMEOUT; + } + + /** + * {@inheritdoc} + */ + public function getPersistentIdentifier() + { + return $this->engine->config->get('session_redis_persistent_id') ?: ''; + } + + /** + * {@inheritdoc} + */ + public function getCompressionThreshold() + { + return $this->engine->config->get('session_redis_compression_threshold') ?: self::DEFAULT_COMPRESSION_THRESHOLD ; + } + + /** + * {@inheritdoc} + */ + public function getCompressionLibrary() + { + return $this->engine->config->get('session_redis_compression_library') ?: (self::PARAM_COMPRESSION_LIBRARY); + } + + /** + * {@inheritdoc} + */ + public function getMaxConcurrency() + { + return $this->engine->config->get('session_redis_max_concurrency') ?: (self::PARAM_MAX_CONCURRENCY); + } + + /** + * {@inheritdoc} + */ + public function getMaxLifetime() + { + return $this->engine->config->get('session_redis_max_lifetime') ?: self::SESSION_MAX_LIFETIME; + } + + /** + * {@inheritdoc} + */ + public function getMinLifetime() + { + return (int)$this->engine->config->get('session_redis_min_lifetime') ?: (self::PARAM_MIN_LIFETIME); + } + + /** + * {@inheritdoc} + */ + public function getDisableLocking() + { + return (bool)$this->engine->config->get('session_redis_disable_locking') ?: (self::PARAM_DISABLE_LOCKING); + } + + /** + * {@inheritdoc} + */ + public function getBotLifetime() + { + return (int) $this->engine->config->get('session_redis_bot_lifetime') ?: (self::PARAM_BOT_LIFETIME); + } + + /** + * {@inheritdoc} + */ + public function getBotFirstLifetime() + { + return (string)$this->engine->config->get('session_redis_bot_first_lifetime') ?: (self::PARAM_BOT_FIRST_LIFETIME); + } + + /** + * {@inheritdoc} + */ + public function getFirstLifetime() + { + return (int)$this->engine->config->get('session_redis_first_lifetime') ?: (self::PARAM_FIRST_LIFETIME); + } + + /** + * {@inheritdoc} + */ + public function getBreakAfter() + { + return (int)$this->engine->config->get('session_redis_break_after') ?: (self::PARAM_BREAK_AFTER); + } + + /** + * {@inheritdoc} + */ + public function getLifetime() + { + return (int)$this->engine->config->get('session_redis_expire') ?: self::PARAM_SESSION_LIFETIME; + } + + /** + * {@inheritdoc} + */ + public function getSentinelServers() + { + return $this->engine->config->get('session_redis_sentinel_servers') ?: null; + } + + /** + * {@inheritdoc} + */ + public function getSentinelMaster() + { + return $this->engine->config->get('session_redis_sentinel_master') ?: null; + } + + /** + * {@inheritdoc} + */ + public function getSentinelVerifyMaster() + { + return $this->engine->config->get('session_redis_verify_master') ?: (null); + } + + /** + * {@inheritdoc} + */ + public function getSentinelConnectRetries() + { + return $this->engine->config->get('session_redis_sentinel_connect_retries') ?: (self::PARAM_SENTINEL_CONNECT_RETRIES); + } + + /** + * {@inheritdoc} + */ + public function getFailAfter() + { + return (int)$this->engine->config->get('session_redis_fail_after') ?: self::DEFAULT_FAIL_AFTER; + } + +} \ No newline at end of file diff --git a/system/session/Redis/Handler.php b/system/session/Redis/Handler.php new file mode 100644 index 0000000..2cdd9c3 --- /dev/null +++ b/system/session/Redis/Handler.php @@ -0,0 +1,176 @@ +config = $config; + $this->logger = $logger; + } + + + /** + * Get connection + * + * @return \Cm\RedisSession\Handler + * @throws Exception + */ + private function getConnection() + { + $pid = getmypid(); + if (!isset($this->connection[$pid])) { + try { + //$this->connection[$pid] = new \Cm\RedisSession\Handler($this->config, $this->logger); + $this->connection[$pid] = new \Phacil\Framework\Session\Redis\AbstractCM\Handler($this->config, $this->logger); + } catch (ConnectionFailedException $e) { + throw new Exception(($e->getMessage())); + } + } + return $this->connection[$pid]; + } + + function setName($name){ + $this->name = $name; + } + + /** + * Open session + * + * @param string $savePath ignored + * @param string $sessionName ignored + * @return bool + * @throws Exception + */ + public function open($savePath, $sessionName) + { + return $this->getConnection()->open($savePath, $sessionName); + } + + /** + * Fetch session data + * + * @param string $sessionId + * @return string + * @throws ConcurrentConnectionsExceededException + * @throws Exception + */ + public function read($sessionId) + { + try { + return $this->getConnection()->read($sessionId); + } catch (ConcurrentConnectionsExceededException $e) { + throw new Exception($e->getMessage(), 1); + + } + } + + /** + * Update session + * + * @param string $sessionId + * @param string $sessionData + * @return boolean + * @throws Exception + */ + public function write($sessionId, $sessionData) + { + try { + //$this->getConnection()->setName($this->name); + $cha = $this->getConnection()->write($sessionId, $sessionData); + return $cha; + } catch (ConcurrentConnectionsExceededException $e) { + throw new Exception($e->getMessage(), 1); + } + + } + + /** + * Destroy session + * + * @param string $sessionId + * @return boolean + * @throws Exception + */ + public function destroy($sessionId) + { + return $this->getConnection()->destroy($sessionId); + } + + /** + * Overridden to prevent calling getLifeTime at shutdown + * + * @return bool + * @throws Exception + */ + public function close() + { + return $this->getConnection()->close(); + } + + /** + * Garbage collection + * + * @param int $maxLifeTime ignored + * @return boolean + * @throws Exception + * @SuppressWarnings(PHPMD.ShortMethodName) + */ + public function gc($maxLifeTime) + { + return $this->getConnection()->gc($maxLifeTime); + } + + /** + * Get the number of failed lock attempts + * + * @return int + * @throws Exception + */ + public function getFailedLockAttempts() + { + return $this->getConnection()->getFailedLockAttempts(); + } +} \ No newline at end of file diff --git a/system/session/Redis/Logger.php b/system/session/Redis/Logger.php new file mode 100644 index 0000000..cd0d63a --- /dev/null +++ b/system/session/Redis/Logger.php @@ -0,0 +1,73 @@ +logLevel = $config->getLogLevel() ?: self::ALERT; + } + + /** + * {@inheritdoc} + */ + public function setLogLevel($level) + { + $this->logLevel = $level; + } + + /** + * {@inheritdoc} + */ + public function log($message, $level) + { + $engine = \Phacil\Framework\Registry::getInstance(); + if ($this->logLevel >= $level) { + switch ($level) { + case self::EMERGENCY: + case self::CRITICAL: + case self::ERROR: + trigger_error($message, E_ERROR); + break; + case self::ALERT: + case self::WARNING: + //$this->logger->alert($message); + $engine->log->write($message,E_WARNING); + break; + case self::NOTICE: + case self::INFO: + $engine->log->write($message); + break; + default: + $engine->log->write($message); + } + } + } + + /** + * {@inheritdoc} + */ + public function logException(\Exception $e) + { + throw new Exception($e->getMessage()); + } +} diff --git a/system/session/autoload.php b/system/session/autoload.php index 77c9d87..fa05138 100644 --- a/system/session/autoload.php +++ b/system/session/autoload.php @@ -26,7 +26,7 @@ use Phacil\Framework\Config; * @since 1.0.0 * @package Phacil\Framework */ -final class Session +class Session { /** * @@ -61,6 +61,14 @@ final class Session */ public $redisKey; + /** + * + * @var \Phacil\Framework\Session\Redis\Handler + */ + protected $saveHandler; + + protected $redisExpire; + /** * * @param bool $redis @@ -75,12 +83,14 @@ final class Session { $this->name = (Config::SESSION_PREFIX() ?: 'SESS') . (isset($_SERVER['REMOTE_ADDR']) ? md5($_SERVER['REMOTE_ADDR']) : md5(date("dmY"))); + //define('SESSION_PREFIX_INTERNAL_REDIS', Config::REDIS_SESSION_PREFIX() ?: 'phacil_'); + + $this->redis($redis, $redisDSN, $redisPort, $redisPass, $redis_expire, $redis_prefix); + if (!session_id()) { $this->openSession(); } - $this->redis($redis, $redisDSN, $redisPort, $redisPass, $redis_expire, $redis_prefix); - if (session_name() === $this->name) { $this->data =& $_SESSION; } else { @@ -130,6 +140,16 @@ final class Session if (!$redis) return false; + $redisConfig = new \Phacil\Framework\Session\Redis\Config(); + + $this->saveHandler = new \Phacil\Framework\Session\Redis\Handler($redisConfig, new \Phacil\Framework\Session\Redis\Logger($redisConfig)); + + $this->saveHandler->setName($this->name); + + $a = $this->registerSaveHandler(); + + return; + $this->redisExpire = ($redis_expire) ?: session_cache_expire() * 60; $this->redisPrefix = ($redis_prefix) ?: 'phacil_'; $this->generateRedisKey(); @@ -146,6 +166,24 @@ final class Session return $this->redis; } + /** + * Register save handler + * + * @return bool + */ + protected function registerSaveHandler() + { + return session_set_save_handler( + //$this->saveHandler + [$this->saveHandler, 'open'], + [$this->saveHandler, 'close'], + [$this->saveHandler, 'read'], + [$this->saveHandler, 'write'], + [$this->saveHandler, 'destroy'], + [$this->saveHandler, 'gc'] + ); + } + /** * Generate the Redis Session KEY * @@ -167,6 +205,7 @@ final class Session */ private function closeSession($force = false) { + return ; if (session_status() == PHP_SESSION_ACTIVE || $force) { session_unset(); session_destroy(); @@ -186,21 +225,7 @@ final class Session return (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || $_SERVER['SERVER_PORT'] == 443; } - /** - * Set the Redis session data - * @return void - * @since 2.0.0 - */ - public function __destruct() - { - if ($this->redis) { - $this->generateRedisKey(); - - $this->redis->set($this->redisKey, serialize($_SESSION)); - - $this->redis->expire($this->redisKey, ($this->redisExpire)); - } - } + /** * Flush all session data diff --git a/system/vendor/colinmollenhour/cache-backend-redis/.github/workflows/php.yml b/system/vendor/colinmollenhour/cache-backend-redis/.github/workflows/php.yml new file mode 100644 index 0000000..607241c --- /dev/null +++ b/system/vendor/colinmollenhour/cache-backend-redis/.github/workflows/php.yml @@ -0,0 +1,50 @@ +name: PHPUnit + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +permissions: + contents: read + +jobs: + test: + + runs-on: ubuntu-latest + + services: + redis: + image: redis + options: >- + --health-cmd "redis-cli ping" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 6379:6379 + + steps: + - uses: actions/checkout@v3 + + - name: Validate composer.json and composer.lock + run: composer validate --strict + + - name: Cache Composer packages + id: composer-cache + uses: actions/cache@v3 + with: + path: vendor + key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-php- + + - name: Install dependencies + run: composer install --prefer-dist --no-progress + + - name: Run PHPUnit test suite + run: composer run-script test + + - name: Run PHP CS Fixer + run: composer run-script php-cs-fixer -- --dry-run diff --git a/system/vendor/colinmollenhour/cache-backend-redis/.gitignore b/system/vendor/colinmollenhour/cache-backend-redis/.gitignore new file mode 100644 index 0000000..9e2f056 --- /dev/null +++ b/system/vendor/colinmollenhour/cache-backend-redis/.gitignore @@ -0,0 +1,4 @@ +.basedir +vendor +composer.lock +/.php-cs-fixer.cache diff --git a/system/vendor/colinmollenhour/cache-backend-redis/.gitmodules b/system/vendor/colinmollenhour/cache-backend-redis/.gitmodules new file mode 100644 index 0000000..cc36417 --- /dev/null +++ b/system/vendor/colinmollenhour/cache-backend-redis/.gitmodules @@ -0,0 +1,3 @@ +[submodule "lib/Credis"] + path = lib/Credis + url = https://github.com/colinmollenhour/credis diff --git a/system/vendor/colinmollenhour/cache-backend-redis/.php-cs-fixer.dist.php b/system/vendor/colinmollenhour/cache-backend-redis/.php-cs-fixer.dist.php new file mode 100644 index 0000000..a1945d5 --- /dev/null +++ b/system/vendor/colinmollenhour/cache-backend-redis/.php-cs-fixer.dist.php @@ -0,0 +1,21 @@ +setRules([ + '@PSR12' => true, + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->in([ + 'Cm/', + 'tests/', + ]) + ->name('*.php') + ->ignoreDotFiles(true) + ->ignoreVCS(true) + ); diff --git a/system/vendor/colinmollenhour/cache-backend-redis/Cm/Cache/Backend/Redis.php b/system/vendor/colinmollenhour/cache-backend-redis/Cm/Cache/Backend/Redis.php new file mode 100644 index 0000000..5d91303 --- /dev/null +++ b/system/vendor/colinmollenhour/cache-backend-redis/Cm/Cache/Backend/Redis.php @@ -0,0 +1,1416 @@ +forceStandalone = isset($options['force_standalone']) && $options['force_standalone']; + $clientOptions->connectRetries = isset($options['connect_retries']) ? (int) $options['connect_retries'] : self::DEFAULT_CONNECT_RETRIES; + $clientOptions->readTimeout = isset($options['read_timeout']) ? (float) $options['read_timeout'] : null; + $clientOptions->password = $options['password'] ?? null; + $clientOptions->username = $options['username'] ?? null; + $clientOptions->database = isset($options['database']) ? (int) $options['database'] : 0; + $clientOptions->persistent = $options['persistent'] ?? ''; + $clientOptions->timeout = $options['timeout'] ?? self::DEFAULT_CONNECT_TIMEOUT; + return $clientOptions; + } + + /** + * Construct Zend_Cache Redis backend + * @param array $options + * @throws Zend_Cache_Exception + * @throws CredisException + * @noinspection PhpMissingParentConstructorInspection + */ + public function __construct($options = array()) + { + if (empty($options['server']) && empty($options['cluster'])) { + Zend_Cache::throwException('Redis \'server\' not specified.'); + } + + $this->_clientOptions = $this->getClientOptions($options); + + // If 'sentinel_master' is specified then server is actually sentinel and master address should be fetched from server. + $sentinelMaster = empty($options['sentinel_master']) ? null : $options['sentinel_master']; + if ($sentinelMaster) { + $sentinelClientOptions = isset($options['sentinel']) && is_array($options['sentinel']) + ? $this->getClientOptions($options['sentinel'] + $options) + : $this->_clientOptions; + $servers = preg_split('/\s*,\s*/', trim($options['server']), -1, PREG_SPLIT_NO_EMPTY); + $sentinel = null; + $exception = null; + for ($i = 0; $i <= $sentinelClientOptions->connectRetries; $i++) { // Try each sentinel in round-robin fashion + foreach ($servers as $server) { + try { + $sentinelClient = new Credis_Client($server, null, $sentinelClientOptions->timeout, $sentinelClientOptions->persistent); + $sentinelClient->forceStandalone(); + $sentinelClient->setMaxConnectRetries(0); + if ($sentinelClientOptions->readTimeout) { + $sentinelClient->setReadTimeout($sentinelClientOptions->readTimeout); + } + if ($sentinelClientOptions->password) { + $sentinelClient->auth($sentinelClientOptions->password) or Zend_Cache::throwException('Unable to authenticate with the redis sentinel.'); + } + $sentinel = new Credis_Sentinel($sentinelClient); + $sentinel + ->setClientTimeout($this->_clientOptions->timeout) + ->setClientPersistent($this->_clientOptions->persistent); + $redisMaster = $sentinel->getMasterClient($sentinelMaster); + $this->_applyClientOptions($redisMaster); + + // Verify connected server is actually master as per Sentinel client spec + if (! empty($options['sentinel_master_verify'])) { + $roleData = $redisMaster->role(); + if (! $roleData || $roleData[0] != 'master') { + usleep(100000); // Sleep 100ms and try again + $redisMaster = $sentinel->getMasterClient($sentinelMaster); + $this->_applyClientOptions($redisMaster); + $roleData = $redisMaster->role(); + if (! $roleData || $roleData[0] != 'master') { + Zend_Cache::throwException('Unable to determine master redis server.'); + } + } + } + + $this->_redis = $redisMaster; + break 2; + } catch (Exception $e) { + unset($sentinelClient); + $exception = $e; + } + } + } + if (! $this->_redis) { + Zend_Cache::throwException('Unable to connect to a redis sentinel: '.$exception->getMessage(), $exception); + } + + // Optionally use read slaves - will only be used for 'load' operation + if (! empty($options['load_from_slaves'])) { + $slaves = $sentinel->getSlaveClients($sentinelMaster); + if ($slaves) { + if ($options['load_from_slaves'] == 2) { + $slaves[] = $this->_redis; // Also send reads to the master + } + $slaveSelect = isset($options['slave_select_callable']) && is_callable($options['slave_select_callable']) ? $options['slave_select_callable'] : null; + if ($slaveSelect) { + $slave = $slaveSelect($slaves, $this->_redis); + } else { + $slaveKey = array_rand($slaves); + $slave = $slaves[$slaveKey]; /* @var $slave Credis_Client */ + } + if ($slave instanceof Credis_Client && $slave !== $this->_redis) { + try { + $this->_applyClientOptions($slave, true); + $this->_slave = $slave; + } catch (Exception $e) { + // If there is a problem with first slave then skip 'load_from_slaves' option + } + } + } + } + unset($sentinel); + } + + // Instantiate Credis_Cluster + // DEPRECATED + elseif (! empty($options['cluster'])) { + $this->_setupReadWriteCluster($options); + } + + // Direct connection to single Redis server and optional slaves + else { + $port = $options['port'] ?? 6379; + $this->_redis = new Credis_Client($options['server'], $port, $this->_clientOptions->timeout, $this->_clientOptions->persistent); + $this->_applyClientOptions($this->_redis); + + // Support loading from a replication slave + if (isset($options['load_from_slave'])) { + if (is_array($options['load_from_slave'])) { + if (isset($options['load_from_slave']['server'])) { // Single slave + $server = $options['load_from_slave']['server']; + $port = $options['load_from_slave']['port']; + $clientOptions = $this->getClientOptions($options['load_from_slave'] + $options); + $totalServers = 2; + } else { // Multiple slaves + $slaveKey = array_rand($options['load_from_slave']); + $slave = $options['load_from_slave'][$slaveKey]; + $server = $slave['server']; + $port = $slave['port']; + $clientOptions = $this->getClientOptions($slave + $options); + $totalServers = count($options['load_from_slave']) + 1; + } + } else { // String + $server = $options['load_from_slave']; + $port = 6379; + $clientOptions = $this->_clientOptions; + + // If multiple addresses are given, split and choose a random one + if (strpos($server, ',') !== false) { + $slaves = preg_split('/\s*,\s*/', $server, -1, PREG_SPLIT_NO_EMPTY); + $slaveKey = array_rand($slaves); + $server = $slaves[$slaveKey]; + $port = null; + $totalServers = count($slaves) + 1; + } else { + $totalServers = 2; + } + } + // Skip setting up slave if master is not write only, and it is randomly chosen to be the read server + $masterWriteOnly = isset($options['master_write_only']) ? (int) $options['master_write_only'] : false; + if (is_string($server) && $server && ! (!$masterWriteOnly && rand(1, $totalServers) === 1)) { + try { + $slave = new Credis_Client($server, $port, $clientOptions->timeout, $clientOptions->persistent); + $this->_applyClientOptions($slave, true, $clientOptions); + $this->_slave = $slave; + } catch (Exception $e) { + // Slave will not be used + } + } + } + } + + if (isset($options['notMatchingTags'])) { + $this->_notMatchingTags = (bool) $options['notMatchingTags']; + } + + if (isset($options['compress_tags'])) { + $this->_compressTags = (int) $options['compress_tags']; + } + + if (isset($options['compress_data'])) { + $this->_compressData = (int) $options['compress_data']; + } + + if (isset($options['lifetimelimit'])) { + $this->_lifetimelimit = (int) min($options['lifetimelimit'], self::MAX_LIFETIME); + } + + if (isset($options['compress_threshold'])) { + $this->_compressThreshold = (int) $options['compress_threshold']; + if ($this->_compressThreshold < 1) { + $this->_compressThreshold = 1; + } + } + + if (isset($options['automatic_cleaning_factor'])) { + $this->_options['automatic_cleaning_factor'] = (int) $options['automatic_cleaning_factor']; + } else { + $this->_options['automatic_cleaning_factor'] = 0; + } + + if (isset($options['compression_lib'])) { + $this->_compressionLib = (string) $options['compression_lib']; + } elseif (function_exists('snappy_compress')) { + $this->_compressionLib = 'snappy'; + } elseif (function_exists('lz4_compress')) { + $version = phpversion("lz4"); + if (version_compare($version, "0.3.0") < 0) { + $this->_compressTags = $this->_compressTags > 1; + $this->_compressData = $this->_compressData > 1; + } + $this->_compressionLib = 'l4z'; + } elseif (function_exists('zstd_compress')) { + $version = phpversion("zstd"); + if (version_compare($version, "0.4.13") < 0) { + $this->_compressTags = $this->_compressTags > 1; + $this->_compressData = $this->_compressData > 1; + } + $this->_compressionLib = 'zstd'; + } elseif (function_exists('lzf_compress')) { + $this->_compressionLib = 'lzf'; + } else { + $this->_compressionLib = 'gzip'; + } + $this->_compressPrefix = substr($this->_compressionLib, 0, 2).self::COMPRESS_PREFIX; + + if (isset($options['sunion_chunk_size']) && $options['sunion_chunk_size'] > 0) { + $this->_sunionChunkSize = (int) $options['sunion_chunk_size']; + } + + if (isset($options['remove_chunk_size']) && $options['remove_chunk_size'] > 0) { + $this->_removeChunkSize = (int) $options['remove_chunk_size']; + } + + if (isset($options['use_lua'])) { + $this->_useLua = (bool) $options['use_lua']; + } + + if (isset($options['lua_max_c_stack'])) { + $this->_luaMaxCStack = (int) $options['lua_max_c_stack']; + } + + if (isset($options['retry_reads_on_master'])) { + $this->_retryReadsOnMaster = (bool) $options['retry_reads_on_master']; + } + + if (isset($options['auto_expire_lifetime'])) { + $this->_autoExpireLifetime = (int) $options['auto_expire_lifetime']; + } + + if (isset($options['auto_expire_pattern'])) { + $this->_autoExpirePattern = (string) $options['auto_expire_pattern']; + } + + if (isset($options['auto_expire_refresh_on_load'])) { + $this->_autoExpireRefreshOnLoad = (bool) $options['auto_expire_refresh_on_load']; + } + } + + /** + * Apply common configuration to client instances. + * + * @param Credis_Client $client + * @param bool $forceSelect + * @param null|stdClass $clientOptions + * @throws CredisException + * @throws Zend_Cache_Exception + */ + protected function _applyClientOptions(Credis_Client $client, $forceSelect = false, $clientOptions = null) + { + if ($clientOptions === null) { + $clientOptions = $this->_clientOptions; + } + + if ($clientOptions->forceStandalone) { + $client->forceStandalone(); + } + + $client->setMaxConnectRetries($clientOptions->connectRetries); + + if ($clientOptions->readTimeout) { + $client->setReadTimeout($clientOptions->readTimeout); + } + + if ($clientOptions->password) { + if ($clientOptions->username) { + $client->auth($clientOptions->password, $clientOptions->username) or Zend_Cache::throwException('Unable to authenticate with the redis server.'); + } else { + $client->auth($clientOptions->password) or Zend_Cache::throwException('Unable to authenticate with the redis server.'); + } + } + + // Always select database when persistent is used in case connection is re-used by other clients + if ($forceSelect || $clientOptions->database || $client->getPersistence()) { + $client->select($clientOptions->database) or Zend_Cache::throwException('The redis database could not be selected.'); + } + } + + /** + * @param $options + * @throws CredisException + * @throws Zend_Cache_Exception + * @deprecated - Previously this setup an instance of Credis_Cluster but this class was not complete or flawed + */ + protected function _setupReadWriteCluster($options) + { + if (!empty($options['cluster']['master'])) { + foreach ($options['cluster']['master'] as $masterNode) { + if (empty($masterNode['server']) || empty($masterNode['port'])) { + continue; + } + + $this->_redis = new Credis_Client( + $masterNode['host'], + $masterNode['port'], + $masterNode['timeout'] ?? 2.5, + $masterNode['persistent'] ?? '' + ); + $this->_applyClientOptions($this->_redis); + break; + } + } + + if (!empty($options['cluster']['slave'])) { + $slaveKey = array_rand($options['cluster']['slave']); + $slave = $options['cluster']['slave'][$slaveKey]; + $this->_slave = new Credis_Client( + $slave['host'], + $slave['port'], + $slave['timeout'] ?? 2.5, + $slave['persistent'] ?? '' + ); + $this->_applyClientOptions($this->_redis, true); + } + } + + /** + * Load value with given id from cache + * + * @param string $id Cache id + * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested + * @return bool|string + * @throws CredisException + */ + public function load($id, $doNotTestCacheValidity = false) + { + if ($this->_slave) { + try { + $data = $this->_slave->hGet(self::PREFIX_KEY.$id, self::FIELD_DATA); + + // Prevent compounded effect of cache flood on asynchronously replicating master/slave setup + if ($this->_retryReadsOnMaster && $data === false) { + $data = $this->_redis->hGet(self::PREFIX_KEY.$id, self::FIELD_DATA); + } + } catch (CredisException $e) { + // Always retry reads on master when dataset is loading on slave + if ($e->getMessage() === 'LOADING Redis is loading the dataset in memory') { + $data = $this->_redis->hGet(self::PREFIX_KEY.$id, self::FIELD_DATA); + } else { + throw $e; + } + } + } else { + try { + $data = $this->_redis->hGet(self::PREFIX_KEY.$id, self::FIELD_DATA); + } catch (CredisException $e) { + // Retry once after 1 second when dataset is loading + if ($e->getMessage() === 'LOADING Redis is loading the dataset in memory') { + sleep(1); + $data = $this->_redis->hGet(self::PREFIX_KEY.$id, self::FIELD_DATA); + } else { + throw $e; + } + } + } + if ($data === null || $data === false || is_object($data)) { + return false; + } + + $decoded = $this->_decodeData($data); + + if ($this->_autoExpireLifetime === 0 || !$this->_autoExpireRefreshOnLoad) { + return $decoded; + } + + $matches = $this->_matchesAutoExpiringPattern($id); + if (!$matches) { + return $decoded; + } + + $this->_redis->expire(self::PREFIX_KEY.$id, min($this->_autoExpireLifetime, self::MAX_LIFETIME)); + + return $decoded; + } + + /** + * Test if a cache is available or not (for the given id) + * + * @param string $id Cache id + * @return bool|int False if record is not available or "last modified" timestamp of the available cache record + */ + public function test($id) + { + // Don't use slave for this since `test` is usually used for locking + $mtime = $this->_redis->hGet(self::PREFIX_KEY.$id, self::FIELD_MTIME); + return ($mtime ? (int)$mtime : false); + } + + /** + * Get the lifetime + * + * if $specificLifetime is not false, the given specific lifetime is used + * else, the global lifetime is used + * + * @param int $specificLifetime + * @return int Cache lifetime + */ + public function getLifetime($specificLifetime) + { + // Lifetimes set via Layout XMLs get parsed as string so bool(false) becomes string("false") + if ($specificLifetime === 'false') { + $specificLifetime = false; + } + + return parent::getLifetime($specificLifetime); + } + + /** + * Save some string datas into a cache record + * + * Note : $data is always "string" (serialization is done by the + * core not by the backend) + * + * @param string $data Datas to cache + * @param string $id Cache id + * @param array $tags Array of strings, the cache record will be tagged by each string entry + * @param bool|int $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime) + * @throws CredisException + * @return boolean True if no problem + */ + public function save($data, $id, $tags = array(), $specificLifetime = false) + { + if (!is_array($tags)) { + $tags = $tags ? array($tags) : array(); + } else { + $tags = array_flip(array_flip($tags)); + } + + $lifetime = $this->_getAutoExpiringLifetime($this->getLifetime($specificLifetime), $id); + $lifetime = $lifetime === null ? $lifetime : (int) $lifetime; + + if ($this->_useLua) { + $sArgs = array( + self::PREFIX_KEY, + self::FIELD_DATA, + self::FIELD_TAGS, + self::FIELD_MTIME, + self::FIELD_INF, + self::SET_TAGS, + self::PREFIX_TAG_IDS, + self::SET_IDS, + $id, + $this->_encodeData($data, $this->_compressData), + $this->_encodeData(implode(',', $tags), $this->_compressTags), + time(), + $lifetime ? 0 : 1, + min($lifetime, self::MAX_LIFETIME), + $this->_notMatchingTags ? 1 : 0 + ); + + $res = $this->_redis->evalSha(self::LUA_SAVE_SH1, $tags, $sArgs); + if (is_null($res)) { + $script = + "local oldTags = redis.call('HGET', ARGV[1]..ARGV[9], ARGV[3]) ". + "redis.call('HMSET', ARGV[1]..ARGV[9], ARGV[2], ARGV[10], ARGV[3], ARGV[11], ARGV[4], ARGV[12], ARGV[5], ARGV[13]) ". + "if (ARGV[13] == '0') then ". + "redis.call('EXPIRE', ARGV[1]..ARGV[9], ARGV[14]) ". + "end ". + "if next(KEYS) ~= nil then ". + "redis.call('SADD', ARGV[6], unpack(KEYS)) ". + "for _, tagname in ipairs(KEYS) do ". + "redis.call('SADD', ARGV[7]..tagname, ARGV[9]) ". + "end ". + "end ". + "if (ARGV[15] == '1') then ". + "redis.call('SADD', ARGV[8], ARGV[9]) ". + "end ". + "if (oldTags ~= false) then ". + "return oldTags ". + "else ". + "return '' ". + "end"; + $res = $this->_redis->eval($script, $tags, $sArgs); + } + + // Process removed tags if cache entry already existed + if ($res) { + $oldTags = explode(',', $this->_decodeData($res)); + if ($remTags = ($oldTags ? array_diff($oldTags, $tags) : false)) { + // Update the id list for each tag + foreach ($remTags as $tag) { + $this->_redis->sRem(self::PREFIX_TAG_IDS . $tag, $id); + } + } + } + + return true; + } + + // Get list of tags previously assigned + $oldTags = $this->_decodeData($this->_redis->hGet(self::PREFIX_KEY.$id, self::FIELD_TAGS)); + $oldTags = $oldTags ? explode(',', $oldTags) : array(); + + $this->_redis->pipeline()->multi(); + + // Set the data + $result = $this->_redis->hMSet(self::PREFIX_KEY.$id, array( + self::FIELD_DATA => $this->_encodeData($data, $this->_compressData), + self::FIELD_TAGS => $this->_encodeData(implode(',', $tags), $this->_compressTags), + self::FIELD_MTIME => time(), + self::FIELD_INF => is_null($lifetime) ? 1 : 0, + )); + if (! $result) { + throw new CredisException("Could not set cache key $id"); + } + + // Set expiration if specified + if ($lifetime !== false && !is_null($lifetime)) { + $this->_redis->expire(self::PREFIX_KEY.$id, min($lifetime, self::MAX_LIFETIME)); + } + + // Process added tags + if ($tags) { + // Update the list with all the tags + $this->_redis->sAdd(self::SET_TAGS, $tags); + + // Update the id list for each tag + foreach ($tags as $tag) { + $this->_redis->sAdd(self::PREFIX_TAG_IDS . $tag, $id); + } + } + + // Process removed tags + if ($remTags = ($oldTags ? array_diff($oldTags, $tags) : false)) { + // Update the id list for each tag + foreach ($remTags as $tag) { + $this->_redis->sRem(self::PREFIX_TAG_IDS . $tag, $id); + } + } + + // Update the list with all the ids + if ($this->_notMatchingTags) { + $this->_redis->sAdd(self::SET_IDS, $id); + } + + $this->_redis->exec(); + + return true; + } + + /** + * Remove a cache record + * + * @param string $id Cache id + * @return boolean True if no problem + */ + public function remove($id) + { + // Get list of tags for this id + $tags = explode(',', $this->_decodeData($this->_redis->hGet(self::PREFIX_KEY.$id, self::FIELD_TAGS))); + + $this->_redis->pipeline()->multi(); + + // Remove data + $this->_redis->unlink(self::PREFIX_KEY.$id); + + // Remove id from list of all ids + if ($this->_notMatchingTags) { + $this->_redis->sRem(self::SET_IDS, $id); + } + + // Update the id list for each tag + foreach ($tags as $tag) { + $this->_redis->sRem(self::PREFIX_TAG_IDS . $tag, $id); + } + + $result = $this->_redis->exec(); + + return isset($result[0]) && (bool)$result[0]; + } + + /** + * @param array $tags + * @throws Zend_Cache_Exception + */ + protected function _removeByNotMatchingTags($tags) + { + $ids = $this->getIdsNotMatchingTags($tags); + $this->_removeByIds($ids); + } + + /** + * @param array $tags + */ + protected function _removeByMatchingTags($tags) + { + $ids = $this->getIdsMatchingTags($tags); + $this->_removeByIds($ids); + } + + /** + * @param array $ids + */ + protected function _removeByIds($ids) + { + if ($ids) { + $ids = array_chunk($ids, $this->_removeChunkSize); + foreach ($ids as $idsChunk) { + $this->_redis->pipeline()->multi(); + + // Remove data + $this->_redis->unlink($this->_preprocessIds($idsChunk)); + + // Remove ids from list of all ids + if ($this->_notMatchingTags) { + $this->_redis->sRem(self::SET_IDS, $idsChunk); + } + + $this->_redis->exec(); + } + } + } + + /** + * @param array $tags + */ + protected function _removeByMatchingAnyTags($tags) + { + if ($this->_useLua) { + $tags = array_chunk($tags, $this->_sunionChunkSize); + foreach ($tags as $chunk) { + $args = array(self::PREFIX_TAG_IDS, self::PREFIX_KEY, self::SET_TAGS, self::SET_IDS, ($this->_notMatchingTags ? 1 : 0), (int) $this->_luaMaxCStack); + if (! $this->_redis->evalSha(self::LUA_CLEAN_SH1, $chunk, $args)) { + $script = + "for i = 1, #KEYS, ARGV[6] do " . + "local prefixedTags = {} " . + "for x, tag in ipairs(KEYS) do " . + "prefixedTags[x] = ARGV[1]..tag " . + "end " . + "local keysToDel = redis.call('SUNION', unpack(prefixedTags, i, math.min(#prefixedTags, i + ARGV[6] - 1))) " . + "for _, keyname in ipairs(keysToDel) do " . + "redis.call('UNLINK', ARGV[2]..keyname) " . + "if (ARGV[5] == '1') then " . + "redis.call('SREM', ARGV[4], keyname) " . + "end " . + "end " . + "redis.call('UNLINK', unpack(prefixedTags, i, math.min(#prefixedTags, i + ARGV[6] - 1))) " . + "redis.call('SREM', ARGV[3], unpack(KEYS, i, math.min(#KEYS, i + ARGV[6] - 1))) " . + "end " . + "return true"; + $this->_redis->eval($script, $chunk, $args); + } + } + return; + } + + $ids = $this->getIdsMatchingAnyTags($tags); + + $this->_redis->pipeline()->multi(); + + if ($ids) { + $ids = array_chunk($ids, $this->_removeChunkSize); + foreach ($ids as $idsChunk) { + // Remove data + $this->_redis->unlink($this->_preprocessIds($idsChunk)); + + // Remove ids from list of all ids + if ($this->_notMatchingTags) { + $this->_redis->sRem(self::SET_IDS, $idsChunk); + } + + // Commit each chunk in a separate transaction + if (count($ids) > 1) { + $this->_redis->pipeline()->exec(); + $this->_redis->pipeline()->multi(); + } + } + } + + // Remove tag id lists + $this->_redis->unlink($this->_preprocessTagIds($tags)); + + // Remove tags from list of tags + $this->_redis->sRem(self::SET_TAGS, $tags); + + $this->_redis->exec(); + } + + /** + * Clean up tag id lists since as keys expire the ids remain in the tag id lists + */ + protected function _collectGarbage() + { + // Clean up expired keys from tag id set and global id set + + if ($this->_useLua) { + $sArgs = array(self::PREFIX_KEY, self::SET_TAGS, self::SET_IDS, self::PREFIX_TAG_IDS, ($this->_notMatchingTags ? 1 : 0)); + $allTags = (array) $this->_redis->sMembers(self::SET_TAGS); + $tagsCount = count($allTags); + $counter = 0; + $tagsBatch = array(); + foreach ($allTags as $tag) { + $tagsBatch[] = $tag; + $counter++; + if (count($tagsBatch) == 10 || $counter == $tagsCount) { + if (! $this->_redis->evalSha(self::LUA_GC_SH1, $tagsBatch, $sArgs)) { + $script = + "local tagKeys = {} ". + "local expired = {} ". + "local expiredCount = 0 ". + "local notExpiredCount = 0 ". + "for _, tagName in ipairs(KEYS) do ". + "tagKeys = redis.call('SMEMBERS', ARGV[4]..tagName) ". + "for __, keyName in ipairs(tagKeys) do ". + "if (redis.call('EXISTS', ARGV[1]..keyName) == 0) then ". + "expiredCount = expiredCount + 1 ". + "expired[expiredCount] = keyName ". + /* Redis Lua scripts have a hard limit of 8000 parameters per command */ + "if (expiredCount == 7990) then ". + "redis.call('SREM', ARGV[4]..tagName, unpack(expired)) ". + "if (ARGV[5] == '1') then ". + "redis.call('SREM', ARGV[3], unpack(expired)) ". + "end ". + "expiredCount = 0 ". + "expired = {} ". + "end ". + "else ". + "notExpiredCount = notExpiredCount + 1 ". + "end ". + "end ". + "if (expiredCount > 0) then ". + "redis.call('SREM', ARGV[4]..tagName, unpack(expired)) ". + "if (ARGV[5] == '1') then ". + "redis.call('SREM', ARGV[3], unpack(expired)) ". + "end ". + "end ". + "if (notExpiredCount == 0) then ". + "redis.call ('UNLINK', ARGV[4]..tagName) ". + "redis.call ('SREM', ARGV[2], tagName) ". + "end ". + "expired = {} ". + "expiredCount = 0 ". + "notExpiredCount = 0 ". + "end ". + "return true"; + $this->_redis->eval($script, $tagsBatch, $sArgs); + } + $tagsBatch = array(); + /* Give Redis some time to handle other requests */ + usleep(20000); + } + } + return; + } + + $exists = array(); + $tags = (array) $this->_redis->sMembers(self::SET_TAGS); + foreach ($tags as $tag) { + // Get list of expired ids for each tag + $tagMembers = $this->_redis->sMembers(self::PREFIX_TAG_IDS . $tag); + $numTagMembers = count($tagMembers); + $expired = array(); + $numExpired = $numNotExpired = 0; + if ($numTagMembers) { + while ($id = array_pop($tagMembers)) { + if (! isset($exists[$id])) { + $exists[$id] = $this->_redis->exists(self::PREFIX_KEY.$id); + } + if ($exists[$id]) { + $numNotExpired++; + } else { + $numExpired++; + $expired[] = $id; + + // Remove incrementally to reduce memory usage + if (count($expired) % 100 == 0 && $numNotExpired > 0) { + $this->_redis->sRem(self::PREFIX_TAG_IDS . $tag, $expired); + if ($this->_notMatchingTags) { // Clean up expired ids from ids set + $this->_redis->sRem(self::SET_IDS, $expired); + } + $expired = array(); + } + } + } + if (! count($expired)) { + continue; + } + } + + // Remove empty tags or completely expired tags + if ($numExpired == $numTagMembers) { + $this->_redis->unlink(self::PREFIX_TAG_IDS . $tag); + $this->_redis->sRem(self::SET_TAGS, $tag); + } + // Clean up expired ids from tag ids set + elseif (count($expired)) { + $this->_redis->sRem(self::PREFIX_TAG_IDS . $tag, $expired); + if ($this->_notMatchingTags) { // Clean up expired ids from ids set + $this->_redis->sRem(self::SET_IDS, $expired); + } + } + unset($expired); + } + + // TODO + // Clean up global list of ids for ids with no tag +// if ($this->_notMatchingTags) { +// } + } + + /** + * Clean some cache records + * + * Available modes are : + * 'all' (default) => remove all cache entries ($tags is not used) + * 'old' => runs _collectGarbage() + * 'matchingTag' => supported + * 'notMatchingTag' => supported + * 'matchingAnyTag' => supported + * + * @param string $mode Clean mode + * @param array $tags Array of tags + * @throws Zend_Cache_Exception + * @return boolean True if no problem + */ + public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array()) + { + if ($tags && ! is_array($tags)) { + $tags = array($tags); + } + + try { + if ($mode == Zend_Cache::CLEANING_MODE_ALL) { + return $this->_redis->flushDb(); + } + if ($mode == Zend_Cache::CLEANING_MODE_OLD) { + $this->_collectGarbage(); + return true; + } + if (! count($tags)) { + return true; + } + switch ($mode) { + case Zend_Cache::CLEANING_MODE_MATCHING_TAG: + + $this->_removeByMatchingTags($tags); + break; + + case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG: + + $this->_removeByNotMatchingTags($tags); + break; + + case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG: + + $this->_removeByMatchingAnyTags($tags); + break; + + default: + Zend_Cache::throwException('Invalid mode for clean() method: '.$mode); + } + } catch (CredisException $e) { + Zend_Cache::throwException('Error cleaning cache by mode '.$mode.': '.$e->getMessage(), $e); + } + return true; + } + + /** + * Return true if the automatic cleaning is available for the backend + * + * @return boolean + */ + public function isAutomaticCleaningAvailable() + { + return true; + } + + /** + * Set the frontend directives + * + * @param array $directives Assoc of directives + * @throws Zend_Cache_Exception + * @return void + */ + public function setDirectives($directives) + { + parent::setDirectives($directives); + $lifetime = $this->getLifetime(false); + if ($lifetime > self::MAX_LIFETIME) { + Zend_Cache::throwException('Redis backend has a limit of 30 days (2592000 seconds) for the lifetime'); + } + } + + /** + * Get the auto expiring lifetime. + * + * Mainly a workaround for the issues that arise due to the fact that + * Magento's Enterprise_PageCache module doesn't set any expiry. + * + * @param int $lifetime + * @param string $id + * @return int Cache lifetime + */ + protected function _getAutoExpiringLifetime($lifetime, $id) + { + if ($lifetime || !$this->_autoExpireLifetime) { + // If it's already truthy, or there's no auto expire go with it. + return $lifetime; + } + + $matches = $this->_matchesAutoExpiringPattern($id); + if (!$matches) { + // Only apply auto expire for keys that match the pattern + return $lifetime; + } + + if ($this->_autoExpireLifetime > 0) { + // Return the auto expire lifetime if set + return $this->_autoExpireLifetime; + } + + // Return whatever it was set to. + return $lifetime; + } + + protected function _matchesAutoExpiringPattern($id) + { + $matches = array(); + preg_match($this->_autoExpirePattern, $id, $matches); + + return !empty($matches); + } + + /** + * Return an array of stored cache ids + * + * @return array array of stored cache ids (string) + */ + public function getIds() + { + if ($this->_notMatchingTags) { + return (array) $this->_redis->sMembers(self::SET_IDS); + } else { + $keys = $this->_redis->keys(self::PREFIX_KEY . '*'); + $prefixLen = strlen(self::PREFIX_KEY); + foreach ($keys as $index => $key) { + $keys[$index] = substr($key, $prefixLen); + } + return $keys; + } + } + + /** + * Return an array of stored tags + * + * @return array array of stored tags (string) + */ + public function getTags() + { + return (array) $this->_redis->sMembers(self::SET_TAGS); + } + + /** + * Return an array of stored cache ids which match given tags + * + * In case of multiple tags, a logical AND is made between tags + * + * @param array $tags array of tags + * @return array array of matching cache ids (string) + */ + public function getIdsMatchingTags($tags = array()) + { + if ($tags) { + return (array) $this->_redis->sInter($this->_preprocessTagIds($tags)); + } + return array(); + } + + /** + * Return an array of stored cache ids which don't match given tags + * + * In case of multiple tags, a negated logical AND is made between tags + * + * @param array $tags array of tags + * @return array array of not matching cache ids (string) + * @throws Zend_Cache_Exception + */ + public function getIdsNotMatchingTags($tags = array()) + { + if (! $this->_notMatchingTags) { + Zend_Cache::throwException("notMatchingTags is currently disabled."); + } + if ($tags) { + return (array) $this->_redis->sDiff(self::SET_IDS, $this->_preprocessTagIds($tags)); + } + return (array) $this->_redis->sMembers(self::SET_IDS); + } + + /** + * Return an array of stored cache ids which match any given tags + * + * In case of multiple tags, a logical OR is made between tags + * + * @param array $tags array of tags + * @return array array of any matching cache ids (string) + */ + public function getIdsMatchingAnyTags($tags = array()) + { + $result = array(); + if ($tags) { + $chunks = array_chunk($tags, $this->_sunionChunkSize); + foreach ($chunks as $chunk) { + $result = array_merge($result, (array) $this->_redis->sUnion($this->_preprocessTagIds($chunk))); + } + if (count($chunks) > 1) { + $result = array_unique($result); // since we are chunking requests, we must de-duplicate member names + } + } + return $result; + } + + /** + * Return redis server info and stats + * + * @return array + */ + public function getInfo() + { + return $this->_redis->info(); + } + + /** + * Return the filling percentage of the backend storage + * + * @throws Zend_Cache_Exception + * @return int integer between 0 and 100 + */ + public function getFillingPercentage() + { + try { + $maxMem = $this->_redis->config('GET', 'maxmemory'); + } catch (CredisException $e) { + throw new Zend_Cache_Exception($e->getMessage(), 0, $e); + } + if (0 == (int) $maxMem['maxmemory']) { + return 1; + } + $info = $this->_redis->info(); + return (int) round( + ($info['used_memory']/$maxMem['maxmemory']*100) + ); + } + + /** + * Return the keyspace hit/miss percentage of the backend storage + * + * @throws Zend_Cache_Exception + * @return int integer between 0 and 100 + */ + public function getHitMissPercentage() + { + try { + $info = $this->_redis->info(); + } catch (CredisException $e) { + throw new Zend_Cache_Exception($e->getMessage(), 0, $e); + } + $hits = $info['keyspace_hits']; + $misses = $info['keyspace_misses']; + $total = $misses+$hits; + $percentage = 0; + if ($total > 0) { + $percentage = round($hits*100/$total); + } + return $percentage; + } + + /** + * Return an array of metadatas for the given cache id + * + * The array must include these keys : + * - expire : the expire timestamp + * - tags : a string array of tags + * - mtime : timestamp of last modification time + * + * @param string $id cache id + * @return array|bool array of metadatas (false if the cache id is not found) + */ + public function getMetadatas($id) + { + list($tags, $mtime, $inf) = array_values( + $this->_redis->hMGet(self::PREFIX_KEY.$id, array(self::FIELD_TAGS, self::FIELD_MTIME, self::FIELD_INF)) + ); + if (! $mtime) { + return false; + } + $tags = explode(',', $this->_decodeData($tags)); + $expire = $inf === '1' ? false : time() + $this->_redis->ttl(self::PREFIX_KEY.$id); + + return array( + 'expire' => $expire, + 'tags' => $tags, + 'mtime' => $mtime, + ); + } + + /** + * Give (if possible) an extra lifetime to the given cache id + * + * @param string $id cache id + * @param int $extraLifetime + * @return boolean true if ok + */ + public function touch($id, $extraLifetime) + { + $inf = $this->_redis->hGet(self::PREFIX_KEY.$id, self::FIELD_INF); + if ($inf === '0') { + $expireAt = time() + $this->_redis->ttl(self::PREFIX_KEY.$id) + $extraLifetime; + return (bool) $this->_redis->expireAt(self::PREFIX_KEY.$id, $expireAt); + } + return false; + } + + /** + * Return an associative array of capabilities (booleans) of the backend + * + * The array must include these keys : + * - automatic_cleaning (is automating cleaning necessary) + * - tags (are tags supported) + * - expired_read (is it possible to read expired cache records + * (for doNotTestCacheValidity option for example)) + * - priority does the backend deal with priority when saving + * - infinite_lifetime (is infinite lifetime can work with this backend) + * - get_list (is it possible to get the list of cache ids and the complete list of tags) + * + * @return array associative of with capabilities + */ + public function getCapabilities() + { + return array( + 'automatic_cleaning' => ($this->_options['automatic_cleaning_factor'] > 0), + 'tags' => true, + 'expired_read' => false, + 'priority' => false, + 'infinite_lifetime' => true, + 'get_list' => true, + ); + } + + /** + * @param string $data + * @param int $level + * @throws CredisException + * @return string + */ + protected function _encodeData($data, $level) + { + if ($this->_compressionLib && $level !== 0 && strlen($data) >= $this->_compressThreshold) { + switch($this->_compressionLib) { + case 'snappy': $data = snappy_compress($data); + break; + case 'lzf': $data = lzf_compress($data); + break; + case 'l4z': $data = lz4_compress($data, $level); + break; + case 'zstd': $data = zstd_compress($data, $level); + break; + case 'gzip': $data = gzcompress($data, $level); + break; + default: throw new CredisException("Unrecognized 'compression_lib'."); + } + if (! $data) { + throw new CredisException("Could not compress cache data."); + } + return $this->_compressPrefix.$data; + } + return $data; + } + + /** + * @param bool|string $data + * @return string + */ + protected function _decodeData($data) + { + try { + if (substr($data, 2, 3) == self::COMPRESS_PREFIX) { + switch(substr($data, 0, 2)) { + case 'sn': return snappy_uncompress(substr($data, 5)); + case 'lz': return lzf_decompress(substr($data, 5)); + case 'l4': return lz4_uncompress(substr($data, 5)); + case 'zs': return zstd_uncompress(substr($data, 5)); + case 'gz': case 'zc': return gzuncompress(substr($data, 5)); + } + } + } catch(Exception $e) { + // Some applications will capture the php error that these functions can sometimes generate and throw it as an Exception + $data = false; + } + return $data; + } + + /** + * @param $item + * @param $index + * @param $prefix + */ + protected function _preprocess(&$item, $index, $prefix) + { + $item = $prefix . $item; + } + + /** + * @param $ids + * @return array + */ + protected function _preprocessIds($ids) + { + array_walk($ids, array($this, '_preprocess'), self::PREFIX_KEY); + return $ids; + } + + /** + * @param $tags + * @return array + */ + protected function _preprocessTagIds($tags) + { + array_walk($tags, array($this, '_preprocess'), self::PREFIX_TAG_IDS); + return $tags; + } + + /** + * Required to pass unit tests + * + * @param string $id + * @return void + */ + public function ___expire($id) + { + $this->_redis->unlink(self::PREFIX_KEY.$id); + } + + /** + * Only for unit tests + */ + public function ___scriptFlush() + { + $this->_redis->script('flush'); + } + + /** + * @return array + */ + public function ___checkScriptsExist() + { + $scripts = []; + $result = $this->_redis->script('exists', self::LUA_SAVE_SH1, self::LUA_CLEAN_SH1, self::LUA_GC_SH1); + if ($result[0] ?? false) { + $scripts[] = 'save'; + } + if ($result[1] ?? false) { + $scripts[] = 'clean'; + } + if ($result[2] ?? false) { + $scripts[] = 'garbage'; + } + return $scripts; + } +} diff --git a/system/vendor/colinmollenhour/cache-backend-redis/Dockerfile b/system/vendor/colinmollenhour/cache-backend-redis/Dockerfile new file mode 100644 index 0000000..024fb99 --- /dev/null +++ b/system/vendor/colinmollenhour/cache-backend-redis/Dockerfile @@ -0,0 +1,2 @@ +FROM php:8.1-alpine +COPY --from=composer /usr/bin/composer /usr/bin/composer diff --git a/system/vendor/colinmollenhour/cache-backend-redis/ISSUE_TEMPLATE.md b/system/vendor/colinmollenhour/cache-backend-redis/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..e0e5c33 --- /dev/null +++ b/system/vendor/colinmollenhour/cache-backend-redis/ISSUE_TEMPLATE.md @@ -0,0 +1,2 @@ + + diff --git a/system/vendor/colinmollenhour/cache-backend-redis/LICENSE b/system/vendor/colinmollenhour/cache-backend-redis/LICENSE new file mode 100644 index 0000000..60f736f --- /dev/null +++ b/system/vendor/colinmollenhour/cache-backend-redis/LICENSE @@ -0,0 +1,29 @@ +Copyright (c) 2013, Colin Mollenhour +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * The name of Colin Mollenhour may not be used to endorse or promote products + derived from this software without specific prior written permission. + + * If any files are modified, you must cause the modified files to carry prominent + notices stating that you changed the files and the date of any change. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/system/vendor/colinmollenhour/cache-backend-redis/README.md b/system/vendor/colinmollenhour/cache-backend-redis/README.md new file mode 100644 index 0000000..27ddd04 --- /dev/null +++ b/system/vendor/colinmollenhour/cache-backend-redis/README.md @@ -0,0 +1,241 @@ +This `Zend_Cache` backend allows you to use a Redis server as a central cache storage. Tags are fully supported +without the use of `TwoLevels` cache so this backend is great for use on a single machine or in a cluster. +Works with any Zend Framework project including all versions of Magento! + +# FEATURES + + - Uses the [phpredis PECL extension](https://github.com/nicolasff/phpredis) for best performance (requires **master** branch or tagged version newer than Aug 19 2011). + - Falls back to standalone PHP if phpredis isn't available using the [Credis](https://github.com/colinmollenhour/credis) library. + - Tagging is fully supported, implemented using the Redis "set" and "hash" data types for efficient tag management. + - Key expiration is handled automatically by Redis. + - Supports unix socket connection for even better performance on a single machine. + - Supports configurable compression for memory savings. Can choose between gzip, lzf and snappy and can change configuration without flushing cache. + - Uses transactions to prevent race conditions between saves, cleans or removes causing unexpected results. + - Supports a configurable "auto expiry lifetime" which, if set, will be used as the TTL when the key otherwise wouldn't expire. In combination with "auto expiry refresh on load" offers a more sane cache management strategy for Magento's `Enterprise_PageCache` module. + - __Unit tested!__ + +# REQUIREMENTS + +As this backend uses [Credis](https://github.com/colinmollenhour/credis) there are no additional requirements, but for improved performance you can install [phpredis](https://github.com/nicolasff/phpredis) which is a compiled extension. + + * For 2.4 support you must use the "master" branch or a tagged version newer than Aug 19, 2011. + * phpredis is optional, but it is much faster than standalone mode + * phpredis does not support setting read timeouts at the moment (see pull request #260). If you receive read errors (“read error on connection”), this + might be the reason. + +# INSTALLATION + +Add the package as a dependency to your project with Composer. + +```shell +composer require colinmollenhour/cache-backend-redis +``` + +### modman + +It is not the recommended method, but you may install via [modman](https://github.com/colinmollenhour/modman): + +```shell +modman clone https://github.com/colinmollenhour/Cm_Cache_Backend_Redis +``` + +# CONFIGURATION + +These examples assume you are using Magento, but the configuration can just be passed to the constructor as a PHP +array with the same key names as seen in the examples. + +Edit `app/etc/local.xml` to configure: + + + + Cm_Cache_Backend_Redis + + 127.0.0.1 + 6379 + + 0 + + 0 + 1 + 10 + 0 + 1 + 1 + 20480 + gzip + 0 + tcp://redis-slave:6379 + + + + + + Cm_Cache_Backend_Redis + + 127.0.0.1 + 6379 + + 1 + + 0 + 1 + 57600 + 0 + + + + + +## High Availability and Load Balancing Support + +### Redis Sentinel + +You may achieve high availability and load balancing using [Redis Sentinel](http://redis.io/topics/sentinel). To enable use of Redis Sentinel the `server` +specified should be a comma-separated list of Sentinel servers and the `sentinel_master` option should be specified +to indicate the name of the sentinel master set (e.g. 'mymaster'). If using `sentinel_master` you may also specify +`load_from_slaves` in which case a random slave will be chosen for performing reads in order to load balance across multiple Redis instances. +Using the value '1' indicates to only load from slaves and '2' to include the master in the random read slave selection. + +Example configuration: + + + + Cm_Cache_Backend_Redis + + tcp://10.0.0.1:26379,tcp://10.0.0.2:26379,tcp://10.0.0.3:26379 + 0.5 + mymaster + 1 + 1 + + + +### Load Balancer or Service Discovery + +It is also possible to achieve high availability by using other methods where you can specify separate connection addresses for the +master and slave(s). The `load_from_slave` option has been added for this purpose and this option does *not* +connect to a Sentinel server as the example above, although you probably would benefit from still having a Sentinel setup purely for +the easier replication and failover. + +Examples would be to use a TCP load balancer (e.g. HAProxy) with separate ports for master and slaves, or a DNS-based system that +uses service discovery health checks to expose master and slaves via different DNS names. + +Example configuration: + + + + Cm_Cache_Backend_Redis + + tcp://redis-master:6379 + tcp://redis-slaves:6379 + 0 + 0.5 + + + +### Static Configuration + +You may also statically specify the master and slave servers by passing either an array to `load_from_slave` or a string +with multiple addresses separated by a comma. + + + + Cm_Cache_Backend_Redis + + tcp://redis-master:6379 + tcp://redis-slave1:6379,tcp://redis-slave2:6379 + 0 + 0.5 + + + +### ElastiCache + +The following example configuration lets you use ElastiCache Redis (cluster mode disabled) where the writes are sent to +the Primary node and reads are sent to the replicas. This lets you distribute the read traffic between the different nodes. + +The instructions to find the primary and read replica endpoints are [here](http://docs.aws.amazon.com/AmazonElastiCache/latest/UserGuide/Endpoints.html#Endpoints.Find.Redis). + + + + primary-endpoint.0001.euw1.cache.amazonaws.com + 6379 + 0 + 1 + + + replica-endpoint-1.jwbaun.0001.euw1.cache.amazonaws.com + 6379 + + + replica-endpoint-2.jwbaun.0001.euw1.cache.amazonaws.com + 6379 + + + + +#### DEPRECATION NOTICE + +Previously the ElastiCache config instructions suggested setting up a `` node but this functionality was flawed +and is no longer supported. The config is still parsed and loaded for backwards-compatibility but chooses a random slave +to read from rather than using md5 hash of the keys. + +# TUNING + + - The recommended "maxmemory-policy" is "volatile-lru". All tag metadata is non-volatile, so it is + recommended to use key expirations unless non-volatile keys are absolutely necessary so that tag + data cannot get evicted. So, be sure that the "maxmemory" is high enough to accommodate all + the tag data and non-volatile data with enough room left for the volatile key data as well. + - Automatic cleaning is optional and not recommended since it is slow and uses lots of memory. + - Occasional (e.g. once a day) garbage collection is recommended if the entire cache is infrequently cleared and + automatic cleaning is not enabled. The best solution is to run a cron job which does the garbage collection. + (See "Example Garbage Collection Script" below.) + - Compression will have additional CPU overhead but may be worth it for memory savings and reduced traffic. + For high-latency networks it may even improve performance. Use the + [Magento Cache Benchmark](https://github.com/colinmollenhour/magento-cache-benchmark) to analyze your real-world + compression performance and test your system's performance with different compression libraries. + - gzip — Slowest but highest compression. Most likely you will not want to use above level 1 compression. + - lzf — Fastest compress, fast decompress. Install: `sudo pecl install lzf` + - snappy — Fastest decompress, fast compress. Download and install: [snappy](http://code.google.com/p/snappy/) and [php-snappy](http://code.google.com/p/php-snappy/) + - Monitor your redis cache statistics with my modified [munin plugin](https://gist.github.com/1177716). + - Enable persistent connections. Make sure that if you have multiple configurations connecting the persistent + string is unique for each configuration so that "select" commands don't cause conflicts. + - Increase your server's `lua-time-limit` if you are getting "BUSY" errors. This setting can also cause Redis Sentinel + to invoke fail-overs when you would probably prefer to let the Lua script finish and have clients wait a little longer. + - Use the `stats.php` script to inspect your cache to find over-sized or wasteful cache tags. + +### Example Garbage Collection Script (Magento) + + :P'); + ini_set('memory_limit','1024M'); + set_time_limit(0); + error_reporting(E_ALL | E_STRICT); + require_once 'app/Mage.php'; + Mage::app()->getCache()->getBackend()->clean('old'); + // uncomment this for Magento Enterprise Edition + // Enterprise_PageCache_Model_Cache::getCacheInstance()->getFrontend()->getBackend()->clean('old'); + + +# DEVELOPMENT + +Please feel free to send Pull Requests to give back your improvements to the community! + +You can run the unit tests locally with just Docker installed using a simple alias: + +```shell +alias cm-cache-backend-redis='docker run --rm -it -e REDIS_SERVER=host.docker.internal -u $(id -u):$(id -g) -v ${COMPOSER_HOME:-$HOME/.composer}:/tmp -v $(pwd):/app --workdir /app cm-cache-backend-redis' +docker build . -t cm-cache-backend-redis +``` + +Then start a Redis server, install Composer dependencies and run tests like so: +```shell + docker run --rm -d -p 6379 --name cm-cache-backend-redis redis + cm-cache-backend-redis composer install + cm-cache-backend-redis composer run-script test + cm-cache-backend-redis composer run-script php-cs-fixer -- --dry-run +``` + +``` +@copyright Copyright (c) 2022 Colin Mollenhour +This project is licensed under the "New BSD" license (see source). +``` diff --git a/system/vendor/colinmollenhour/cache-backend-redis/composer.json b/system/vendor/colinmollenhour/cache-backend-redis/composer.json new file mode 100644 index 0000000..4d2e3ea --- /dev/null +++ b/system/vendor/colinmollenhour/cache-backend-redis/composer.json @@ -0,0 +1,29 @@ +{ + "name":"colinmollenhour/cache-backend-redis", + "type":"magento-module", + "license":"BSD-3-Clause-Modification", + "homepage":"https://github.com/colinmollenhour/Cm_Cache_Backend_Redis", + "description":"Zend_Cache backend using Redis with full support for tags.", + "authors":[ + { + "name":"Colin Mollenhour" + } + ], + "require":{ + "colinmollenhour/credis": "^1.14" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.4", + "phpunit/phpunit": "^9", + "zf1s/zend-cache": "~1.15" + }, + "autoload": { + "classmap": [ + "Cm/Cache/Backend/Redis.php" + ] + }, + "scripts": { + "test": "vendor/bin/phpunit tests", + "php-cs-fixer": "vendor/bin/php-cs-fixer fix --diff" + } +} diff --git a/system/vendor/colinmollenhour/cache-backend-redis/modman b/system/vendor/colinmollenhour/cache-backend-redis/modman new file mode 100644 index 0000000..adce619 --- /dev/null +++ b/system/vendor/colinmollenhour/cache-backend-redis/modman @@ -0,0 +1,2 @@ +Cm/Cache/Backend/Redis.php app/code/community/Cm/Cache/Backend/Redis.php +lib/* lib/ diff --git a/system/vendor/colinmollenhour/cache-backend-redis/stats.php b/system/vendor/colinmollenhour/cache-backend-redis/stats.php new file mode 100644 index 0000000..901948b --- /dev/null +++ b/system/vendor/colinmollenhour/cache-backend-redis/stats.php @@ -0,0 +1,95 @@ +select($db); + +$tagStats = array(); +foreach ($client->sMembers(Cm_Cache_Backend_Redis::SET_TAGS) as $tag) { + if (preg_match('/^\w{3}_MAGE$/', $tag)) { + continue; + } + $ids = $client->sMembers(Cm_Cache_Backend_Redis::PREFIX_TAG_IDS . $tag); + $tagSizes = array(); + $missing = 0; + foreach ($ids as $id) { + $data = $client->hGet(Cm_Cache_Backend_Redis::PREFIX_KEY.$id, Cm_Cache_Backend_Redis::FIELD_DATA); + $size = strlen($data); + if ($size) { + $tagSizes[] = $size; + } else { + $missing++; + } + } + if ($tagSizes) { + $tagStats[$tag] = array( + 'count' => count($tagSizes), + 'min' => min($tagSizes), + 'max' => max($tagSizes), + 'avg size' => array_sum($tagSizes) / count($tagSizes), + 'total size' => array_sum($tagSizes), + 'missing' => $missing, + ); + } +} + +function _format_bytes($a_bytes) +{ + if ($a_bytes < 1024) { + return $a_bytes .' B'; + } elseif ($a_bytes < 1048576) { + return round($a_bytes / 1024, 4) .' KB'; + } else { + return round($a_bytes / 1048576, 4) . ' MB'; + } +} + +function printStats($data, $key, $limit) +{ + echo "Top $limit tags by ".ucwords($key)."\n"; + echo "------------------------------------------------------------------------------------\n"; + $sort = array(); + foreach ($data as $tag => $stats) { + $sort[$tag] = $stats[$key]; + } + array_multisort($sort, SORT_DESC, $data); + $i = 0; + $fmt = "%-40s| %-8s| %-15s| %-15s\n"; + printf($fmt, 'Tag', 'Count', 'Avg Size', 'Total Size'); + foreach ($data as $tag => $stats) { + $tag = substr($tag, 4); + if (++$i > $limit) { + break; + } + $avg = _format_bytes($stats['avg size']); + $total = _format_bytes($stats['total size']); + printf($fmt, $tag, $stats['count'], $avg, $total); + } + echo "\n"; +} + +// Top 20 by total size +printStats($tagStats, 'total size', $limit); + +// Top 20 by average size +printStats($tagStats, 'avg size', $limit); + +// Top 20 by count +printStats($tagStats, 'count', $limit); diff --git a/system/vendor/colinmollenhour/cache-backend-redis/tests/CommonBackendTest.php b/system/vendor/colinmollenhour/cache-backend-redis/tests/CommonBackendTest.php new file mode 100644 index 0000000..c57a2b5 --- /dev/null +++ b/system/vendor/colinmollenhour/cache-backend-redis/tests/CommonBackendTest.php @@ -0,0 +1,186 @@ +_className = $name; + date_default_timezone_set('UTC'); + parent::__construct($name, $data, $dataName); + } + + public function setUp($noTag = false): void + { + $this->_instance->setDirectives(array('logging' => false)); + if ($noTag) { + $this->_instance->save('bar : data to cache', 'bar'); + $this->_instance->save('bar2 : data to cache', 'bar2'); + $this->_instance->save('bar3 : data to cache', 'bar3'); + } else { + $this->_instance->save('bar : data to cache', 'bar', array('tag3', 'tag4')); + $this->_instance->save('bar2 : data to cache', 'bar2', array('tag3', 'tag1')); + $this->_instance->save('bar3 : data to cache', 'bar3', array('tag2', 'tag3')); + } + } + + public function tearDown(): void + { + $this->_instance->clean(); + } + + public function testConstructorBadOption(): void + { + $this->expectException('Zend_Cache_Exception'); + new Cm_Cache_Backend_Redis(array(1 => 'bar')); + } + + public function testSetDirectivesBadArgument(): void + { + $this->expectException('Zend_Cache_Exception'); + $this->_instance->setDirectives('foo'); + } + + public function testSetDirectivesBadDirective(): void + { + // A bad directive (not known by a specific backend) is possible + // => so no exception here + $this->expectNotToPerformAssertions(); + $this->_instance->setDirectives(array('foo' => true, 'lifetime' => 3600)); + } + + public function testSetDirectivesBadDirective2(): void + { + $this->expectException('Zend_Cache_Exception'); + $this->_instance->setDirectives(array('foo' => true, 12 => 3600)); + } + + public function testSaveCorrectCall(): void + { + $res = $this->_instance->save('data to cache', 'foo', array('tag1', 'tag2')); + $this->assertTrue($res); + } + + public function testSaveWithNullLifeTime(): void + { + $this->_instance->setDirectives(array('lifetime' => null)); + $res = $this->_instance->save('data to cache', 'foo', array('tag1', 'tag2')); + $this->assertTrue($res); + } + + public function testSaveWithSpecificLifeTime(): void + { + $this->_instance->setDirectives(array('lifetime' => 3600)); + $res = $this->_instance->save('data to cache', 'foo', array('tag1', 'tag2'), 10); + $this->assertTrue($res); + } + + public function testRemoveCorrectCall(): void + { + $this->assertTrue($this->_instance->remove('bar')); + $this->assertFalse($this->_instance->test('bar')); + $this->assertFalse($this->_instance->remove('barbar')); + $this->assertFalse($this->_instance->test('barbar')); + } + + public function testTestWithAnExistingCacheId(): void + { + $res = $this->_instance->test('bar'); + $this->assertNotEmpty($res); + $this->assertGreaterThan(999999, $res); + } + + public function testTestWithANonExistingCacheId(): void + { + $this->assertFalse($this->_instance->test('barbar')); + } + + public function testTestWithAnExistingCacheIdAndANullLifeTime(): void + { + $this->_instance->setDirectives(array('lifetime' => null)); + $res = $this->_instance->test('bar'); + $this->assertNotEmpty($res); + $this->assertGreaterThan(999999, $res); + } + + public function testGetWithANonExistingCacheId(): void + { + $this->assertFalse($this->_instance->load('barbar')); + } + + public function testGetWithAnExistingCacheId(): void + { + $this->assertEquals('bar : data to cache', $this->_instance->load('bar')); + } + + public function testGetWithAnExistingCacheIdAndUTFCharacters(): void + { + $data = '"""""' . "'" . '\n' . 'ééééé'; + $this->_instance->save($data, 'foo'); + $this->assertEquals($data, $this->_instance->load('foo')); + } + + public function testGetWithAnExpiredCacheId(): void + { + $this->_instance->___expire('bar'); + $this->_instance->setDirectives(array('lifetime' => -1)); + $this->assertFalse($this->_instance->load('bar')); + $this->assertEquals('bar : data to cache', $this->_instance->load('bar', true)); + } + + public function testCleanModeAll(): void + { + $this->assertTrue($this->_instance->clean('all')); + $this->assertFalse($this->_instance->test('bar')); + $this->assertFalse($this->_instance->test('bar2')); + } + + public function testCleanModeOld(): void + { + $this->_instance->___expire('bar2'); + $this->assertTrue($this->_instance->clean('old')); + $this->assertTrue($this->_instance->test('bar') > 999999); + $this->assertFalse($this->_instance->test('bar2')); + } + + public function testCleanModeMatchingTags(): void + { + $this->assertTrue($this->_instance->clean('matchingTag', array('tag3'))); + $this->assertFalse($this->_instance->test('bar')); + $this->assertFalse($this->_instance->test('bar2')); + } + + public function testCleanModeMatchingTags2(): void + { + $this->assertTrue($this->_instance->clean('matchingTag', array('tag3', 'tag4'))); + $this->assertFalse($this->_instance->test('bar')); + $this->assertTrue($this->_instance->test('bar2') > 999999); + } + + public function testCleanModeNotMatchingTags(): void + { + $this->assertTrue($this->_instance->clean('notMatchingTag', array('tag3'))); + $this->assertTrue($this->_instance->test('bar') > 999999); + $this->assertTrue($this->_instance->test('bar2') > 999999); + } + + public function testCleanModeNotMatchingTags2(): void + { + $this->assertTrue($this->_instance->clean('notMatchingTag', array('tag4'))); + $this->assertTrue($this->_instance->test('bar') > 999999); + $this->assertFalse($this->_instance->test('bar2')); + } + + public function testCleanModeNotMatchingTags3(): void + { + $this->assertTrue($this->_instance->clean('notMatchingTag', array('tag4', 'tag1'))); + $this->assertTrue($this->_instance->test('bar') > 999999); + $this->assertTrue($this->_instance->test('bar2') > 999999); + $this->assertFalse($this->_instance->test('bar3')); + } +} diff --git a/system/vendor/colinmollenhour/cache-backend-redis/tests/CommonExtendedBackendTest.php b/system/vendor/colinmollenhour/cache-backend-redis/tests/CommonExtendedBackendTest.php new file mode 100644 index 0000000..d00532c --- /dev/null +++ b/system/vendor/colinmollenhour/cache-backend-redis/tests/CommonExtendedBackendTest.php @@ -0,0 +1,177 @@ +_capabilities = $this->_instance->getCapabilities(); + } + + public function testGetFillingPercentage(): void + { + $res = $this->_instance->getFillingPercentage(); + $this->assertTrue(is_integer($res)); + $this->assertTrue($res >= 0); + $this->assertTrue($res <= 100); + } + + public function testGetFillingPercentageOnEmptyBackend(): void + { + $this->_instance->clean(); + $res = $this->_instance->getFillingPercentage(); + $this->assertTrue(is_integer($res)); + $this->assertTrue($res >= 0); + $this->assertTrue($res <= 100); + } + + public function testGetIds(): void + { + if (!($this->_capabilities['get_list'])) { + # unsupported by this backend + return; + } + $res = $this->_instance->getIds(); + $this->assertTrue(count($res) == 3); + $this->assertTrue(in_array('bar', $res)); + $this->assertTrue(in_array('bar2', $res)); + $this->assertTrue(in_array('bar3', $res)); + } + + public function testGetTags(): void + { + if (!($this->_capabilities['tags'])) { + # unsupported by this backend + return; + } + $res = $this->_instance->getTags(); + $this->assertCount(4, $res); + $this->assertTrue(in_array('tag1', $res)); + $this->assertTrue(in_array('tag2', $res)); + $this->assertTrue(in_array('tag3', $res)); + $this->assertTrue(in_array('tag4', $res)); + } + + public function testGetIdsMatchingTags(): void + { + if (!($this->_capabilities['tags'])) { + # unsupported by this backend + return; + } + $res = $this->_instance->getIdsMatchingTags(array('tag3')); + $this->assertTrue(count($res) == 3); + $this->assertTrue(in_array('bar', $res)); + $this->assertTrue(in_array('bar2', $res)); + $this->assertTrue(in_array('bar3', $res)); + } + + public function testGetIdsMatchingTags2(): void + { + if (!($this->_capabilities['tags'])) { + # unsupported by this backend + return; + } + $res = $this->_instance->getIdsMatchingTags(array('tag2')); + $this->assertTrue(count($res) == 1); + $this->assertTrue(in_array('bar3', $res)); + } + + public function testGetIdsMatchingTags3(): void + { + if (!($this->_capabilities['tags'])) { + # unsupported by this backend + return; + } + $res = $this->_instance->getIdsMatchingTags(array('tag9999')); + $this->assertEmpty($res); + } + + + public function testGetIdsMatchingTags4(): void + { + if (!($this->_capabilities['tags'])) { + # unsupported by this backend + return; + } + $res = $this->_instance->getIdsMatchingTags(array('tag3', 'tag4')); + $this->assertTrue(count($res) == 1); + $this->assertTrue(in_array('bar', $res)); + } + + public function testGetIdsNotMatchingTags(): void + { + if (!($this->_capabilities['tags'])) { + # unsupported by this backend + return; + } + $res = $this->_instance->getIdsNotMatchingTags(array('tag3')); + $this->assertCount(0, $res); + } + + public function testGetIdsNotMatchingTags2(): void + { + if (!($this->_capabilities['tags'])) { + # unsupported by this backend + return; + } + $res = $this->_instance->getIdsNotMatchingTags(array('tag1')); + $this->assertTrue(count($res) == 2); + $this->assertTrue(in_array('bar', $res)); + $this->assertTrue(in_array('bar3', $res)); + } + + public function testGetIdsNotMatchingTags3(): void + { + if (!($this->_capabilities['tags'])) { + # unsupported by this backend + return; + } + $res = $this->_instance->getIdsNotMatchingTags(array('tag1', 'tag4')); + $this->assertTrue(count($res) == 1); + $this->assertTrue(in_array('bar3', $res)); + } + + public function testGetMetadatas($noTag = false) + { + $res = $this->_instance->getMetadatas('bar'); + $this->assertTrue(isset($res['tags'])); + $this->assertTrue(isset($res['mtime'])); + $this->assertTrue(isset($res['expire'])); + if ($noTag) { + $this->assertEmpty($res['tags']); + } else { + $this->assertTrue(count($res['tags']) == 2); + $this->assertTrue(in_array('tag3', $res['tags'])); + $this->assertTrue(in_array('tag4', $res['tags'])); + } + $this->assertTrue($res['expire'] > time()); + $this->assertTrue($res['mtime'] <= time()); + } + + public function testTouch(): void + { + $res = $this->_instance->getMetadatas('bar'); + $this->assertGreaterThan(time(), $res['expire']); + $bool = $this->_instance->touch('bar', 30); + $this->assertTrue($bool); + $res2 = $this->_instance->getMetadatas('bar'); + $this->assertGreaterThanOrEqual(29, $res2['expire'] - $res['expire']); + $this->assertTrue(($res2['mtime'] >= $res['mtime'])); + } + + public function testGetCapabilities(): void + { + $res = $this->_instance->getCapabilities(); + $this->assertTrue(isset($res['tags'])); + $this->assertTrue(isset($res['automatic_cleaning'])); + $this->assertTrue(isset($res['expired_read'])); + $this->assertTrue(isset($res['priority'])); + $this->assertTrue(isset($res['infinite_lifetime'])); + $this->assertTrue(isset($res['get_list'])); + } +} diff --git a/system/vendor/colinmollenhour/cache-backend-redis/tests/RedisBackendAutoExpiryTest.php b/system/vendor/colinmollenhour/cache-backend-redis/tests/RedisBackendAutoExpiryTest.php new file mode 100644 index 0000000..36ee54f --- /dev/null +++ b/system/vendor/colinmollenhour/cache-backend-redis/tests/RedisBackendAutoExpiryTest.php @@ -0,0 +1,58 @@ +_instance->save($data, $id, $tags, null); + $metadata = $this->_instance->getMetadatas($id); + $this->assertGreaterThan(1, $metadata['expire']); + sleep(1); + $this->_instance->load($id); + $nextMetadata = $this->_instance->getMetadatas($id); + $this->assertGreaterThan($metadata['expire'], $nextMetadata['expire']); + } +} diff --git a/system/vendor/colinmollenhour/cache-backend-redis/tests/RedisBackendStandaloneTest.php b/system/vendor/colinmollenhour/cache-backend-redis/tests/RedisBackendStandaloneTest.php new file mode 100644 index 0000000..ae58292 --- /dev/null +++ b/system/vendor/colinmollenhour/cache-backend-redis/tests/RedisBackendStandaloneTest.php @@ -0,0 +1,41 @@ +_instance = new Cm_Cache_Backend_Redis(array( + 'server' => getenv('REDIS_SERVER') ?: 'localhost', + 'port' => getenv('REDIS_PORT') ?: '6379', + 'database' => '1', + 'notMatchingTags' => true, + 'force_standalone' => $this->forceStandalone, + 'compress_threshold' => 100, + 'compression_lib' => 'gzip', + 'use_lua' => true, + 'lua_max_c_stack' => self::LUA_MAX_C_STACK, + 'auto_expire_lifetime' => $this->autoExpireLifetime, + 'auto_expire_refresh_on_load' => $this->autoExpireRefreshOnLoad, + )); + $this->_instance->clean(); + $this->_instance->___scriptFlush(); + parent::setUp($noTag); + } + + public function tearDown(): void + { + parent::tearDown(); + unset($this->_instance); + } + + public function testGetWithAnExpiredCacheId(): void + { + $this->markTestSkipped('Getting expired data is unsupported by Redis'); + } + + public function testCompression(): void + { + $longString = str_repeat(md5('asd')."\r\n", 50); + $this->assertTrue($this->_instance->save($longString, 'long', array('long'))); + $this->assertTrue($this->_instance->load('long') == $longString); + } + + public function testExpiredCleanup(): void + { + $this->assertTrue($this->_instance->clean()); + $this->assertTrue($this->_instance->save('BLAH', 'foo', array('TAG1', 'TAG2'), 1)); + $this->assertTrue($this->_instance->save('BLAH', 'bar', array('TAG1', 'TAG3'), 1)); + $ids = $this->_instance->getIdsMatchingAnyTags(array('TAG1','TAG2','TAG3')); + sort($ids); + $this->assertEquals(array('bar','foo'), $ids); + + // sleep(2); + $this->_instance->___expire('foo'); + $this->_instance->___expire('bar'); + + $this->_instance->clean(Zend_Cache::CLEANING_MODE_OLD); + $this->assertEquals(array(), $this->_instance->getIdsMatchingAnyTags(array('TAG1','TAG2','TAG3'))); + $this->assertEquals(array(), $this->_instance->getTags()); + } + + /** + $this->_instance->save('bar : data to cache', 'bar', array('tag3', 'tag4')); + $this->_instance->save('bar2 : data to cache', 'bar2', array('tag3', 'tag1')); + $this->_instance->save('bar3 : data to cache', 'bar3', array('tag2', 'tag3')); + */ + public function testGetIdsMatchingAnyTags(): void + { + $res = $this->_instance->getIdsMatchingAnyTags(array('tag999')); + $this->assertCount(0, $res); + } + + public function testGetIdsMatchingAnyTags2(): void + { + $res = $this->_instance->getIdsMatchingAnyTags(array('tag1', 'tag999')); + $this->assertCount(1, $res); + $this->assertTrue(in_array('bar2', $res)); + } + + public function testGetIdsMatchingAnyTags3(): void + { + $res = $this->_instance->getIdsMatchingAnyTags(array('tag3', 'tag999')); + $this->assertCount(3, $res); + $this->assertTrue(in_array('bar', $res)); + $this->assertTrue(in_array('bar2', $res)); + $this->assertTrue(in_array('bar3', $res)); + } + + public function testGetIdsMatchingAnyTags4(): void + { + $res = $this->_instance->getIdsMatchingAnyTags(array('tag1', 'tag4')); + $this->assertCount(2, $res); + $this->assertTrue(in_array('bar', $res)); + $this->assertTrue(in_array('bar2', $res)); + } + + public function testCleanModeMatchingAnyTags(): void + { + $this->_instance->clean(Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG, array('tag999')); + $this->assertTrue(!!$this->_instance->load('bar')); + $this->assertTrue(!!$this->_instance->load('bar2')); + $this->assertTrue(!!$this->_instance->load('bar3')); + } + + public function testCleanModeMatchingAnyTags2(): void + { + $this->_instance->clean(Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG, array('tag1', 'tag999')); + $this->assertTrue(!!$this->_instance->load('bar')); + $this->assertFalse(!!$this->_instance->load('bar2')); + $this->assertTrue(!!$this->_instance->load('bar3')); + } + + public function testCleanModeMatchingAnyTags3(): void + { + $this->_instance->clean(Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG, array('tag3', 'tag999')); + $this->assertFalse(!!$this->_instance->load('bar')); + $this->assertFalse(!!$this->_instance->load('bar2')); + $this->assertFalse(!!$this->_instance->load('bar3')); + } + + public function testCleanModeMatchingAnyTags4(): void + { + $this->_instance->clean(Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG, array('tag1', 'tag4')); + $this->assertFalse(!!$this->_instance->load('bar')); + $this->assertFalse(!!$this->_instance->load('bar2')); + $this->assertTrue(!!$this->_instance->load('bar3')); + } + + public function testCleanModeMatchingAnyTags5(): void + { + $tags = array('tag1', 'tag4'); + for ($i = 0; $i < self::LUA_MAX_C_STACK*5; $i++) { + $this->_instance->save('foo', 'foo'.$i, $tags); + } + $this->assertGreaterThan(self::LUA_MAX_C_STACK, count($this->_instance->getIdsMatchingAnyTags($tags))); + $this->_instance->clean(Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG, $tags); + $this->assertCount(0, $this->_instance->getIdsMatchingAnyTags($tags)); + } + + public function testCleanModeMatchingAnyTags6(): void + { + $tags = array(); + for ($i = 0; $i < self::LUA_MAX_C_STACK*5; $i++) { + $tags[] = 'baz'.$i; + } + $this->_instance->save('foo', 'foo', $tags); + $_tags = array(end($tags)); + $this->assertCount(1, $this->_instance->getIdsMatchingAnyTags($_tags)); + $this->_instance->clean(Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG, $_tags); + $this->assertCount(0, $this->_instance->getIdsMatchingAnyTags($_tags)); + } + + public function testScriptsCaching(): void + { + $this->_instance->___scriptFlush(); + $this->assertEquals([], $this->_instance->___checkScriptsExist()); + + $this->_instance->save('foo', 'bar', ['x','y']); + $this->assertEquals(['save'], $this->_instance->___checkScriptsExist()); + + $this->_instance->___scriptFlush(); + $this->_instance->clean(Zend_Cache::CLEANING_MODE_OLD); + $this->assertEquals(['garbage'], $this->_instance->___checkScriptsExist()); + + $this->_instance->___scriptFlush(); + $this->_instance->clean(Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG, ['x']); + $this->assertEquals(['clean'], $this->_instance->___checkScriptsExist()); + } +} diff --git a/system/vendor/colinmollenhour/credis/.editorconfig b/system/vendor/colinmollenhour/credis/.editorconfig new file mode 100644 index 0000000..e13e644 --- /dev/null +++ b/system/vendor/colinmollenhour/credis/.editorconfig @@ -0,0 +1,6 @@ +[*.php] +charset=utf-8 +end_of_line=lf +insert_final_newline=true +indent_style=space +indent_size=4 \ No newline at end of file diff --git a/system/vendor/colinmollenhour/credis/.php-cs-fixer.dist.php b/system/vendor/colinmollenhour/credis/.php-cs-fixer.dist.php new file mode 100644 index 0000000..6a41e79 --- /dev/null +++ b/system/vendor/colinmollenhour/credis/.php-cs-fixer.dist.php @@ -0,0 +1,19 @@ +setRules([ + '@PSR12' => true, + 'visibility_required' => false, // php 5.6 doesn't support "public const ..." + ]) + ->setFinder(PhpCsFixer\Finder::create() + ->in(__DIR__) + ->name('*.php') + ->ignoreDotFiles(true) + ->ignoreVCS(true) + ) +; diff --git a/system/vendor/colinmollenhour/credis/Client.php b/system/vendor/colinmollenhour/credis/Client.php index 4a253da..0107191 100644 --- a/system/vendor/colinmollenhour/credis/Client.php +++ b/system/vendor/colinmollenhour/credis/Client.php @@ -20,25 +20,22 @@ * @package Credis_Client */ -if( ! defined('CRLF')) define('CRLF', sprintf('%s%s', chr(13), chr(10))); /** * Credis-specific errors, wraps native Redis errors */ class CredisException extends Exception { - const CODE_TIMED_OUT = 1; const CODE_DISCONNECTED = 2; - public function __construct($message, $code = 0, $exception = NULL) + public function __construct($message, $code = 0, $exception = null) { - if ($exception && get_class($exception) == 'RedisException' && strpos($message,'read error on connection') === 0) { + if ($exception && get_class($exception) == 'RedisException' && strpos($message, 'read error on connection') === 0) { $code = CredisException::CODE_DISCONNECTED; } parent::__construct($message, $code, $exception); } - } /** @@ -59,7 +56,7 @@ class CredisException extends Exception * @method int|Credis_Client dbsize() * * Keys: - * @method int|Credis_Client del(string $key) + * @method int|Credis_Client del(string|array ...$keys) * @method int|Credis_Client exists(string $key) * @method int|Credis_Client expire(string $key, int $seconds) * @method int|Credis_Client expireAt(string $key, int $timestamp) @@ -70,18 +67,19 @@ class CredisException extends Exception * @method array|Credis_Client sort(string $key, string $arg1, string $valueN = null) * @method int|Credis_Client ttl(string $key) * @method string|Credis_Client type(string $key) + * @method string|Credis_Client unlink(string|array ...$keys) * * Scalars: * @method int|Credis_Client append(string $key, string $value) * @method int|Credis_Client decr(string $key) * @method int|Credis_Client decrBy(string $key, int $decrement) - * @method bool|string|Credis_Client get(string $key) + * @method false|string|Credis_Client get(string $key) * @method int|Credis_Client getBit(string $key, int $offset) * @method string|Credis_Client getRange(string $key, int $start, int $end) * @method string|Credis_Client getSet(string $key, string $value) * @method int|Credis_Client incr(string $key) * @method int|Credis_Client incrBy(string $key, int $decrement) - * @method array|Credis_Client mGet(array $keys) + * @method false|array|Credis_Client mGet(array $keys) * @method bool|Credis_Client mSet(array $keysValues) * @method int|Credis_Client mSetNx(array $keysValues) * @method bool|Credis_Client set(string $key, string $value, int | array $options = null) @@ -113,7 +111,7 @@ class CredisException extends Exception * @method bool|string|Credis_Client hGet(string $key, string $field) * @method bool|int|Credis_Client hLen(string $key) * @method bool|Credis_Client hDel(string $key, string $field) - * @method array|Credis_Client hKeys(string $key, string $field) + * @method array|Credis_Client hKeys(string $key) * @method array|Credis_Client hVals(string $key) * @method array|Credis_Client hGetAll(string $key) * @method bool|Credis_Client hExists(string $key, string $field) @@ -167,18 +165,14 @@ class CredisException extends Exception * @method string|int|array|bool|Credis_Client eval(string $script, array $keys = null, array $args = null) * @method string|int|array|bool|Credis_Client evalSha(string $script, array $keys = null, array $args = null) */ -class Credis_Client { - - const VERSION = '1.11.4'; - - const TYPE_STRING = 'string'; - const TYPE_LIST = 'list'; - const TYPE_SET = 'set'; - const TYPE_ZSET = 'zset'; - const TYPE_HASH = 'hash'; - const TYPE_NONE = 'none'; - - const FREAD_BLOCK_SIZE = 8192; +class Credis_Client +{ + const TYPE_STRING = 'string'; + const TYPE_LIST = 'list'; + const TYPE_SET = 'set'; + const TYPE_ZSET = 'zset'; + const TYPE_HASH = 'hash'; + const TYPE_NONE = 'none'; /** * Socket connection to the Redis server or Redis library instance @@ -194,26 +188,32 @@ class Credis_Client { protected $host; /** - * Scheme of the Redis server (tcp, tls, unix) - * @var string + * Scheme of the Redis server (tcp, tls, tlsv1.2, unix) + * @var string|null */ protected $scheme; + /** + * SSL Meta information + * @var array|null + */ + protected $sslMeta; + /** * Port on which the Redis server is running - * @var integer|null + * @var int|null */ protected $port; /** * Timeout for connecting to Redis server - * @var float + * @var float|null */ protected $timeout; /** * Timeout for reading response from Redis server - * @var float + * @var float|null */ protected $readTimeout; @@ -226,12 +226,12 @@ class Credis_Client { /** * @var bool */ - protected $closeOnDestruct = TRUE; + protected $closeOnDestruct = true; /** * @var bool */ - protected $connected = FALSE; + protected $connected = false; /** * @var bool @@ -251,7 +251,7 @@ class Credis_Client { /** * @var bool */ - protected $usePipeline = FALSE; + protected $usePipeline = false; /** * @var array @@ -266,20 +266,20 @@ class Credis_Client { /** * @var bool */ - protected $isMulti = FALSE; + protected $isMulti = false; /** * @var bool */ - protected $isWatching = FALSE; + protected $isWatching = false; /** - * @var string + * @var string|null */ protected $authUsername; /** - * @var string + * @var string|null */ protected $authPassword; @@ -295,7 +295,7 @@ class Credis_Client { protected $wrapperMethods = array('delete' => 'del', 'getkeys' => 'keys', 'sremove' => 'srem'); /** - * @var array + * @var array|callable|null */ protected $renamedCommands; @@ -309,37 +309,66 @@ class Credis_Client { */ protected $subscribed = false; + /** @var bool */ + protected $oldPhpRedis = false; + + /** @var array */ + protected $tlsOptions = []; + /** - * Creates a Redisent connection to the Redis server on host {@link $host} and port {@link $port}. + * @var bool + */ + protected $isTls = false; + + /** + * Gets Useful Meta debug information about the SSL + * + * @return array|null + */ + public function getSslMeta() + { + return $this->sslMeta; + } + + /** + * Creates a connection to the Redis server on host {@link $host} and port {@link $port}. * $host may also be a path to a unix socket or a string in the form of tcp://[hostname]:[port] or unix://[path] * * @param string $host The hostname of the Redis server - * @param integer $port The port number of the Redis server - * @param float $timeout Timeout period in seconds - * @param string $persistent Flag to establish persistent connection - * @param int $db The selected datbase of the Redis server - * @param string $password The authentication password of the Redis server - * @param string $username The authentication username of the Redis server - */ - public function __construct($host = '127.0.0.1', $port = 6379, $timeout = null, $persistent = '', $db = 0, $password = null, $username = null) + * @param int|null $port The port number of the Redis server + * @param float|null $timeout Timeout period in seconds + * @param string $persistent Flag to establish persistent connection + * @param int $db The selected database of the Redis server + * @param string|null $password The authentication password of the Redis server + * @param string|null $username The authentication username of the Redis server + * @param array|null $tlsOptions The TLS/SSL context options. See https://www.php.net/manual/en/context.ssl.php for details + * @throws CredisException + */ + public function __construct($host = '127.0.0.1', $port = 6379, $timeout = null, $persistent = '', $db = 0, $password = null, $username = null, array $tlsOptions = null) { - $this->host = (string) $host; - $this->port = (int) $port; + $this->host = (string)$host; + if ($port !== null) { + $this->port = (int)$port; + } $this->scheme = null; $this->timeout = $timeout; - $this->persistent = (string) $persistent; - $this->standalone = ! extension_loaded('redis'); + $this->persistent = (string)$persistent; + $this->standalone = !extension_loaded('redis'); $this->authPassword = $password; $this->authUsername = $username; $this->selectedDb = (int)$db; $this->convertHost(); + if ($tlsOptions) { + $this->setTlsOptions($tlsOptions); + } // PHP Redis extension support TLS/ACL AUTH since 5.3.0 + $this->oldPhpRedis = (bool)version_compare(phpversion('redis'), '5.3.0', '<'); if (( - $this->scheme === 'tls' - || $this->authUsername !== null - ) - && !$this->standalone && version_compare(phpversion('redis'),'5.3.0','<')){ + $this->isTls + || $this->authUsername !== null + ) + && !$this->standalone && $this->oldPhpRedis) { $this->standalone = true; } } @@ -356,7 +385,7 @@ class Credis_Client { */ public function isSubscribed() { - return $this->subscribed; + return $this->subscribed; } /** @@ -367,6 +396,7 @@ class Credis_Client { { return $this->host; } + /** * Return the port of the Redis instance * @return int|null @@ -376,6 +406,14 @@ class Credis_Client { return $this->port; } + /** + * @return bool + */ + public function isTls() + { + return $this->isTls; + } + /** * Return the selected database * @return int @@ -384,6 +422,7 @@ class Credis_Client { { return $this->selectedDb; } + /** * @return string */ @@ -391,19 +430,20 @@ class Credis_Client { { return $this->persistent; } + /** - * @throws CredisException * @return Credis_Client + * @throws CredisException */ public function forceStandalone() { if ($this->standalone) { return $this; } - if($this->connected) { + if ($this->connected) { throw new CredisException('Cannot force Credis_Client to use standalone PHP driver after a connection has already been established.'); } - $this->standalone = TRUE; + $this->standalone = true; return $this; } @@ -426,37 +466,54 @@ class Credis_Client { $this->closeOnDestruct = $flag; return $this; } + + /** + * @throws CredisException + */ + public function setTlsOptions(array $tlsOptions) + { + if ($this->connected) { + throw new CredisException('Cannot change TLS options after a connection has already been established.'); + } + $this->tlsOptions = $tlsOptions; + } + + /** + * @throws CredisException + */ protected function convertHost() { - if (preg_match('#^(tcp|tls|unix)://(.*)$#', $this->host, $matches)) { - if($matches[1] == 'tcp' || $matches[1] == 'tls') { + if (preg_match('#^(tcp|tls|ssl|tlsv\d(?:\.\d)?|unix)://(.+)$#', $this->host, $matches)) { + $this->isTls = strpos($matches[1], 'tls') === 0 || strpos($matches[1], 'ssl') === 0; + if ($this->isTls || $matches[1] === 'tcp') { $this->scheme = $matches[1]; - if ( ! preg_match('#^([^:]+)(:([0-9]+))?(/(.+))?$#', $matches[2], $matches)) { - throw new CredisException('Invalid host format; expected '.$this->scheme.'://host[:port][/persistence_identifier]'); + if (!preg_match('#^([^:]+)(:([0-9]+))?(/(.+))?$#', $matches[2], $matches)) { + throw new CredisException('Invalid host format; expected ' . $this->scheme . '://host[:port][/persistence_identifier]'); } $this->host = $matches[1]; - $this->port = (int) (isset($matches[3]) ? $matches[3] : $this->port); + $this->port = (int)(isset($matches[3]) ? $matches[3] : $this->port); $this->persistent = isset($matches[5]) ? $matches[5] : $this->persistent; } else { $this->host = $matches[2]; - $this->port = NULL; + $this->port = null; $this->scheme = 'unix'; - if (substr($this->host,0,1) != '/') { + if (substr($this->host, 0, 1) != '/') { throw new CredisException('Invalid unix socket format; expected unix:///path/to/redis.sock'); } } } - if ($this->port !== NULL && substr($this->host,0,1) == '/') { - $this->port = NULL; + if ($this->port !== null && substr($this->host, 0, 1) == '/') { + $this->port = null; $this->scheme = 'unix'; } if (!$this->scheme) { $this->scheme = 'tcp'; } } + /** - * @throws CredisException * @return Credis_Client + * @throws CredisException */ public function connect() { @@ -464,32 +521,56 @@ class Credis_Client { return $this; } $this->close(true); - + $tlsOptions = $this->isTls ? $this->tlsOptions : []; if ($this->standalone) { $flags = STREAM_CLIENT_CONNECT; - $remote_socket = $this->port === NULL - ? $this->scheme.'://'.$this->host - : $this->scheme.'://'.$this->host.':'.$this->port; - if ($this->persistent && $this->port !== NULL) { + $remote_socket = $this->port === null + ? $this->scheme . '://' . $this->host + : $this->scheme . '://' . $this->host . ':' . $this->port; + if ($this->persistent && $this->port !== null) { // Persistent connections to UNIX sockets are not supported - $remote_socket .= '/'.$this->persistent; + $remote_socket .= '/' . $this->persistent; $flags = $flags | STREAM_CLIENT_PERSISTENT; } - $result = $this->redis = @stream_socket_client($remote_socket, $errno, $errstr, $this->timeout !== null ? $this->timeout : 2.5, $flags); - } - else { - if ( ! $this->redis) { - $this->redis = new Redis; + if ($this->isTls) { + $tlsOptions = array_merge($tlsOptions, [ + 'capture_peer_cert' => true, + 'capture_peer_cert_chain' => true, + 'capture_session_meta' => true, + ]); + } + + // passing $context as null errors before php 8.0 + $context = stream_context_create(['ssl' => $tlsOptions]); + + $result = $this->redis = @stream_socket_client($remote_socket, $errno, $errstr, $this->timeout !== null ? $this->timeout : 2.5, $flags, $context); + + if ($result && $this->isTls) { + $this->sslMeta = stream_context_get_options($context); } - $socketTimeout = $this->timeout ? $this->timeout : 0.0; - try - { - $result = $this->persistent - ? $this->redis->pconnect($this->host, (int)$this->port, $socketTimeout, $this->persistent) - : $this->redis->connect($this->host, (int)$this->port, $socketTimeout); + } else { + if (!$this->redis) { + $this->redis = new Redis(); } - catch(Exception $e) - { + $socketTimeout = $this->timeout ?: 0.0; + try { + if ($this->oldPhpRedis) { + $result = $this->persistent + ? $this->redis->pconnect($this->host, (int)$this->port, $socketTimeout, $this->persistent) + : $this->redis->connect($this->host, (int)$this->port, $socketTimeout); + } else { + // 7th argument is non-documented TLS options. But it only exists on the newer versions of phpredis + if ($tlsOptions) { + $context = ['stream' => $tlsOptions]; + } else { + $context = []; + } + /** @noinspection PhpMethodParametersCountMismatchInspection */ + $result = $this->persistent + ? $this->redis->pconnect($this->scheme . '://' . $this->host, (int)$this->port, $socketTimeout, $this->persistent, 0, 0.0, $context) + : $this->redis->connect($this->scheme . '://' . $this->host, (int)$this->port, $socketTimeout, null, 0, 0.0, $context); + } + } catch (Exception $e) { // Some applications will capture the php error that phpredis can sometimes generate and throw it as an Exception $result = false; $errno = 1; @@ -498,31 +579,39 @@ class Credis_Client { } // Use recursion for connection retries - if ( ! $result) { + if (!$result) { $this->connectFailures++; if ($this->connectFailures <= $this->maxConnectRetries) { return $this->connect(); } $failures = $this->connectFailures; $this->connectFailures = 0; - throw new CredisException("Connection to Redis {$this->host}:{$this->port} failed after $failures failures." . (isset($errno) && isset($errstr) ? "Last Error : ({$errno}) {$errstr}" : "")); + throw new CredisException(sprintf( + "Connection to Redis%s %s://%s failed after %s failures.%s", + $this->standalone ? ' standalone' : '', + $this->scheme, + $this->host . ($this->port ? ':' . $this->port : ''), + $failures, + (isset($errno) && isset($errstr) ? "Last Error : ({$errno}) {$errstr}" : "") + )); } $this->connectFailures = 0; - $this->connected = TRUE; + $this->connected = true; // Set read timeout if ($this->readTimeout) { $this->setReadTimeout($this->readTimeout); } - if($this->authPassword) { + if ($this->authPassword) { $this->auth($this->authPassword, $this->authUsername); } - if($this->selectedDb !== 0) { + if ($this->selectedDb !== 0) { $this->select($this->selectedDb); } return $this; } + /** * @return bool */ @@ -530,13 +619,14 @@ class Credis_Client { { return $this->connected; } + /** * Set the read timeout for the connection. Use 0 to disable timeouts entirely (or use a very long timeout * if not supported). * - * @param int $timeout 0 (or -1) for no timeout, otherwise number of seconds - * @throws CredisException + * @param float $timeout 0 (or -1) for no timeout, otherwise number of seconds * @return Credis_Client + * @throws CredisException */ public function setReadTimeout($timeout) { @@ -547,13 +637,17 @@ class Credis_Client { if ($this->isConnected()) { if ($this->standalone) { $timeout = $timeout <= 0 ? 315360000 : $timeout; // Ten-year timeout - stream_set_blocking($this->redis, TRUE); - stream_set_timeout($this->redis, (int) floor($timeout), ($timeout - floor($timeout)) * 1000000); - } else if (defined('Redis::OPT_READ_TIMEOUT')) { + stream_set_blocking($this->redis, true); + stream_set_timeout($this->redis, (int)floor($timeout), ($timeout - floor($timeout)) * 1000000); + } elseif (defined('Redis::OPT_READ_TIMEOUT')) { // supported in phpredis 2.2.3 - // a timeout value of -1 means reads will not timeout + // a timeout value of -1 means reads will not time out $timeout = $timeout == 0 ? -1 : $timeout; - $this->redis->setOption(Redis::OPT_READ_TIMEOUT, $timeout); + try { + $this->redis->setOption(Redis::OPT_READ_TIMEOUT, $timeout); + } catch (RedisException $e) { + throw new CredisException($e->getMessage(), $e->getCode(), $e); + } } } return $this; @@ -562,10 +656,10 @@ class Credis_Client { /** * @return bool */ - public function close($force = FALSE) + public function close($force = false) { - $result = TRUE; - if ($this->redis && ($force || $this->connected && ! $this->persistent)) { + $result = true; + if ($this->redis && ($force || $this->connected && !$this->persistent)) { try { if (is_callable(array($this->redis, 'close'))) { $this->redis->close(); @@ -574,9 +668,10 @@ class Credis_Client { $this->redis = null; } } catch (Exception $e) { - ; // Ignore exceptions on close + // Ignore exceptions on close + $result = false; } - $this->connected = $this->usePipeline = $this->isMulti = $this->isWatching = FALSE; + $this->connected = $this->usePipeline = $this->isMulti = $this->isWatching = false; } return $result; } @@ -592,16 +687,17 @@ class Credis_Client { * @param string|callable|array $command * @param string|null $alias * @return $this + * @throws CredisException */ - public function renameCommand($command, $alias = NULL) + public function renameCommand($command, $alias = null) { - if ( ! $this->standalone) { + if (!$this->standalone) { $this->forceStandalone(); } - if ($alias === NULL) { + if ($alias === null) { $this->renamedCommands = $command; } else { - if ( ! $this->renamedCommands) { + if (!$this->renamedCommands) { $this->renamedCommands = array(); } $this->renamedCommands[$command] = $alias; @@ -618,12 +714,12 @@ class Credis_Client { static $map; // Command renaming not enabled - if ($this->renamedCommands === NULL) { + if ($this->renamedCommands === null) { return $command; } // Initialize command map - if ($map === NULL) { + if ($map === null) { if (is_array($this->renamedCommands)) { $map = $this->renamedCommands; } else { @@ -632,17 +728,15 @@ class Credis_Client { } // Generate and return cached result - if ( ! isset($map[$command])) { + if (!isset($map[$command])) { // String means all commands are hashed with salted md5 if (is_string($this->renamedCommands)) { - $map[$command] = md5($this->renamedCommands.$command); - } - // Would already be set in $map if it was intended to be renamed - else if (is_array($this->renamedCommands)) { + $map[$command] = md5($this->renamedCommands . $command); + } // Would already be set in $map if it was intended to be renamed + elseif (is_array($this->renamedCommands)) { return $command; - } - // User-supplied function - else if (is_callable($this->renamedCommands)) { + } // User-supplied function + elseif (is_callable($this->renamedCommands)) { $map[$command] = call_user_func($this->renamedCommands, $command); } } @@ -653,12 +747,13 @@ class Credis_Client { * @param string $password * @param string|null $username * @return bool + * @throws CredisException */ public function auth($password, $username = null) { if ($username !== null) { $response = $this->__call('auth', array($username, $password)); - $this->authUsername= $username; + $this->authUsername = $username; } else { $response = $this->__call('auth', array($password)); } @@ -669,69 +764,93 @@ class Credis_Client { /** * @param int $index * @return bool + * @throws CredisException */ public function select($index) { $response = $this->__call('select', array($index)); - $this->selectedDb = (int) $index; + $this->selectedDb = (int)$index; return $response; } /** - * @param string|array $pattern + * @param string $caller + * @return void + * @throws CredisException + */ + protected function assertNotPipelineOrMulti($caller) + { + if ($this->standalone && ($this->isMulti || $this->usePipeline) || + // phpredis triggers a php fatal error, so do the check before + !$this->standalone && ($this->redis->getMode() === Redis::MULTI || $this->redis->getMode() === Redis::PIPELINE)) { + throw new CredisException('multi()/pipeline() mode can not be used with '.$caller); + } + } + + /** + * @param string|array ...$args * @return array + * @throws CredisException */ - public function pUnsubscribe() + public function pUnsubscribe(...$args) { - list($command, $channel, $subscribedChannels) = $this->__call('punsubscribe', func_get_args()); - $this->subscribed = $subscribedChannels > 0; - return array($command, $channel, $subscribedChannels); + list($command, $channel, $subscribedChannels) = $this->__call('punsubscribe', $args); + $this->subscribed = $subscribedChannels > 0; + return array($command, $channel, $subscribedChannels); } /** - * @param int $Iterator + * @param ?int $Iterator * @param string $pattern * @param int $count * @return bool|array + * @throws CredisException */ public function scan(&$Iterator, $pattern = null, $count = null) { + $this->assertNotPipelineOrMulti(__METHOD__); return $this->__call('scan', array(&$Iterator, $pattern, $count)); } /** - * @param int $Iterator - * @param string $field - * @param string $pattern - * @param int $count - * @return bool|array - */ - public function hscan(&$Iterator, $field, $pattern = null, $count = null) - { - return $this->__call('hscan', array($field, &$Iterator, $pattern, $count)); - } + * @param ?int $Iterator + * @param string $field + * @param string $pattern + * @param int $count + * @return bool|array + * @throws CredisException + */ + public function hscan(&$Iterator, $field, $pattern = null, $count = null) + { + $this->assertNotPipelineOrMulti(__METHOD__); + return $this->__call('hscan', array($field, &$Iterator, $pattern, $count)); + } /** - * @param int $Iterator + * @param ?int $Iterator * @param string $field * @param string $pattern - * @param int $Iterator + * @param ?int $count * @return bool|array + * @throws CredisException */ public function sscan(&$Iterator, $field, $pattern = null, $count = null) { + $this->assertNotPipelineOrMulti(__METHOD__); return $this->__call('sscan', array($field, &$Iterator, $pattern, $count)); } /** - * @param int $Iterator + * @param ?int $Iterator * @param string $field * @param string $pattern - * @param int $Iterator + * @param ?int $count * @return bool|array + * @throws CredisException */ public function zscan(&$Iterator, $field, $pattern = null, $count = null) { + $this->assertNotPipelineOrMulti(__METHOD__); return $this->__call('zscan', array($field, &$Iterator, $pattern, $count)); } @@ -743,7 +862,7 @@ class Credis_Client { */ public function pSubscribe($patterns, $callback) { - if ( ! $this->standalone) { + if (!$this->standalone) { return $this->__call('pSubscribe', array((array)$patterns, $callback)); } @@ -756,7 +875,7 @@ class Credis_Client { list($command, $pattern, $status) = $this->__call('psubscribe', array($patterns)); } $this->subscribed = $status > 0; - if ( ! $status) { + if (!$status) { throw new CredisException('Invalid pSubscribe response.'); } } @@ -771,25 +890,26 @@ class Credis_Client { } /** - * @param string|array $pattern + * @param string|array ...$args * @return array + * @throws CredisException */ - public function unsubscribe() + public function unsubscribe(...$args) { - list($command, $channel, $subscribedChannels) = $this->__call('unsubscribe', func_get_args()); - $this->subscribed = $subscribedChannels > 0; - return array($command, $channel, $subscribedChannels); + list($command, $channel, $subscribedChannels) = $this->__call('unsubscribe', $args); + $this->subscribed = $subscribedChannels > 0; + return array($command, $channel, $subscribedChannels); } /** * @param string|array $channels * @param $callback - * @throws CredisException * @return $this|array|bool|Credis_Client|mixed|null|string + * @throws CredisException */ public function subscribe($channels, $callback) { - if ( ! $this->standalone) { + if (!$this->standalone) { return $this->__call('subscribe', array((array)$channels, $callback)); } @@ -802,7 +922,7 @@ class Credis_Client { list($command, $channel, $status) = $this->__call('subscribe', array($channels)); } $this->subscribed = $status > 0; - if ( ! $status) { + if (!$status) { throw new CredisException('Invalid subscribe response.'); } } @@ -819,31 +939,33 @@ class Credis_Client { /** * @param string|null $name * @return string|Credis_Client + * @throws CredisException */ public function ping($name = null) { - return $this->__call('ping', $name ? array($name) : array()); + return $this->__call('ping', $name ? array($name) : array()); } - /** - * @param string $command - * @param array $args - * - * @return array|Credis_Client - */ - public function rawCommand($command, array $args) - { - if($this->standalone) - { - return $this->__call($command, $args); - } - else - { - \array_unshift($args, $command); - return $this->__call('rawCommand', $args); - } - } + /** + * @param string $command + * @param array $args + * + * @return array|Credis_Client + * @throws CredisException + */ + public function rawCommand($command, array $args) + { + if ($this->standalone) { + return $this->__call($command, $args); + } else { + \array_unshift($args, $command); + return $this->__call('rawCommand', $args); + } + } + /** + * @throws CredisException + */ public function __call($name, $args) { // Lazy connection @@ -852,26 +974,26 @@ class Credis_Client { $name = strtolower($name); // Send request via native PHP - if($this->standalone) - { + if ($this->standalone) { + // Early returns should verify how phpredis behaves! $trackedArgs = array(); switch ($name) { case 'eval': case 'evalsha': $script = array_shift($args); - $keys = (array) array_shift($args); - $eArgs = (array) array_shift($args); + $keys = (array)array_shift($args); + $eArgs = (array)array_shift($args); $args = array($script, count($keys), $keys, $eArgs); break; case 'zinterstore': case 'zunionstore': $dest = array_shift($args); - $keys = (array) array_shift($args); + $keys = (array)array_shift($args); $weights = array_shift($args); $aggregate = array_shift($args); $args = array($dest, count($keys), $keys); if ($weights) { - $args[] = (array) $weights; + $args[] = (array)$weights; } if ($aggregate) { $args[] = $aggregate; @@ -885,9 +1007,9 @@ class Credis_Client { } elseif (count($args) === 3 && is_array($args[2])) { $tmp_args = $args; $args = array($tmp_args[0], $tmp_args[1]); - foreach ($tmp_args[2] as $k=>$v) { + foreach ($tmp_args[2] as $k => $v) { if (is_string($k)) { - $args[] = array($k,$v); + $args[] = array($k, $v); } elseif (is_int($k)) { $args[] = $v; } @@ -897,18 +1019,17 @@ class Credis_Client { break; case 'scan': $trackedArgs = array(&$args[0]); - if (empty($trackedArgs[0])) - { + if ($trackedArgs[0] === null) { $trackedArgs[0] = 0; + } elseif ($trackedArgs[0] === 0) { + return false; } $eArgs = array($trackedArgs[0]); - if (!empty($args[1])) - { + if (!empty($args[1])) { $eArgs[] = 'MATCH'; $eArgs[] = $args[1]; } - if (!empty($args[2])) - { + if (!empty($args[2])) { $eArgs[] = 'COUNT'; $eArgs[] = $args[2]; } @@ -917,24 +1038,23 @@ class Credis_Client { case 'sscan': case 'zscan': case 'hscan': - $trackedArgs = array(&$args[1]); - if (empty($trackedArgs[0])) - { - $trackedArgs[0] = 0; - } - $eArgs = array($args[0],$trackedArgs[0]); - if (!empty($args[2])) - { - $eArgs[] = 'MATCH'; - $eArgs[] = $args[2]; - } - if (!empty($args[3])) - { - $eArgs[] = 'COUNT'; - $eArgs[] = $args[3]; - } - $args = $eArgs; - break; + $trackedArgs = array(&$args[1]); + if ($trackedArgs[0] === null) { + $trackedArgs[0] = 0; + } elseif ($trackedArgs[0] === 0) { + return false; + } + $eArgs = array($args[0], $trackedArgs[0]); + if (!empty($args[2])) { + $eArgs[] = 'MATCH'; + $eArgs[] = $args[2]; + } + if (!empty($args[3])) { + $eArgs[] = 'COUNT'; + $eArgs[] = $args[3]; + } + $args = $eArgs; + break; case 'zrangebyscore': case 'zrevrangebyscore': case 'zrange': @@ -953,17 +1073,17 @@ class Credis_Client { } break; case 'mget': - if (isset($args[0]) && is_array($args[0])) - { + if (isset($args[0]) && is_array($args[0])) { $args = array_values($args[0]); } + if (is_array($args) && count($args) === 0) { + return ($this->isMulti || $this->usePipeline) ? $this : false; + } break; case 'hmset': - if (isset($args[1]) && is_array($args[1])) - { + if (isset($args[1]) && is_array($args[1])) { $cArgs = array(); - foreach($args[1] as $id => $value) - { + foreach ($args[1] as $id => $value) { $cArgs[] = $id; $cArgs[] = $value; } @@ -978,121 +1098,148 @@ class Credis_Client { break; case 'hmget': // hmget needs to track the keys for rehydrating the results - if (isset($args[1])) - { + if (isset($args[1])) { $trackedArgs = $args[1]; } break; + case 'multi': + // calling multi() multiple times is a no-op + if ($this->isMulti) { + return $this; + } + break; } // Flatten arguments $args = self::_flattenArguments($args); // In pipeline mode - if($this->usePipeline) - { - if($name === 'pipeline') { + if ($this->usePipeline) { + if ($name === 'pipeline') { throw new CredisException('A pipeline is already in use and only one pipeline is supported.'); - } - else if($name === 'exec') { - if($this->isMulti) { - $this->commandNames[] = array($name, $trackedArgs); + } elseif ($name === 'exec') { + if ($this->isMulti) { + $this->commandNames[] = array($name, $trackedArgs, true); $this->commands .= self::_prepare_command(array($this->getRenamedCommand($name))); } - // Write request - if($this->commands) { - $this->write_command($this->commands); - } - $this->commands = NULL; - - // Read response - $queuedResponses = array(); - $response = array(); - foreach($this->commandNames as $command) { - list($name, $arguments) = $command; - $result = $this->read_reply($name, true); - if ($result !== null) - { - $result = $this->decode_reply($name, $result, $arguments); + try { + // Write request + if ($this->commands) { + $this->write_command($this->commands); } - else - { - $queuedResponses[] = $command; + + // Read response + $queuedResponses = array(); + $response = array(); + foreach ($this->commandNames as $command) { + list($name, $arguments, $requireDispatch) = $command; + if (!$requireDispatch) { + $queuedResponses[] = $command; + continue; + } + $result = $this->read_reply($name, true); + if ($result !== null) { + if ($name === 'multi') { + continue; + } + $result = $this->decode_reply($name, $result, $arguments); + $response[] = $result; + } else { + $queuedResponses[] = $command; + } } - $response[] = $result; - } - if($this->isMulti) { - $response = array_pop($response); - foreach($queuedResponses as $key => $command) - { - list($name, $arguments) = $command; - $response[$key] = $this->decode_reply($name, $response[$key], $arguments); + if ($this->isMulti) { + $execResponse = array_pop($response); + foreach ($queuedResponses as $key => $command) { + list($name, $arguments) = $command; + $response[] = $this->decode_reply($name, $execResponse[$key], $arguments); + } } + } catch (CredisException $e) { + // the connection on redis's side is likely in a bad state, force it closed to abort the pipeline/transaction + $this->close(true); + throw $e; + } finally { + $this->commands = $this->commandNames = null; + $this->isMulti = $this->usePipeline = false; } - - $this->commandNames = NULL; - $this->usePipeline = $this->isMulti = FALSE; return $response; - } - else if ($name === 'discard') - { - $this->commands = NULL; - $this->commandNames = NULL; - $this->usePipeline = $this->isMulti = FALSE; - } - else { - if($name === 'multi') { - $this->isMulti = TRUE; + } elseif ($name === 'discard') { + $this->commands = null; + $this->commandNames = null; + $this->usePipeline = $this->isMulti = false; + } else { + if ($name === 'multi') { + $this->isMulti = true; } array_unshift($args, $this->getRenamedCommand($name)); - $this->commandNames[] = array($name, $trackedArgs); + $this->commandNames[] = array($name, $trackedArgs, true); $this->commands .= self::_prepare_command($args); return $this; } } // Start pipeline mode - if($name === 'pipeline') - { - $this->usePipeline = TRUE; - $this->commandNames = array(); + if ($name === 'pipeline') { + $this->usePipeline = true; + if (!$this->isMulti) { + $this->commandNames = []; + } $this->commands = ''; return $this; } // If unwatching, allow reconnect with no error thrown - if($name === 'unwatch') { - $this->isWatching = FALSE; + if ($name === 'unwatch') { + $this->isWatching = false; } // Non-pipeline mode array_unshift($args, $this->getRenamedCommand($name)); $command = self::_prepare_command($args); - $this->write_command($command); - $response = $this->read_reply($name); - $response = $this->decode_reply($name, $response, $trackedArgs); + // transaction mode needs to track commands + if ($this->isMulti) { + try { + if ($name === 'exec' || $name === 'discard') { + try { + $this->write_command($command); + $response = $this->read_reply($name); + $response = $this->decode_reply($name, $response, $trackedArgs); + } finally { + $this->isMulti = false; + $this->commandNames = []; + } + } else { + $this->commandNames[] = array($name, $trackedArgs, false); + $this->write_command($command); + $response = $this->read_reply($name); + } + } catch (CredisException $e) { + // the connection on redis's side is likely in a bad state, force it closed to abort the transaction + $this->isMulti = false; + $this->commandNames = []; + $this->close(true); + throw $e; + } + } else { + $this->write_command($command); + $response = $this->read_reply($name); + $response = $this->decode_reply($name, $response, $trackedArgs); + } // Watch mode disables reconnect so error is thrown - if($name == 'watch') { - $this->isWatching = TRUE; - } - // Transaction mode - else if($this->isMulti && ($name == 'exec' || $name == 'discard')) { - $this->isMulti = FALSE; - } - // Started transaction - else if($this->isMulti || $name == 'multi') { - $this->isMulti = TRUE; + if ($name === 'watch') { + $this->isWatching = true; + } // Started transaction + elseif ($this->isMulti || $name === 'multi') { + $this->isMulti = true; $response = $this; } - } - - // Send request via phpredis client - else - { + } // Send request via phpredis client + else { // Tweak arguments - switch($name) { + switch ($name) { case 'get': // optimize common cases case 'set': case 'hget': @@ -1102,14 +1249,12 @@ class Credis_Client { case 'msetnx': case 'hmset': case 'hmget': - case 'del': case 'zrangebyscore': case 'zrevrangebyscore': - break; + break; case 'zrange': case 'zrevrange': - if (isset($args[3]) && is_array($args[3])) - { + if (isset($args[3]) && is_array($args[3])) { $cArgs = $args[3]; $args[3] = !empty($cArgs['withscores']); } @@ -1120,18 +1265,18 @@ class Credis_Client { $cArgs = array(); $cArgs[] = array_shift($args); // destination $cArgs[] = array_shift($args); // keys - if(isset($args[0]) and isset($args[0]['weights'])) { - $cArgs[] = (array) $args[0]['weights']; + if (isset($args[0]) and isset($args[0]['weights'])) { + $cArgs[] = (array)$args[0]['weights']; } else { $cArgs[] = null; } - if(isset($args[0]) and isset($args[0]['aggregate'])) { + if (isset($args[0]) and isset($args[0]['aggregate'])) { $cArgs[] = strtoupper($args[0]['aggregate']); } $args = $cArgs; break; case 'mget': - if(isset($args[0]) && ! is_array($args[0])) { + if (isset($args[0]) && !is_array($args[0])) { $args = array($args); } break; @@ -1168,7 +1313,7 @@ class Credis_Client { break; case 'auth': // For phpredis pre-v5.3, the type signature is string, not array|string - $args = (is_array($args) && count($args) === 1) ? $args : array($args); + $args = $this->oldPhpRedis ? $args : array($args); break; default: // Flatten arguments @@ -1177,30 +1322,26 @@ class Credis_Client { try { // Proxy pipeline mode to the phpredis library - if($name == 'pipeline' || $name == 'multi') { - if($this->isMulti) { - return $this; - } else { - $this->isMulti = TRUE; + if ($name == 'pipeline' || $name == 'multi') { + if (!$this->isMulti) { + $this->isMulti = true; $this->redisMulti = call_user_func_array(array($this->redis, $name), $args); - return $this; } - } - else if($name == 'exec' || $name == 'discard') { - $this->isMulti = FALSE; + return $this; + } elseif ($name == 'exec' || $name == 'discard') { + $this->isMulti = false; $response = $this->redisMulti->$name(); - $this->redisMulti = NULL; - #echo "> $name : ".substr(print_r($response, TRUE),0,100)."\n"; + $this->redisMulti = null; return $response; } // Use aliases to be compatible with phpredis wrapper - if(isset($this->wrapperMethods[$name])) { + if (isset($this->wrapperMethods[$name])) { $name = $this->wrapperMethods[$name]; } // Multi and pipeline return self for chaining - if($this->isMulti) { + if ($this->isMulti) { call_user_func_array(array($this->redisMulti, $name), $args); return $this; } @@ -1219,13 +1360,16 @@ class Credis_Client { throw $e; } } - } - // Wrap exceptions - catch(RedisException $e) { + } // Wrap exceptions + catch (RedisException $e) { $code = 0; - if ( ! ($result = $this->redis->IsConnected())) { - $this->close(true); - $code = CredisException::CODE_DISCONNECTED; + try { + if (!($result = $this->redis->IsConnected())) { + $this->close(true); + $code = CredisException::CODE_DISCONNECTED; + } + } catch (RedisException $e2) { + throw new CredisException($e2->getMessage(), $e2->getCode(), $e2); } throw new CredisException($e->getMessage(), $code, $e); } @@ -1233,87 +1377,93 @@ class Credis_Client { #echo "> $name : ".substr(print_r($response, TRUE),0,100)."\n"; // change return values where it is too difficult to minim in standalone mode - switch($name) - { - case 'type': - $typeMap = array( - self::TYPE_NONE, - self::TYPE_STRING, - self::TYPE_SET, - self::TYPE_LIST, - self::TYPE_ZSET, - self::TYPE_HASH, - ); - $response = $typeMap[$response]; - break; - - // Handle scripting errors - case 'eval': - case 'evalsha': - case 'script': - $error = $this->redis->getLastError(); - $this->redis->clearLastError(); - if ($error && substr($error,0,8) == 'NOSCRIPT') { - $response = NULL; - } else if ($error) { - throw new CredisException($error); - } - break; - case 'exists': - // smooth over phpredis-v4 vs earlier difference to match documented credis return results - $response = (int) $response; - break; - case 'ping': - if ($response) { - if ($response === true) { - $response = isset($args[0]) ? $args[0] : "PONG"; - } else if ($response[0] === '+') { - $response = substr($response, 1); - } - } - break; - case 'auth': - if (is_bool($response) && $response === true){ + try { + switch ($name) { + case 'type': + $typeMap = array( + self::TYPE_NONE, + self::TYPE_STRING, + self::TYPE_SET, + self::TYPE_LIST, + self::TYPE_ZSET, + self::TYPE_HASH, + ); + $response = $typeMap[$response]; + break; + + case 'eval': + case 'evalsha': + case 'script': + $error = $this->redis->getLastError(); $this->redis->clearLastError(); - } - default: - $error = $this->redis->getLastError(); - $this->redis->clearLastError(); - if ($error) { - throw new CredisException(rtrim($error)); - } - break; + if ($error && substr($error, 0, 8) == 'NOSCRIPT') { + $response = null; + } elseif ($error) { + throw new CredisException($error); + } + break; + case 'exists': + // smooth over phpredis-v4 vs earlier difference to match documented credis return results + $response = (int)$response; + break; + case 'ping': + if ($response) { + if ($response === true) { + $response = isset($args[0]) ? $args[0] : "PONG"; + } elseif ($response[0] === '+') { + $response = substr($response, 1); + } + } + break; + case 'auth': + if (is_bool($response) && $response === true) { + $this->redis->clearLastError(); + } + // no break + default: + $error = $this->redis->getLastError(); + $this->redis->clearLastError(); + if ($error) { + throw new CredisException(rtrim($error)); + } + break; + } + } catch (RedisException $e) { + throw new CredisException($e->getMessage(), $e->getCode(), $e); } } return $response; } + /** + * @throws CredisException + */ protected function write_command($command) { // Reconnect on lost connection (Redis server "timeout" exceeded since last command) - if(feof($this->redis)) { + if (feof($this->redis)) { // If a watch or transaction was in progress and connection was lost, throw error rather than reconnect // since transaction/watch state will be lost. - if(($this->isMulti && ! $this->usePipeline) || $this->isWatching) { + if (($this->isMulti && !$this->usePipeline) || $this->isWatching) { $this->close(true); throw new CredisException('Lost connection to Redis server during watch or transaction.'); } $this->close(true); $this->connect(); - if($this->authPassword) { + if ($this->authPassword) { $this->auth($this->authPassword); } - if($this->selectedDb != 0) { + if ($this->selectedDb != 0) { $this->select($this->selectedDb); } } $commandLen = strlen($command); - $lastFailed = FALSE; + $lastFailed = false; for ($written = 0; $written < $commandLen; $written += $fwrite) { $fwrite = fwrite($this->redis, substr($command, $written)); - if ($fwrite === FALSE || ($fwrite == 0 && $lastFailed)) { + if ($fwrite === false || ($fwrite == 0 && $lastFailed)) { $this->close(true); throw new CredisException('Failed to write entire command to stream'); } @@ -1321,10 +1471,13 @@ class Credis_Client { } } + /** + * @throws CredisException + */ protected function read_reply($name = '', $returnQueued = false) { $reply = fgets($this->redis); - if($reply === FALSE) { + if ($reply === false) { $info = stream_get_meta_data($this->redis); $this->close(true); if ($info['timed_out']) { @@ -1333,87 +1486,89 @@ class Credis_Client { throw new CredisException('Lost connection to Redis server.', CredisException::CODE_DISCONNECTED); } } - $reply = rtrim($reply, CRLF); + $reply = rtrim($reply, "\r\n"); #echo "> $name: $reply\n"; $replyType = substr($reply, 0, 1); switch ($replyType) { /* Error reply */ case '-': - if($this->isMulti || $this->usePipeline) { - $response = FALSE; - } else if ($name == 'evalsha' && substr($reply,0,9) == '-NOSCRIPT') { - $response = NULL; + if ($this->isMulti || $this->usePipeline) { + $response = false; + } elseif ($name == 'evalsha' && substr($reply, 0, 9) == '-NOSCRIPT') { + $response = null; } else { - throw new CredisException(substr($reply,0,4) == '-ERR' ? 'ERR '.substr($reply, 5) : substr($reply,1)); + throw new CredisException(substr($reply, 0, 4) == '-ERR' ? 'ERR ' . substr($reply, 5) : substr($reply, 1)); } break; - /* Inline reply */ + /* Inline reply */ case '+': $response = substr($reply, 1); - if($response == 'OK') { - return TRUE; + if ($response == 'OK') { + return true; } - if($response == 'QUEUED') { + if ($response == 'QUEUED') { return $returnQueued ? null : true; } break; - /* Bulk reply */ + /* Bulk reply */ case '$': - if ($reply == '$-1') return FALSE; - $size = (int) substr($reply, 1); + if ($reply == '$-1') { + return false; + } + $size = (int)substr($reply, 1); $response = stream_get_contents($this->redis, $size + 2); - if( ! $response) { + if (!$response) { $this->close(true); throw new CredisException('Error reading reply.'); } $response = substr($response, 0, $size); break; - /* Multi-bulk reply */ + /* Multi-bulk reply */ case '*': $count = substr($reply, 1); - if ($count == '-1') return FALSE; + if ($count == '-1') { + return false; + } $response = array(); for ($i = 0; $i < $count; $i++) { - $response[] = $this->read_reply(); + $response[] = $this->read_reply(); } break; - /* Integer reply */ + /* Integer reply */ case ':': $response = intval(substr($reply, 1)); break; default: - throw new CredisException('Invalid response: '.print_r($reply, TRUE)); - break; + throw new CredisException('Invalid response: ' . print_r($reply, true)); } return $response; } - protected function decode_reply($name, $response, array &$arguments = array() ) + /** + * @throws CredisException + */ + protected function decode_reply($name, $response, array &$arguments = array()) { // Smooth over differences between phpredis and standalone response - switch ($name) - { + switch ($name) { case '': // Minor optimization for multi-bulk replies break; case 'config': case 'hgetall': $keys = $values = array(); - while ($response) - { + while ($response) { $keys[] = array_shift($response); $values[] = array_shift($response); } $response = count($keys) ? array_combine($keys, $values) : array(); break; case 'info': - $lines = explode(CRLF, trim($response, CRLF)); + $lines = explode("\r\n", trim($response, "\r\n")); $response = array(); - foreach ($lines as $line) - { - if (!$line || substr($line, 0, 1) == '#') - { + foreach ($lines as $line) { + if (!$line || substr($line, 0, 1) == '#') { continue; } list($key, $value) = explode(':', $line, 2); @@ -1421,17 +1576,16 @@ class Credis_Client { } break; case 'ttl': - if ($response === -1) - { + if ($response === -1) { $response = false; } break; case 'hmget': - if (count($arguments) != count($response)) - { + if (count($arguments) != count($response)) { throw new CredisException( 'hmget arguments and response do not match: ' . print_r($arguments, true) . ' ' . print_r( - $response, true + $response, + true ) ); } @@ -1448,12 +1602,10 @@ class Credis_Client { case 'zscan': $arguments[0] = intval(array_shift($response)); $response = empty($response[0]) ? array() : $response[0]; - if (!empty($response) && is_array($response)) - { + if (!empty($response) && is_array($response)) { $count = count($response); $out = array(); - for ($i = 0; $i < $count; $i += 2) - { + for ($i = 0; $i < $count; $i += 2) { $out[$response[$i]] = $response[$i + 1]; } $response = $out; @@ -1463,19 +1615,14 @@ class Credis_Client { case 'zrevrangebyscore': case 'zrange': case 'zrevrange': - if (in_array('withscores', $arguments, true)) - { + if (in_array('withscores', $arguments, true)) { // Map array of values into key=>score list like phpRedis does $item = null; $out = array(); - foreach ($response as $value) - { - if ($item == null) - { + foreach ($response as $value) { + if ($item == null) { $item = $value; - } - else - { + } else { // 2nd value is the score $out[$item] = (float)$value; $item = null; @@ -1497,12 +1644,12 @@ class Credis_Client { */ private static function _prepare_command($args) { - return sprintf('*%d%s%s%s', count($args), CRLF, implode(CRLF, array_map(array('self', '_map'), $args)), CRLF); + return sprintf('*%d%s%s%s', count($args), "\r\n", implode("\r\n", array_map([static::class, '_map'], $args)), "\r\n"); } private static function _map($arg) { - return sprintf('$%d%s%s', strlen($arg), CRLF, $arg); + return sprintf('$%d%s%s', strlen((string)$arg), "\r\n", $arg); } /** @@ -1513,7 +1660,8 @@ class Credis_Client { * becomes * array('zrangebyscore', '-inf', 123, 'limit', '0', '1') * - * @param array $in + * @param array $arguments + * @param array $out * @return array */ private static function _flattenArguments(array $arguments, &$out = array()) diff --git a/system/vendor/colinmollenhour/credis/Cluster.php b/system/vendor/colinmollenhour/credis/Cluster.php index eda43b8..108fdd5 100644 --- a/system/vendor/colinmollenhour/credis/Cluster.php +++ b/system/vendor/colinmollenhour/credis/Cluster.php @@ -15,329 +15,324 @@ */ class Credis_Cluster { - /** - * Collection of Credis_Client objects attached to Redis servers - * @var Credis_Client[] - */ - protected $clients; - /** - * If a server is set as master, all write commands go to that one - * @var Credis_Client - */ - protected $masterClient; - /** - * Aliases of Credis_Client objects attached to Redis servers, used to route commands to specific servers - * @see Credis_Cluster::to - * @var array - */ - protected $aliases; + /** + * Collection of Credis_Client objects attached to Redis servers + * @var Credis_Client[] + */ + protected $clients; + /** + * If a server is set as master, all write commands go to that one + * @var Credis_Client + */ + protected $masterClient; + /** + * Aliases of Credis_Client objects attached to Redis servers, used to route commands to specific servers + * @see Credis_Cluster::to + * @var array + */ + protected $aliases; - /** - * Hash ring of Redis server nodes - * @var array - */ - protected $ring; + /** + * Hash ring of Redis server nodes + * @var array + */ + protected $ring; - /** - * Individual nodes of pointers to Redis servers on the hash ring - * @var array - */ - protected $nodes; + /** + * Individual nodes of pointers to Redis servers on the hash ring + * @var array + */ + protected $nodes; - /** - * The commands that are not subject to hashing - * @var array - * @access protected - */ - protected $dont_hash; + /** + * The commands that are not subject to hashing + * @var array + * @access protected + */ + protected $dont_hash; - /** - * Currently working cluster-wide database number. - * @var int - */ - protected $selectedDb = 0; + /** + * Currently working cluster-wide database number. + * @var int + */ + protected $selectedDb = 0; - /** - * Creates an interface to a cluster of Redis servers - * Each server should be in the format: - * array( - * 'host' => hostname, - * 'port' => port, - * 'db' => db, - * 'password' => password, - * 'timeout' => timeout, - * 'alias' => alias, - * 'persistent' => persistence_identifier, - * 'master' => master - * 'write_only'=> true/false - * ) - * - * @param array $servers The Redis servers in the cluster. - * @param int $replicas - * @param bool $standAlone - * @throws CredisException - */ - public function __construct($servers, $replicas = 128, $standAlone = false) - { - $this->clients = array(); - $this->masterClient = null; - $this->aliases = array(); - $this->ring = array(); - $this->replicas = (int)$replicas; - $client = null; - foreach ($servers as $server) + /** + * Creates an interface to a cluster of Redis servers + * Each server should be in the format: + * array( + * 'host' => hostname, + * 'port' => port, + * 'db' => db, + * 'password' => password, + * 'timeout' => timeout, + * 'alias' => alias, + * 'persistent' => persistence_identifier, + * 'master' => master + * 'write_only'=> true/false + * ) + * + * @param array $servers The Redis servers in the cluster. + * @param int $replicas + * @param bool $standAlone + * @throws CredisException + */ + public function __construct($servers, $replicas = 128, $standAlone = false) { - if(is_array($server)){ - $client = new Credis_Client( - $server['host'], - $server['port'], - isset($server['timeout']) ? $server['timeout'] : 2.5, - isset($server['persistent']) ? $server['persistent'] : '', - isset($server['db']) ? $server['db'] : 0, - isset($server['password']) ? $server['password'] : null - ); - if (isset($server['alias'])) { - $this->aliases[$server['alias']] = $client; - } - if(isset($server['master']) && $server['master'] === true){ - $this->masterClient = $client; - if(isset($server['write_only']) && $server['write_only'] === true){ - continue; + $this->clients = array(); + $this->masterClient = null; + $this->aliases = array(); + $this->ring = array(); + $this->replicas = (int)$replicas; + $client = null; + foreach ($servers as $server) { + if (is_array($server)) { + $client = new Credis_Client( + $server['host'], + $server['port'], + isset($server['timeout']) ? $server['timeout'] : 2.5, + isset($server['persistent']) ? $server['persistent'] : '', + isset($server['db']) ? $server['db'] : 0, + isset($server['password']) ? $server['password'] : null + ); + if (isset($server['alias'])) { + $this->aliases[$server['alias']] = $client; + } + if (isset($server['master']) && $server['master'] === true) { + $this->masterClient = $client; + if (isset($server['write_only']) && $server['write_only'] === true) { + continue; + } + } + } elseif ($server instanceof Credis_Client) { + $client = $server; + } else { + throw new CredisException('Server should either be an array or an instance of Credis_Client'); + } + if ($standAlone) { + $client->forceStandalone(); + } + $this->clients[] = $client; + for ($replica = 0; $replica <= $this->replicas; $replica++) { + $md5num = hexdec(substr(md5($client->getHost() . ':' . $client->getPort() . '-' . $replica), 0, 7)); + $this->ring[$md5num] = count($this->clients) - 1; } - } - } elseif($server instanceof Credis_Client){ - $client = $server; - } else { - throw new CredisException('Server should either be an array or an instance of Credis_Client'); - } - if($standAlone) { - $client->forceStandalone(); - } - $this->clients[] = $client; - for ($replica = 0; $replica <= $this->replicas; $replica++) { - $md5num = hexdec(substr(md5($client->getHost().':'.$client->getPort().'-'.$replica),0,7)); - $this->ring[$md5num] = count($this->clients)-1; - } - } - ksort($this->ring, SORT_NUMERIC); - $this->nodes = array_keys($this->ring); - $this->dont_hash = array_flip(array( - 'RANDOMKEY', 'DBSIZE', 'PIPELINE', 'EXEC', - 'SELECT', 'MOVE', 'FLUSHDB', 'FLUSHALL', - 'SAVE', 'BGSAVE', 'LASTSAVE', 'SHUTDOWN', - 'INFO', 'MONITOR', 'SLAVEOF' - )); - if($this->masterClient !== null && count($this->clients()) == 0){ - $this->clients[] = $this->masterClient; - for ($replica = 0; $replica <= $this->replicas; $replica++) { - $md5num = hexdec(substr(md5($this->masterClient->getHost().':'.$this->masterClient->getHost().'-'.$replica),0,7)); - $this->ring[$md5num] = count($this->clients)-1; } + ksort($this->ring, SORT_NUMERIC); $this->nodes = array_keys($this->ring); + $this->dont_hash = array_flip(array( + 'RANDOMKEY', 'DBSIZE', 'PIPELINE', 'EXEC', + 'SELECT', 'MOVE', 'FLUSHDB', 'FLUSHALL', + 'SAVE', 'BGSAVE', 'LASTSAVE', 'SHUTDOWN', + 'INFO', 'MONITOR', 'SLAVEOF' + )); + if ($this->masterClient !== null && count($this->clients()) == 0) { + $this->clients[] = $this->masterClient; + for ($replica = 0; $replica <= $this->replicas; $replica++) { + $md5num = hexdec(substr(md5($this->masterClient->getHost() . ':' . $this->masterClient->getHost() . '-' . $replica), 0, 7)); + $this->ring[$md5num] = count($this->clients) - 1; + } + $this->nodes = array_keys($this->ring); + } } - } - /** - * @param Credis_Client $masterClient - * @param bool $writeOnly - * @return Credis_Cluster - */ - public function setMasterClient(Credis_Client $masterClient, $writeOnly=false) - { - if(!$masterClient instanceof Credis_Client){ - throw new CredisException('Master client should be an instance of Credis_Client'); - } - $this->masterClient = $masterClient; - if (!isset($this->aliases['master'])) { - $this->aliases['master'] = $masterClient; - } - if(!$writeOnly){ - $this->clients[] = $this->masterClient; - for ($replica = 0; $replica <= $this->replicas; $replica++) { - $md5num = hexdec(substr(md5($this->masterClient->getHost().':'.$this->masterClient->getHost().'-'.$replica),0,7)); - $this->ring[$md5num] = count($this->clients)-1; + /** + * @param Credis_Client $masterClient + * @param bool $writeOnly + * @return Credis_Cluster + */ + public function setMasterClient(Credis_Client $masterClient, $writeOnly = false) + { + if (!$masterClient instanceof Credis_Client) { + throw new CredisException('Master client should be an instance of Credis_Client'); } - $this->nodes = array_keys($this->ring); - } - return $this; - } - /** - * Get a client by index or alias. - * - * @param string|int $alias - * @throws CredisException - * @return Credis_Client - */ - public function client($alias) - { - if (is_int($alias) && isset($this->clients[$alias])) { - return $this->clients[$alias]; - } - else if (isset($this->aliases[$alias])) { - return $this->aliases[$alias]; + $this->masterClient = $masterClient; + if (!isset($this->aliases['master'])) { + $this->aliases['master'] = $masterClient; + } + if (!$writeOnly) { + $this->clients[] = $this->masterClient; + for ($replica = 0; $replica <= $this->replicas; $replica++) { + $md5num = hexdec(substr(md5($this->masterClient->getHost() . ':' . $this->masterClient->getHost() . '-' . $replica), 0, 7)); + $this->ring[$md5num] = count($this->clients) - 1; + } + $this->nodes = array_keys($this->ring); + } + return $this; } - throw new CredisException("Client $alias does not exist."); - } - /** - * Get an array of all clients - * - * @return array|Credis_Client[] - */ - public function clients() - { - return $this->clients; - } - - /** - * Execute a command on all clients - * - * @return array - */ - public function all() - { - $args = func_get_args(); - $name = array_shift($args); - $results = array(); - foreach($this->clients as $client) { - $results[] = call_user_func_array([$client, $name], $args); + /** + * Get a client by index or alias. + * + * @param string|int $alias + * @return Credis_Client + * @throws CredisException + */ + public function client($alias) + { + if (is_int($alias) && isset($this->clients[$alias])) { + return $this->clients[$alias]; + } elseif (isset($this->aliases[$alias])) { + return $this->aliases[$alias]; + } + throw new CredisException("Client $alias does not exist."); } - return $results; - } - /** - * Get the client that the key would hash to. - * - * @param string $key - * @return \Credis_Client - */ - public function byHash($key) - { - return $this->clients[$this->hash($key)]; - } + /** + * Get an array of all clients + * + * @return array|Credis_Client[] + */ + public function clients() + { + return $this->clients; + } - /** - * @param int $index - * @return void - */ - public function select($index) - { - $this->selectedDb = (int) $index; - } + /** + * Execute a command on all clients + * + * @return array + */ + public function all() + { + $args = func_get_args(); + $name = array_shift($args); + $results = array(); + foreach ($this->clients as $client) { + $results[] = call_user_func_array([$client, $name], $args); + } + return $results; + } - /** - * Execute a Redis command on the cluster with automatic consistent hashing and read/write splitting - * - * @param string $name - * @param array $args - * @return mixed - */ - public function __call($name, $args) - { - if($this->masterClient !== null && !$this->isReadOnlyCommand($name)){ - $client = $this->masterClient; - }elseif (count($this->clients()) == 1 || isset($this->dont_hash[strtoupper($name)]) || !isset($args[0])) { - $client = $this->clients[0]; + /** + * Get the client that the key would hash to. + * + * @param string $key + * @return \Credis_Client + */ + public function byHash($key) + { + return $this->clients[$this->hash($key)]; } - else { - $hashKey = $args[0]; - if (is_array($hashKey)) { - $hashKey = join('|', $hashKey); - } - $client = $this->byHash($hashKey); + + /** + * @param int $index + * @return void + */ + public function select($index) + { + $this->selectedDb = (int)$index; } - // Ensure that current client is working on the same database as expected. - if ($client->getSelectedDb() != $this->selectedDb) { - $client->select($this->selectedDb); + + /** + * Execute a Redis command on the cluster with automatic consistent hashing and read/write splitting + * + * @param string $name + * @param array $args + * @return mixed + */ + public function __call($name, $args) + { + if ($this->masterClient !== null && !$this->isReadOnlyCommand($name)) { + $client = $this->masterClient; + } elseif (count($this->clients()) == 1 || isset($this->dont_hash[strtoupper($name)]) || !isset($args[0])) { + $client = $this->clients[0]; + } else { + $hashKey = $args[0]; + if (is_array($hashKey)) { + $hashKey = join('|', $hashKey); + } + $client = $this->byHash($hashKey); + } + // Ensure that current client is working on the same database as expected. + if ($client->getSelectedDb() != $this->selectedDb) { + $client->select($this->selectedDb); + } + return call_user_func_array([$client, $name], $args); } - return call_user_func_array([$client, $name], $args); - } - /** - * Get client index for a key by searching ring with binary search - * - * @param string $key The key to hash - * @return int The index of the client object associated with the hash of the key - */ - public function hash($key) - { - $needle = hexdec(substr(md5($key),0,7)); - $server = $min = 0; - $max = count($this->nodes) - 1; - while ($max >= $min) { - $position = (int) (($min + $max) / 2); - $server = $this->nodes[$position]; - if ($needle < $server) { - $max = $position - 1; - } - else if ($needle > $server) { - $min = $position + 1; - } - else { - break; - } + /** + * Get client index for a key by searching ring with binary search + * + * @param string $key The key to hash + * @return int The index of the client object associated with the hash of the key + */ + public function hash($key) + { + $needle = hexdec(substr(md5($key), 0, 7)); + $server = $min = 0; + $max = count($this->nodes) - 1; + while ($max >= $min) { + $position = (int)(($min + $max) / 2); + $server = $this->nodes[$position]; + if ($needle < $server) { + $max = $position - 1; + } elseif ($needle > $server) { + $min = $position + 1; + } else { + break; + } + } + return $this->ring[$server]; } - return $this->ring[$server]; - } - public function isReadOnlyCommand($command) - { - static $readOnlyCommands = array( - 'DBSIZE' => true, - 'INFO' => true, - 'MONITOR' => true, - 'EXISTS' => true, - 'TYPE' => true, - 'KEYS' => true, - 'SCAN' => true, - 'RANDOMKEY' => true, - 'TTL' => true, - 'GET' => true, - 'MGET' => true, - 'SUBSTR' => true, - 'STRLEN' => true, - 'GETRANGE' => true, - 'GETBIT' => true, - 'LLEN' => true, - 'LRANGE' => true, - 'LINDEX' => true, - 'SCARD' => true, - 'SISMEMBER' => true, - 'SINTER' => true, - 'SUNION' => true, - 'SDIFF' => true, - 'SMEMBERS' => true, - 'SSCAN' => true, - 'SRANDMEMBER' => true, - 'ZRANGE' => true, - 'ZREVRANGE' => true, - 'ZRANGEBYSCORE' => true, - 'ZREVRANGEBYSCORE' => true, - 'ZCARD' => true, - 'ZSCORE' => true, - 'ZCOUNT' => true, - 'ZRANK' => true, - 'ZREVRANK' => true, - 'ZSCAN' => true, - 'HGET' => true, - 'HMGET' => true, - 'HEXISTS' => true, - 'HLEN' => true, - 'HKEYS' => true, - 'HVALS' => true, - 'HGETALL' => true, - 'HSCAN' => true, - 'PING' => true, - 'AUTH' => true, - 'SELECT' => true, - 'ECHO' => true, - 'QUIT' => true, - 'OBJECT' => true, - 'BITCOUNT' => true, - 'TIME' => true, - 'SORT' => true, - ); - return array_key_exists(strtoupper($command), $readOnlyCommands); - } + public function isReadOnlyCommand($command) + { + static $readOnlyCommands = array( + 'DBSIZE' => true, + 'INFO' => true, + 'MONITOR' => true, + 'EXISTS' => true, + 'TYPE' => true, + 'KEYS' => true, + 'SCAN' => true, + 'RANDOMKEY' => true, + 'TTL' => true, + 'GET' => true, + 'MGET' => true, + 'SUBSTR' => true, + 'STRLEN' => true, + 'GETRANGE' => true, + 'GETBIT' => true, + 'LLEN' => true, + 'LRANGE' => true, + 'LINDEX' => true, + 'SCARD' => true, + 'SISMEMBER' => true, + 'SINTER' => true, + 'SUNION' => true, + 'SDIFF' => true, + 'SMEMBERS' => true, + 'SSCAN' => true, + 'SRANDMEMBER' => true, + 'ZRANGE' => true, + 'ZREVRANGE' => true, + 'ZRANGEBYSCORE' => true, + 'ZREVRANGEBYSCORE' => true, + 'ZCARD' => true, + 'ZSCORE' => true, + 'ZCOUNT' => true, + 'ZRANK' => true, + 'ZREVRANK' => true, + 'ZSCAN' => true, + 'HGET' => true, + 'HMGET' => true, + 'HEXISTS' => true, + 'HLEN' => true, + 'HKEYS' => true, + 'HVALS' => true, + 'HGETALL' => true, + 'HSCAN' => true, + 'PING' => true, + 'AUTH' => true, + 'SELECT' => true, + 'ECHO' => true, + 'QUIT' => true, + 'OBJECT' => true, + 'BITCOUNT' => true, + 'TIME' => true, + 'SORT' => true, + ); + return array_key_exists(strtoupper($command), $readOnlyCommands); + } } - diff --git a/system/vendor/colinmollenhour/credis/Module.php b/system/vendor/colinmollenhour/credis/Module.php index 70b9ba9..f85a699 100644 --- a/system/vendor/colinmollenhour/credis/Module.php +++ b/system/vendor/colinmollenhour/credis/Module.php @@ -47,7 +47,7 @@ class Credis_Module */ public function setModule($moduleName) { - $this->moduleName = (string) $moduleName; + $this->moduleName = (string)$moduleName; return $this; } diff --git a/system/vendor/colinmollenhour/credis/README.markdown b/system/vendor/colinmollenhour/credis/README.markdown index bf1c8e9..0a9f09a 100644 --- a/system/vendor/colinmollenhour/credis/README.markdown +++ b/system/vendor/colinmollenhour/credis/README.markdown @@ -44,6 +44,14 @@ $redis = new Credis_Client(/* connection string */); `tls://host[:port][/persistence_identifier]` +or + +`tlsv1.2://host[:port][/persistence_identifier]` + +Before php 7.2, `tls://` only supports TLSv1.0, either `ssl://` or `tlsv1.2` can be used to force TLSv1.2 support. + +Recent versions of redis do not support the protocols/cyphers that older versions of php default to, which may result in cryptic connection failures. + #### Enable transport level security (TLS) Use TLS connection string `tls://127.0.0.1:6379` instead of TCP connection `tcp://127.0.0.1:6379` string in order to enable transport level security. diff --git a/system/vendor/colinmollenhour/credis/Sentinel.php b/system/vendor/colinmollenhour/credis/Sentinel.php index 5a7c2a4..995d218 100644 --- a/system/vendor/colinmollenhour/credis/Sentinel.php +++ b/system/vendor/colinmollenhour/credis/Sentinel.php @@ -52,6 +52,31 @@ class Credis_Sentinel * @var string */ protected $_password = ''; + /** + * Store the AUTH username used by Credis_Client instances (Redis v6+) + * @var string + */ + protected $_username = ''; + /** + * @var null|float + */ + protected $_timeout; + /** + * @var string + */ + protected $_persistent; + /** + * @var int + */ + protected $_db; + /** + * @var string|null + */ + protected $_replicaCmd = null; + /** + * @var string|null + */ + protected $_redisVersion = null; /** * Connect with a Sentinel node. Sentinel will do the master and slave discovery @@ -60,17 +85,15 @@ class Credis_Sentinel * @param string $password (deprecated - use setClientPassword) * @throws CredisException */ - public function __construct(Credis_Client $client, $password = NULL) + public function __construct(Credis_Client $client, $password = null, $username = null) { - if(!$client instanceof Credis_Client){ - throw new CredisException('Sentinel client should be an instance of Credis_Client'); - } $client->forceStandalone(); // SENTINEL command not currently supported by phpredis - $this->_client = $client; - $this->_password = $password; - $this->_timeout = NULL; + $this->_client = $client; + $this->_password = $password; + $this->_username = $username; + $this->_timeout = null; $this->_persistent = ''; - $this->_db = 0; + $this->_db = 0; } /** @@ -121,6 +144,37 @@ class Credis_Sentinel return $this; } + /** + * @param null|string $username + * @return $this + */ + public function setClientUsername($username) + { + $this->_username = $username; + return $this; + } + + /** + * @param null|string $replicaCmd + * @return $this + */ + public function setReplicaCommand($replicaCmd) + { + $this->_replicaCmd = $replicaCmd; + return $this; + } + + public function detectRedisVersion() + { + if ($this->_redisVersion !== null && $this->_replicaCmd !== null) { + return; + } + $serverInfo = $this->info('server'); + $this->_redisVersion = $serverInfo['redis_version']; + // Redis v7+ renames the replica command to 'replicas' instead of 'slaves' + $this->_replicaCmd = version_compare($this->_redisVersion, '7.0.0', '>=') ? 'replicas' : 'slaves'; + } + /** * @return Credis_Sentinel * @deprecated @@ -141,10 +195,10 @@ class Credis_Sentinel public function createMasterClient($name) { $master = $this->getMasterAddressByName($name); - if(!isset($master[0]) || !isset($master[1])){ + if (!isset($master[0]) || !isset($master[1])) { throw new CredisException('Master not found'); } - return new Credis_Client($master[0], $master[1], $this->_timeout, $this->_persistent, $this->_db, $this->_password); + return new Credis_Client($master[0], $master[1], $this->_timeout, $this->_persistent, $this->_db, $this->_password, $this->_username); } /** @@ -154,7 +208,7 @@ class Credis_Sentinel */ public function getMasterClient($name) { - if(!isset($this->_master[$name])){ + if (!isset($this->_master[$name])) { $this->_master[$name] = $this->createMasterClient($name); } return $this->_master[$name]; @@ -171,12 +225,12 @@ class Credis_Sentinel { $slaves = $this->slaves($name); $workingSlaves = array(); - foreach($slaves as $slave) { - if(!isset($slave[9])){ + foreach ($slaves as $slave) { + if (!isset($slave[9])) { throw new CredisException('Can\' retrieve slave status'); } - if(!strstr($slave[9],'s_down') && !strstr($slave[9],'disconnected')) { - $workingSlaves[] = new Credis_Client($slave[3], $slave[5], $this->_timeout, $this->_persistent, $this->_db, $this->_password); + if (!strstr($slave[9], 's_down') && !strstr($slave[9], 'disconnected')) { + $workingSlaves[] = new Credis_Client($slave[3], $slave[5], $this->_timeout, $this->_persistent, $this->_db, $this->_password, $this->_username); } } return $workingSlaves; @@ -189,7 +243,7 @@ class Credis_Sentinel */ public function getSlaveClients($name) { - if(!isset($this->_slaves[$name])){ + if (!isset($this->_slaves[$name])) { $this->_slaves[$name] = $this->createSlaveClients($name); } return $this->_slaves[$name]; @@ -211,27 +265,27 @@ class Credis_Sentinel * @throws CredisException * @deprecated */ - public function createCluster($name, $db=0, $replicas=128, $selectRandomSlave=true, $writeOnly=false, $masterOnly=false) + public function createCluster($name, $db = 0, $replicas = 128, $selectRandomSlave = true, $writeOnly = false, $masterOnly = false) { $clients = array(); $workingClients = array(); $master = $this->master($name); - if(strstr($master[9],'s_down') || strstr($master[9],'disconnected')) { + if (strstr($master[9], 's_down') || strstr($master[9], 'disconnected')) { throw new CredisException('The master is down'); } if (!$masterOnly) { $slaves = $this->slaves($name); - foreach($slaves as $slave){ - if(!strstr($slave[9],'s_down') && !strstr($slave[9],'disconnected')) { - $workingClients[] = array('host'=>$slave[3],'port'=>$slave[5],'master'=>false,'db'=>$db,'password'=>$this->_password); + foreach ($slaves as $slave) { + if (!strstr($slave[9], 's_down') && !strstr($slave[9], 'disconnected')) { + $workingClients[] = array('host' => $slave[3], 'port' => $slave[5], 'master' => false, 'db' => $db, 'password' => $this->_password); } } - if(count($workingClients)>0){ - if($selectRandomSlave){ - if(!$writeOnly){ - $workingClients[] = array('host'=>$master[3],'port'=>$master[5],'master'=>false,'db'=>$db,'password'=>$this->_password); + if (count($workingClients) > 0) { + if ($selectRandomSlave) { + if (!$writeOnly) { + $workingClients[] = array('host' => $master[3], 'port' => $master[5], 'master' => false, 'db' => $db, 'password' => $this->_password); } - $clients[] = $workingClients[rand(0,count($workingClients)-1)]; + $clients[] = $workingClients[rand(0, count($workingClients) - 1)]; } else { $clients = $workingClients; } @@ -239,8 +293,8 @@ class Credis_Sentinel } else { $writeOnly = false; } - $clients[] = array('host'=>$master[3],'port'=>$master[5], 'db'=>$db ,'master'=>true,'write_only'=>$writeOnly,'password'=>$this->_password); - return new Credis_Cluster($clients,$replicas,$this->_standAlone); + $clients[] = array('host' => $master[3], 'port' => $master[5], 'db' => $db, 'master' => true, 'write_only' => $writeOnly, 'password' => $this->_password); + return new Credis_Cluster($clients, $replicas, $this->_standAlone); } /** @@ -255,9 +309,9 @@ class Credis_Sentinel * @throws CredisException * @deprecated */ - public function getCluster($name, $db=0, $replicas=128, $selectRandomSlave=true, $writeOnly=false, $masterOnly=false) + public function getCluster($name, $db = 0, $replicas = 128, $selectRandomSlave = true, $writeOnly = false, $masterOnly = false) { - if(!isset($this->_cluster[$name])){ + if (!isset($this->_cluster[$name])) { $this->_cluster[$name] = $this->createCluster($name, $db, $replicas, $selectRandomSlave, $writeOnly, $masterOnly); } return $this->_cluster[$name]; @@ -271,8 +325,8 @@ class Credis_Sentinel */ public function __call($name, $args) { - array_unshift($args,$name); - return call_user_func(array($this->_client,'sentinel'),$args); + array_unshift($args, $name); + return call_user_func(array($this->_client, 'sentinel'), $args); } /** @@ -284,8 +338,7 @@ class Credis_Sentinel */ public function info($section = null) { - if ($section) - { + if ($section) { return $this->_client->info($section); } return $this->_client->info(); @@ -307,7 +360,10 @@ class Credis_Sentinel */ public function slaves($name) { - return $this->_client->sentinel('slaves',$name); + if ($this->_replicaCmd === null) { + $this->detectRedisVersion(); + } + return $this->_client->sentinel($this->_replicaCmd, $name); } /** @@ -317,7 +373,7 @@ class Credis_Sentinel */ public function master($name) { - return $this->_client->sentinel('master',$name); + return $this->_client->sentinel('master', $name); } /** @@ -327,7 +383,7 @@ class Credis_Sentinel */ public function getMasterAddressByName($name) { - return $this->_client->sentinel('get-master-addr-by-name',$name); + return $this->_client->sentinel('get-master-addr-by-name', $name); } /** @@ -346,7 +402,7 @@ class Credis_Sentinel */ public function failover($name) { - return $this->_client->sentinel('failover',$name); + return $this->_client->sentinel('failover', $name); } /** diff --git a/system/vendor/colinmollenhour/credis/testenv/docker-compose.yml b/system/vendor/colinmollenhour/credis/testenv/docker-compose.yml index c527d8f..8b2b6bf 100644 --- a/system/vendor/colinmollenhour/credis/testenv/docker-compose.yml +++ b/system/vendor/colinmollenhour/credis/testenv/docker-compose.yml @@ -1,30 +1,5 @@ version: '2' services: - - php-56: - build: env/php-5.6/ - volumes: - - ../:/src/ - - php-70: - build: env/php-7.0/ - volumes: - - ../:/src/ - - php-71: - build: env/php-7.1/ - volumes: - - ../:/src/ - - php-72: - build: env/php-7.2/ - volumes: - - ../:/src/ - - php-73: - build: env/php-7.3/ - volumes: - - ../:/src/ php-74: build: env/php-7.4/ diff --git a/system/vendor/colinmollenhour/credis/testenv/env/php-5.6/Dockerfile b/system/vendor/colinmollenhour/credis/testenv/env/php-5.6/Dockerfile deleted file mode 100644 index c6378a4..0000000 --- a/system/vendor/colinmollenhour/credis/testenv/env/php-5.6/Dockerfile +++ /dev/null @@ -1,24 +0,0 @@ -FROM php:5.6 -ENV phpunit_verison 5.7 -ENV redis_version 4.0.11 - -RUN apt-get update && \ - apt-get install -y wget - -RUN wget https://phar.phpunit.de/phpunit-${phpunit_verison}.phar && \ - chmod +x phpunit-${phpunit_verison}.phar && \ - mv phpunit-${phpunit_verison}.phar /usr/local/bin/phpunit - -# install php extension -RUN yes '' | pecl install -f redis-4.3.0 && \ - docker-php-ext-enable redis - -# install redis server -RUN wget http://download.redis.io/releases/redis-${redis_version}.tar.gz && \ - tar -xzf redis-${redis_version}.tar.gz && \ - make -s -C redis-${redis_version} -j - -CMD PATH=$PATH:/usr/local/bin/:/redis-${redis_version}/src/ && \ - cp -rp /src /app && \ - cd /app && \ - phpunit diff --git a/system/vendor/colinmollenhour/credis/testenv/env/php-7.0/Dockerfile b/system/vendor/colinmollenhour/credis/testenv/env/php-7.0/Dockerfile deleted file mode 100644 index 82dfef9..0000000 --- a/system/vendor/colinmollenhour/credis/testenv/env/php-7.0/Dockerfile +++ /dev/null @@ -1,25 +0,0 @@ -FROM php:7.0 -ENV phpunit_verison 6.5 -ENV redis_version 6.0.8 - -RUN apt-get update && \ - apt-get install -y wget libssl-dev - -RUN wget https://phar.phpunit.de/phpunit-${phpunit_verison}.phar && \ - chmod +x phpunit-${phpunit_verison}.phar && \ - mv phpunit-${phpunit_verison}.phar /usr/local/bin/phpunit - -# install php extension -RUN yes '' | pecl install -f redis && \ - docker-php-ext-enable redis - -# install redis server -RUN wget http://download.redis.io/releases/redis-${redis_version}.tar.gz && \ - tar -xzf redis-${redis_version}.tar.gz && \ - export BUILD_TLS=yes && \ - make -s -C redis-${redis_version} -j - -CMD PATH=$PATH:/usr/local/bin/:/redis-${redis_version}/src/ && \ - cp -rp /src /app && \ - cd /app && \ - phpunit diff --git a/system/vendor/colinmollenhour/credis/testenv/env/php-7.1/Dockerfile b/system/vendor/colinmollenhour/credis/testenv/env/php-7.1/Dockerfile deleted file mode 100644 index e75a4c0..0000000 --- a/system/vendor/colinmollenhour/credis/testenv/env/php-7.1/Dockerfile +++ /dev/null @@ -1,25 +0,0 @@ -FROM php:7.1 -ENV phpunit_verison 7.5 -ENV redis_version 6.0.8 - -RUN apt-get update && \ - apt-get install -y wget libssl-dev - -RUN wget https://phar.phpunit.de/phpunit-${phpunit_verison}.phar && \ - chmod +x phpunit-${phpunit_verison}.phar && \ - mv phpunit-${phpunit_verison}.phar /usr/local/bin/phpunit - -# install php extension -RUN yes '' | pecl install -f redis && \ - docker-php-ext-enable redis - -# install redis server -RUN wget http://download.redis.io/releases/redis-${redis_version}.tar.gz && \ - tar -xzf redis-${redis_version}.tar.gz && \ - export BUILD_TLS=yes && \ - make -s -C redis-${redis_version} -j - -CMD PATH=$PATH:/usr/local/bin/:/redis-${redis_version}/src/ && \ - cp -rp /src /app && \ - cd /app && \ - phpunit diff --git a/system/vendor/colinmollenhour/credis/testenv/env/php-7.2/Dockerfile b/system/vendor/colinmollenhour/credis/testenv/env/php-7.2/Dockerfile deleted file mode 100644 index 0e34aab..0000000 --- a/system/vendor/colinmollenhour/credis/testenv/env/php-7.2/Dockerfile +++ /dev/null @@ -1,25 +0,0 @@ -FROM php:7.2 -ENV phpunit_verison 7.5 -ENV redis_version 6.0.8 - -RUN apt-get update && \ - apt-get install -y wget libssl-dev - -RUN wget https://phar.phpunit.de/phpunit-${phpunit_verison}.phar && \ - chmod +x phpunit-${phpunit_verison}.phar && \ - mv phpunit-${phpunit_verison}.phar /usr/local/bin/phpunit - -# install php extension -RUN yes '' | pecl install -f redis && \ - docker-php-ext-enable redis - -# install redis server -RUN wget http://download.redis.io/releases/redis-${redis_version}.tar.gz && \ - tar -xzf redis-${redis_version}.tar.gz && \ - export BUILD_TLS=yes && \ - make -s -C redis-${redis_version} -j - -CMD PATH=$PATH:/usr/local/bin/:/redis-${redis_version}/src/ && \ - cp -rp /src /app && \ - cd /app && \ - phpunit diff --git a/system/vendor/colinmollenhour/credis/testenv/env/php-7.3/Dockerfile b/system/vendor/colinmollenhour/credis/testenv/env/php-7.3/Dockerfile deleted file mode 100644 index 279ffa9..0000000 --- a/system/vendor/colinmollenhour/credis/testenv/env/php-7.3/Dockerfile +++ /dev/null @@ -1,25 +0,0 @@ -FROM php:7.3 -ENV phpunit_verison 7.5 -ENV redis_version 6.0.8 - -RUN apt-get update && \ - apt-get install -y wget libssl-dev - -RUN wget https://phar.phpunit.de/phpunit-${phpunit_verison}.phar && \ - chmod +x phpunit-${phpunit_verison}.phar && \ - mv phpunit-${phpunit_verison}.phar /usr/local/bin/phpunit - -# install php extension -RUN yes '' | pecl install -f redis && \ - docker-php-ext-enable redis - -# install redis server -RUN wget http://download.redis.io/releases/redis-${redis_version}.tar.gz && \ - tar -xzf redis-${redis_version}.tar.gz && \ - export BUILD_TLS=yes && \ - make -s -C redis-${redis_version} -j - -CMD PATH=$PATH:/usr/local/bin/:/redis-${redis_version}/src/ && \ - cp -rp /src /app && \ - cd /app && \ - phpunit diff --git a/system/vendor/colinmollenhour/php-redis-session-abstract/.gitignore b/system/vendor/colinmollenhour/php-redis-session-abstract/.gitignore new file mode 100644 index 0000000..756b807 --- /dev/null +++ b/system/vendor/colinmollenhour/php-redis-session-abstract/.gitignore @@ -0,0 +1,4 @@ +vendor/ +.idea/ +phpunit.xml +composer.lock diff --git a/system/vendor/colinmollenhour/php-redis-session-abstract/ISSUE_TEMPLATE.md b/system/vendor/colinmollenhour/php-redis-session-abstract/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..e0e5c33 --- /dev/null +++ b/system/vendor/colinmollenhour/php-redis-session-abstract/ISSUE_TEMPLATE.md @@ -0,0 +1,2 @@ + + diff --git a/system/vendor/colinmollenhour/php-redis-session-abstract/LICENSE b/system/vendor/colinmollenhour/php-redis-session-abstract/LICENSE new file mode 100644 index 0000000..058c73e --- /dev/null +++ b/system/vendor/colinmollenhour/php-redis-session-abstract/LICENSE @@ -0,0 +1,29 @@ +Copyright (c) 2013, Colin Mollenhour + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * The name of Colin Mollenhour may not be used to endorse or promote products + derived from this software without specific prior written permission. + + * Redistributions in any form must not change the Cm_RedisSession namespace. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/system/vendor/colinmollenhour/php-redis-session-abstract/README.md b/system/vendor/colinmollenhour/php-redis-session-abstract/README.md new file mode 100644 index 0000000..22d6e3b --- /dev/null +++ b/system/vendor/colinmollenhour/php-redis-session-abstract/README.md @@ -0,0 +1,68 @@ +# php-redis-session-abstract # + +### A Redis-based session handler with optimistic locking. ### + +#### Features: #### +- When a session's data size exceeds the compression threshold the session data will be compressed. +- Compression libraries supported are 'gzip', 'lzf', 'lz4', and 'snappy'. + - Gzip is the slowest but offers the best compression ratios. + - Lzf can be installed easily via PECL. + - Lz4 is supported by HHVM. +- Compression can be enabled, disabled, or reconfigured on the fly with no loss of session data. +- Expiration is handled by Redis; no garbage collection needed. +- Logs when sessions are not written due to not having or losing their lock. +- Limits the number of concurrent lock requests. +- Detects inactive waiting processes to prevent false-positives in concurrency throttling. +- Detects crashed processes to prevent session deadlocks (Linux only). +- Gives shorter session lifetimes to bots and crawlers to reduce wasted resources. +- Locking can be disabled entirely + +#### Locking Algorithm Properties: #### +- Only one process may get a write lock on a session. +- A process may lose it's lock if another process breaks it, in which case the session will not be written. +- The lock may be broken after `BREAK_AFTER` seconds and the process that gets the lock is indeterminate. +- Only `MAX_CONCURRENCY` processes may be waiting for a lock for the same session or else a ConcurrentConnectionsExceededException will be thrown. + +### Compression ## + +Session data compresses very well so using compression is a great way to increase your capacity without +dedicating a ton of RAM to Redis and reducing network utilization. +The default `compression threshold` is 2048 bytes so any session data equal to or larger than this size +will be compressed with the chosen `compression_lib` which is `gzip` by default. Compression can be disabled by setting the `compression_lib` to `none`. However, both `lzf` and +`snappy` offer much faster compression with comparable compression ratios so I definitely recommend using +one of these if you have root. lzf is easy to install via pecl: + + sudo pecl install lzf + +_NOTE:_ If using suhosin with session data encryption enabled (default is `suhosin.session.encrypt=on`), two things: + +1. You will probably get very poor compression ratios. +2. Lzf fails to compress the encrypted data in my experience. No idea why... + +If any compression lib fails to compress the session data an error will be logged in `system.log` and the +session will still be saved without compression. If you have `suhosin.session.encrypt=on` I would either +recommend disabling it (unless you are on a shared host since Magento does it's own session validation already) +or disable compression or at least don't use lzf with encryption enabled. + +## Bot Detection ## + +Bots and crawlers typically do not use cookies which means you may be storing thousands of sessions that +serve no purpose. Even worse, an attacker could use your limited session storage against you by flooding +your backend, thereby causing your legitimate sessions to get evicted. However, you don't want to misidentify +a user as a bot and kill their session unintentionally. This module uses both a regex as well as a +counter on the number of writes against the session to determine the session lifetime. + +## Using with [Cm_Cache_Backend_Redis](https://github.com/colinmollenhour/Cm_Cache_Backend_Redis) ## + +Using Cm_RedisSession alongside Cm_Cache_Backend_Redis should be no problem at all. However, it is strongly advised +to run two separate Redis instances even if they are running on the same server. Running two instances will +actually perform better since Redis is single-threaded so on a multi-core server is bound by the performance of +a single core. Also it makes sense to allocate varying amounts of memory to cache and sessions and to enforce different +"maxmemory" policies. If you absolutely must run one Redis instance for both then just don't use the same 'db' number. +But again, just run two Redis instances. + + +## License ## + + @copyright Copyright (c) 2013 Colin Mollenhour (http://colin.mollenhour.com) + This project is licensed under the "New BSD" license (see source). diff --git a/system/vendor/colinmollenhour/php-redis-session-abstract/composer.json b/system/vendor/colinmollenhour/php-redis-session-abstract/composer.json new file mode 100644 index 0000000..2f53b3d --- /dev/null +++ b/system/vendor/colinmollenhour/php-redis-session-abstract/composer.json @@ -0,0 +1,24 @@ +{ + "name":"colinmollenhour/php-redis-session-abstract", + "type":"library", + "license":"BSD-3-Clause", + "homepage":"https://github.com/colinmollenhour/php-redis-session-abstract", + "description":"A Redis-based session handler with optimistic locking", + "authors":[ + { + "name":"Colin Mollenhour" + } + ], + "require":{ + "php": "^5.5 || ^7.0 || ^8.0", + "colinmollenhour/credis":"~1.6" + }, + "require-dev": { + "phpunit/phpunit": "^9" + }, + "autoload": { + "psr-0": { + "Cm\\RedisSession\\": "src/" + } + } +} diff --git a/system/vendor/colinmollenhour/php-redis-session-abstract/phpunit.xml.dist b/system/vendor/colinmollenhour/php-redis-session-abstract/phpunit.xml.dist new file mode 100644 index 0000000..8f7c20e --- /dev/null +++ b/system/vendor/colinmollenhour/php-redis-session-abstract/phpunit.xml.dist @@ -0,0 +1,42 @@ + + + + + + tests + + + diff --git a/system/vendor/colinmollenhour/php-redis-session-abstract/src/Cm/RedisSession/ConcurrentConnectionsExceededException.php b/system/vendor/colinmollenhour/php-redis-session-abstract/src/Cm/RedisSession/ConcurrentConnectionsExceededException.php new file mode 100644 index 0000000..ae75f7b --- /dev/null +++ b/system/vendor/colinmollenhour/php-redis-session-abstract/src/Cm/RedisSession/ConcurrentConnectionsExceededException.php @@ -0,0 +1,35 @@ +config = $config; + $this->logger = $logger; + + $this->logger->setLogLevel($this->config->getLogLevel() ?: self::DEFAULT_LOG_LEVEL); + $timeStart = microtime(true); + + // Database config + $host = $this->config->getHost() ?: self::DEFAULT_HOST; + $port = $this->config->getPort() ?: self::DEFAULT_PORT; + $pass = $this->config->getPassword() ?: null; + $timeout = $this->config->getTimeout() ?: self::DEFAULT_TIMEOUT; + $persistent = $this->config->getPersistentIdentifier() ?: ''; + $this->_dbNum = $this->config->getDatabase() ?: self::DEFAULT_DATABASE; + + // General config + $this->_readOnly = $readOnly; + $this->_compressionThreshold = $this->config->getCompressionThreshold() ?: self::DEFAULT_COMPRESSION_THRESHOLD; + $this->_compressionLibrary = $this->config->getCompressionLibrary() ?: self::DEFAULT_COMPRESSION_LIBRARY; + $this->_maxConcurrency = $this->config->getMaxConcurrency() ?: self::DEFAULT_MAX_CONCURRENCY; + $this->_failAfter = $this->config->getFailAfter() ?: self::DEFAULT_FAIL_AFTER; + $this->_maxLifetime = $this->config->getMaxLifetime() ?: self::DEFAULT_MAX_LIFETIME; + $this->_minLifetime = $this->config->getMinLifetime() ?: self::DEFAULT_MIN_LIFETIME; + $this->_useLocking = ! $this->config->getDisableLocking(); + + // Use sleep time multiplier so fail after time is in seconds + $this->_failAfter = (int) round((1000000 / self::SLEEP_TIME) * $this->_failAfter); + + // Sentinel config + $sentinelServers = $this->config->getSentinelServers(); + $sentinelMaster = $this->config->getSentinelMaster(); + $sentinelVerifyMaster = $this->config->getSentinelVerifyMaster(); + $sentinelConnectRetries = $this->config->getSentinelConnectRetries(); + + // Connect and authenticate + if ($sentinelServers && $sentinelMaster) { + $servers = preg_split('/\s*,\s*/', trim($sentinelServers), NULL, PREG_SPLIT_NO_EMPTY); + $sentinel = NULL; + $exception = NULL; + for ($i = 0; $i <= $sentinelConnectRetries; $i++) // Try to connect to sentinels in round-robin fashion + foreach ($servers as $server) { + try { + $sentinelClient = new \Credis_Client($server, NULL, $timeout, $persistent); + $sentinelClient->forceStandalone(); + $sentinelClient->setMaxConnectRetries(0); + if ($pass) { + try { + $sentinelClient->auth($pass); + } catch (\CredisException $e) { + // Prevent throwing exception if Sentinel has no password set + if($e->getCode() !== 0 || strpos($e->getMessage(),'ERR Client sent AUTH, but no password is set') === false) { + throw $e; + } + } + } + + $sentinel = new \Credis_Sentinel($sentinelClient); + $sentinel + ->setClientTimeout($timeout) + ->setClientPersistent($persistent); + $redisMaster = $sentinel->getMasterClient($sentinelMaster); + if ($pass) $redisMaster->auth($pass); + + // Verify connected server is actually master as per Sentinel client spec + if ($sentinelVerifyMaster) { + $roleData = $redisMaster->role(); + if ( ! $roleData || $roleData[0] != 'master') { + usleep(100000); // Sleep 100ms and try again + $redisMaster = $sentinel->getMasterClient($sentinelMaster); + if ($pass) $redisMaster->auth($pass); + $roleData = $redisMaster->role(); + if ( ! $roleData || $roleData[0] != 'master') { + throw new Exception('Unable to determine master redis server.'); + } + } + } + if ($this->_dbNum || $persistent) $redisMaster->select(0); + + $this->_redis = $redisMaster; + break 2; + } catch (Exception $e) { + unset($sentinelClient); + $exception = $e; + } + } + unset($sentinel); + + if ( ! $this->_redis) { + throw new ConnectionFailedException('Unable to connect to a Redis: '.$exception->getMessage(), $exception); + } + } + else { + $this->_redis = new \Credis_Client($host, $port, $timeout, $persistent, 0, $pass); + if ($this->hasConnection() == false) { + throw new ConnectionFailedException('Unable to connect to Redis'); + } + } + + // Destructor order cannot be predicted + $this->_redis->setCloseOnDestruct(false); + $this->_log( + sprintf( + "%s initialized for connection to %s:%s after %.5f seconds", + get_class($this), + $this->_redis->getHost(), + $this->_redis->getPort(), + (microtime(true) - $timeStart) + ) + ); + } + + /** + * Open session + * + * @param string $savePath ignored + * @param string $sessionName ignored + * @return bool + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + #[\ReturnTypeWillChange] + public function open($savePath, $sessionName) + { + return true; + } + + /** + * @param $msg + * @param $level + */ + protected function _log($msg, $level = LoggerInterface::DEBUG) + { + $this->logger->log("{$this->_getPid()}: $msg", $level); + } + + /** + * Check Redis connection + * + * @return bool + */ + protected function hasConnection() + { + try { + $this->_redis->connect(); + $this->_log("Connected to Redis"); + return true; + } catch (\Exception $e) { + $this->logger->logException($e); + $this->_log('Unable to connect to Redis'); + return false; + } + } + + /** + * Fetch session data + * + * @param string $sessionId + * @return string + * @throws ConcurrentConnectionsExceededException + */ + #[\ReturnTypeWillChange] + public function read($sessionId) + { + // Get lock on session. Increment the "lock" field and if the new value is 1, we have the lock. + $sessionId = self::SESSION_PREFIX.$sessionId; + $tries = $waiting = $lock = 0; + $lockPid = $oldLockPid = null; // Restart waiting for lock when current lock holder changes + $detectZombies = false; + $breakAfter = $this->_getBreakAfter(); + $timeStart = microtime(true); + $this->_log(sprintf("Attempting to take lock on ID %s", $sessionId)); + + $this->_redis->select($this->_dbNum); + while ($this->_useLocking && !$this->_readOnly) + { + // Increment lock value for this session and retrieve the new value + $oldLock = $lock; + $lock = $this->_redis->hIncrBy($sessionId, 'lock', 1); + + // Get the pid of the process that has the lock + if ($lock != 1 && $tries + 1 >= $breakAfter) { + $lockPid = $this->_redis->hGet($sessionId, 'pid'); + } + + // If we got the lock, update with our pid and reset lock and expiration + if ( $lock == 1 // We actually do have the lock + || ( + $tries >= $breakAfter // We are done waiting and want to start trying to break it + && $oldLockPid == $lockPid // Nobody else got the lock while we were waiting + ) + ) { + $this->_hasLock = true; + break; + } + + // Otherwise, add to "wait" counter and continue + else if ( ! $waiting) { + $i = 0; + do { + $waiting = $this->_redis->hIncrBy($sessionId, 'wait', 1); + } while (++$i < $this->_maxConcurrency && $waiting < 1); + } + + // Handle overloaded sessions + else { + // Detect broken sessions (e.g. caused by fatal errors) + if ($detectZombies) { + $detectZombies = false; + // Lock shouldn't be less than old lock (another process broke the lock) + if ($lock > $oldLock + // Lock should be old+waiting, otherwise there must be a dead process + && $lock + 1 < $oldLock + $waiting + ) { + // Reset session to fresh state + $this->_log( + sprintf( + "Detected zombie waiter after %.5f seconds for ID %s (%d waiting)", + (microtime(true) - $timeStart), + $sessionId, $waiting + ), + LoggerInterface::INFO + ); + $waiting = $this->_redis->hIncrBy($sessionId, 'wait', -1); + continue; + } + } + + // Limit concurrent lock waiters to prevent server resource hogging + if ($waiting >= $this->_maxConcurrency) { + // Overloaded sessions get 503 errors + $this->_redis->hIncrBy($sessionId, 'wait', -1); + $this->_sessionWritten = true; // Prevent session from getting written + $writes = $this->_redis->hGet($sessionId, 'writes'); + $this->_log( + sprintf( + 'Session concurrency exceeded for ID %s; displaying HTTP 503 (%s waiting, %s total ' + . 'requests)', + $sessionId, $waiting, $writes + ), + LoggerInterface::WARNING + ); + throw new ConcurrentConnectionsExceededException(); + } + } + + $tries++; + $oldLockPid = $lockPid; + $sleepTime = self::SLEEP_TIME; + + // Detect dead lock waiters + if ($tries % self::DETECT_ZOMBIES == 1) { + $detectZombies = true; + $sleepTime += 10000; // sleep + 0.01 seconds + } + // Detect dead lock holder every 10 seconds (only works on same node as lock holder) + if ($tries % self::DETECT_ZOMBIES == 0) { + $this->_log( + sprintf( + "Checking for zombies after %.5f seconds of waiting...", (microtime(true) - $timeStart) + ) + ); + + $pid = $this->_redis->hGet($sessionId, 'pid'); + if ($pid && ! $this->_pidExists($pid)) { + // Allow a live process to get the lock + $this->_redis->hSet($sessionId, 'lock', 0); + $this->_log( + sprintf( + "Detected zombie process (%s) for %s (%s waiting)", + $pid, $sessionId, $waiting + ), + LoggerInterface::INFO + ); + continue; + } + } + // Timeout + if ($tries >= $breakAfter + $this->_failAfter) { + $this->_hasLock = false; + $this->_log( + sprintf( + 'Giving up on read lock for ID %s after %.5f seconds (%d attempts)', + $sessionId, + (microtime(true) - $timeStart), + $tries + ), + LoggerInterface::NOTICE + ); + break; + } + else { + $this->_log( + sprintf( + "Waiting %.2f seconds for lock on ID %s (%d tries, lock pid is %s, %.5f seconds elapsed)", + $sleepTime / 1000000, + $sessionId, + $tries, + $lockPid, + (microtime(true) - $timeStart) + ) + ); + usleep($sleepTime); + } + } + $this->failedLockAttempts = $tries; + + // Session can be read even if it was not locked by this pid! + $timeStart2 = microtime(true); + list($sessionData, $sessionWrites) = array_values($this->_redis->hMGet($sessionId, array('data','writes'))); + $this->_log(sprintf("Data read for ID %s in %.5f seconds", $sessionId, (microtime(true) - $timeStart2))); + $this->_sessionWrites = (int) $sessionWrites; + + // This process is no longer waiting for a lock + if ($tries > 0) { + $this->_redis->hIncrBy($sessionId, 'wait', -1); + } + + // This process has the lock, save the pid + if ($this->_hasLock) { + $setData = array( + 'pid' => $this->_getPid(), + 'lock' => 1, + ); + + // Save request data in session so if a lock is broken we can know which page it was for debugging + if (empty($_SERVER['REQUEST_METHOD'])) { + $setData['req'] = @$_SERVER['SCRIPT_NAME']; + } else { + $setData['req'] = $_SERVER['REQUEST_METHOD']." ".@$_SERVER['SERVER_NAME'].@$_SERVER['REQUEST_URI']; + } + if ($lock != 1) { + $this->_log( + sprintf( + "Successfully broke lock for ID %s after %.5f seconds (%d attempts). Lock: %d\nLast request of " + . "broken lock: %s", + $sessionId, + (microtime(true) - $timeStart), + $tries, + $lock, + $this->_redis->hGet($sessionId, 'req') + ), + LoggerInterface::INFO + ); + } + } + + // Set session data and expiration + $this->_redis->pipeline(); + if ( ! empty($setData)) { + $this->_redis->hMSet($sessionId, $setData); + } + $this->_redis->expire($sessionId, 3600*6); // Expiration will be set to correct value when session is written + $this->_redis->exec(); + + // Reset flag in case of multiple session read/write operations + $this->_sessionWritten = false; + + return $sessionData ? (string) $this->_decodeData($sessionData) : ''; + } + + /** + * Update session + * + * @param string $sessionId + * @param string $sessionData + * @return boolean + */ + #[\ReturnTypeWillChange] + public function write($sessionId, $sessionData) + { + if ($this->_sessionWritten || $this->_readOnly) { + $this->_log(sprintf(($this->_sessionWritten ? "Repeated" : "Read-only") . " session write detected; skipping for ID %s", $sessionId)); + return true; + } + $this->_sessionWritten = true; + $timeStart = microtime(true); + + // Do not overwrite the session if it is locked by another pid + try { + if($this->_dbNum) $this->_redis->select($this->_dbNum); // Prevent conflicts with other connections? + + if ( ! $this->_useLocking + || ( ! ($pid = $this->_redis->hGet('sess_'.$sessionId, 'pid')) || $pid == $this->_getPid()) + ) { + $this->_writeRawSession($sessionId, $sessionData, $this->getLifeTime()); + $this->_log(sprintf("Data written to ID %s in %.5f seconds", $sessionId, (microtime(true) - $timeStart))); + + } + else { + if ($this->_hasLock) { + $this->_log(sprintf("Did not write session for ID %s: another process took the lock.", + $sessionId + ), LoggerInterface::WARNING); + } else { + $this->_log(sprintf("Did not write session for ID %s: unable to acquire lock.", + $sessionId + ), LoggerInterface::WARNING); + } + } + } + catch(\Exception $e) { + $this->logger->logException($e); + return false; + } + return true; + } + + /** + * Destroy session + * + * @param string $sessionId + * @return boolean + */ + #[\ReturnTypeWillChange] + public function destroy($sessionId) + { + $this->_log(sprintf("Destroying ID %s", $sessionId)); + $this->_redis->pipeline(); + if($this->_dbNum) $this->_redis->select($this->_dbNum); + $this->_redis->del(self::SESSION_PREFIX.$sessionId); + $this->_redis->exec(); + return true; + } + + /** + * Overridden to prevent calling getLifeTime at shutdown + * + * @return bool + */ + #[\ReturnTypeWillChange] + public function close() + { + $this->_log("Closing connection"); + if ($this->_redis) $this->_redis->close(); + return true; + } + + /** + * Garbage collection + * + * @param int $maxLifeTime ignored + * @return boolean + */ + #[\ReturnTypeWillChange] + public function gc($maxLifeTime) + { + return true; + } + + /** + * Get the number of failed lock attempts + * + * @return int + */ + public function getFailedLockAttempts() + { + return $this->failedLockAttempts; + } + + static public function isBotAgent($userAgent) + { + $isBot = !$userAgent || preg_match(self::BOT_REGEX, $userAgent); + + if (is_array(self::$_botCheckCallback) && isset(self::$_botCheckCallback[0]) && self::$_botCheckCallback[1] && method_exists(self::$_botCheckCallback[0], self::$_botCheckCallback[1])) { + $isBot = (bool) call_user_func_array(self::$_botCheckCallback, [$userAgent, $isBot]); + } + + return $isBot; + } + + /** + * Get lock lifetime + * + * @return int|mixed + */ + protected function getLifeTime() + { + if (is_null($this->_lifeTime)) { + $lifeTime = null; + + // Detect bots by user agent + $botLifetime = is_null($this->config->getBotLifetime()) ? self::DEFAULT_BOT_LIFETIME : $this->config->getBotLifetime(); + if ($botLifetime) { + $userAgent = empty($_SERVER['HTTP_USER_AGENT']) ? false : $_SERVER['HTTP_USER_AGENT']; + if (self::isBotAgent($userAgent)) { + $this->_log(sprintf("Bot detected for user agent: %s", $userAgent)); + $botFirstLifetime = is_null($this->config->getBotFirstLifetime()) ? self::DEFAULT_BOT_FIRST_LIFETIME : $this->config->getBotFirstLifetime(); + if ($this->_sessionWrites <= 1 && $botFirstLifetime) { + $lifeTime = $botFirstLifetime * (1+$this->_sessionWrites); + } else { + $lifeTime = $botLifetime; + } + } + } + + // Use different lifetime for first write + if ($lifeTime === null && $this->_sessionWrites <= 1) { + $firstLifetime = is_null($this->config->getFirstLifetime()) ? self::DEFAULT_FIRST_LIFETIME : $this->config->getFirstLifetime(); + if ($firstLifetime) { + $lifeTime = $firstLifetime * (1+$this->_sessionWrites); + } + } + + // Neither bot nor first write + if ($lifeTime === null) { + $lifeTime = $this->config->getLifetime(); + } + + $this->_lifeTime = $lifeTime; + if ($this->_lifeTime < $this->_minLifetime) { + $this->_lifeTime = $this->_minLifetime; + } + if ($this->_lifeTime > $this->_maxLifetime) { + $this->_lifeTime = $this->_maxLifetime; + } + } + return $this->_lifeTime; + } + + /** + * Encode data + * + * @param string $data + * @return string + */ + protected function _encodeData($data) + { + $originalDataSize = strlen($data); + if ($this->_compressionThreshold > 0 && $this->_compressionLibrary != 'none' && $originalDataSize >= $this->_compressionThreshold) { + $this->_log(sprintf("Compressing %s bytes with %s", $originalDataSize,$this->_compressionLibrary)); + $timeStart = microtime(true); + $prefix = ':'.substr($this->_compressionLibrary,0,2).':'; + switch($this->_compressionLibrary) { + case 'snappy': $data = snappy_compress($data); break; + case 'lzf': $data = lzf_compress($data); break; + case 'lz4': $data = lz4_compress($data); $prefix = ':l4:'; break; + case 'gzip': $data = gzcompress($data, 1); break; + } + if($data) { + $data = $prefix.$data; + $this->_log( + sprintf( + "Data compressed by %.1f percent in %.5f seconds", + ($originalDataSize == 0 ? 0 : (100 - (strlen($data) / $originalDataSize * 100))), + (microtime(true) - $timeStart) + ) + ); + } else { + $this->_log( + sprintf("Could not compress session data using %s", $this->_compressionLibrary), + LoggerInterface::WARNING + ); + } + } + return $data; + } + + /** + * Decode data + * + * @param string $data + * @return string + */ + protected function _decodeData($data) + { + switch (substr($data,0,4)) { + // asking the data which library it uses allows for transparent changes of libraries + case ':sn:': $data = snappy_uncompress(substr($data,4)); break; + case ':lz:': $data = lzf_decompress(substr($data,4)); break; + case ':l4:': $data = lz4_uncompress(substr($data,4)); break; + case ':gz:': $data = gzuncompress(substr($data,4)); break; + } + return $data; + } + + /** + * Write session data to Redis + * + * @param $id + * @param $data + * @param $lifetime + * @throws \Exception + */ + protected function _writeRawSession($id, $data, $lifetime) + { + $sessionId = 'sess_' . $id; + $this->_redis->pipeline() + ->select($this->_dbNum) + ->hMSet($sessionId, array( + 'data' => $this->_encodeData($data), + 'lock' => 0, // 0 so that next lock attempt will get 1 + )) + ->hIncrBy($sessionId, 'writes', 1) + ->expire($sessionId, min((int)$lifetime, (int)$this->_maxLifetime)) + ->exec(); + } + + /** + * Get pid + * + * @return string + */ + protected function _getPid() + { + return gethostname().'|'.getmypid(); + } + + /** + * Check if pid exists + * + * @param $pid + * @return bool + */ + protected function _pidExists($pid) + { + list($host,$pid) = explode('|', $pid); + if (PHP_OS != 'Linux' || $host != gethostname()) { + return true; + } + return @file_exists('/proc/'.$pid); + } + + /** + * Get break time, calculated later than other config settings due to requiring session name to be set + * + * @return int + */ + protected function _getBreakAfter() + { + // Has break after already been calculated? Only fetch from config once, then reuse variable. + if (!$this->_breakAfter) { + // Fetch relevant setting from config using session name + $this->_breakAfter = (float)($this->config->getBreakAfter() ?: self::DEFAULT_BREAK_AFTER); + // Use sleep time multiplier so break time is in seconds + $this->_breakAfter = (int)round((1000000 / self::SLEEP_TIME) * $this->_breakAfter); + } + + return $this->_breakAfter; + } +} diff --git a/system/vendor/colinmollenhour/php-redis-session-abstract/src/Cm/RedisSession/Handler/ConfigInterface.php b/system/vendor/colinmollenhour/php-redis-session-abstract/src/Cm/RedisSession/Handler/ConfigInterface.php new file mode 100644 index 0000000..a315c20 --- /dev/null +++ b/system/vendor/colinmollenhour/php-redis-session-abstract/src/Cm/RedisSession/Handler/ConfigInterface.php @@ -0,0 +1,195 @@ +config = $this->createMock(ConfigInterface::class); + $this->logger = $this->createMock(LoggerInterface::class); + $this->handler = new Handler($this->config, $this->logger); + } + + /** + * Smoke test: open and close connection + */ + public function testOpenClose() + { + $this->assertTrue($this->handler->open('', '')); + + $this->logger->expects($this->once()) + ->method('log') + ->with($this->stringContains('Closing connection'), LoggerInterface::DEBUG); + + $this->assertTrue($this->handler->close()); + } + + /** + * Test basic handler operations + */ + public function testHandler() + { + $sessionId = 1; + $data = 'data'; + $this->handler->destroy($sessionId); + $this->assertTrue($this->handler->write($sessionId, $data)); + $this->assertEquals(0, $this->handler->getFailedLockAttempts()); + $this->assertEquals($data, $this->handler->read($sessionId)); + $this->handler->destroy($sessionId); + $this->assertEquals('', $this->handler->read($sessionId)); + $this->assertTrue($this->handler->close()); + } +} diff --git a/system/vendor/composer/autoload_classmap.php b/system/vendor/composer/autoload_classmap.php index bcda3d9..4b9953c 100644 --- a/system/vendor/composer/autoload_classmap.php +++ b/system/vendor/composer/autoload_classmap.php @@ -6,6 +6,7 @@ $vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname(dirname($vendorDir)); return array( + 'Cm_Cache_Backend_Redis' => $vendorDir . '/colinmollenhour/cache-backend-redis/Cm/Cache/Backend/Redis.php', 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', 'CredisException' => $vendorDir . '/colinmollenhour/credis/Client.php', 'Credis_Client' => $vendorDir . '/colinmollenhour/credis/Client.php', diff --git a/system/vendor/composer/autoload_namespaces.php b/system/vendor/composer/autoload_namespaces.php index 74176c7..d2428f8 100644 --- a/system/vendor/composer/autoload_namespaces.php +++ b/system/vendor/composer/autoload_namespaces.php @@ -7,4 +7,5 @@ $baseDir = dirname(dirname($vendorDir)); return array( 'Mustache' => array($vendorDir . '/mustache/mustache/src'), + 'Cm\\RedisSession\\' => array($vendorDir . '/colinmollenhour/php-redis-session-abstract/src'), ); diff --git a/system/vendor/composer/autoload_static.php b/system/vendor/composer/autoload_static.php index 802bea0..188b79d 100644 --- a/system/vendor/composer/autoload_static.php +++ b/system/vendor/composer/autoload_static.php @@ -64,9 +64,17 @@ class ComposerStaticInit7bc5673f1c1ff265570d44e4bc291db0 0 => __DIR__ . '/..' . '/mustache/mustache/src', ), ), + 'C' => + array ( + 'Cm\\RedisSession\\' => + array ( + 0 => __DIR__ . '/..' . '/colinmollenhour/php-redis-session-abstract/src', + ), + ), ); public static $classMap = array ( + 'Cm_Cache_Backend_Redis' => __DIR__ . '/..' . '/colinmollenhour/cache-backend-redis/Cm/Cache/Backend/Redis.php', 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', 'CredisException' => __DIR__ . '/..' . '/colinmollenhour/credis/Client.php', 'Credis_Client' => __DIR__ . '/..' . '/colinmollenhour/credis/Client.php', diff --git a/system/vendor/composer/installed.json b/system/vendor/composer/installed.json index 697ffc0..7ad679f 100644 --- a/system/vendor/composer/installed.json +++ b/system/vendor/composer/installed.json @@ -1,18 +1,66 @@ { "packages": [ + { + "name": "colinmollenhour/cache-backend-redis", + "version": "1.17.1", + "version_normalized": "1.17.1.0", + "source": { + "type": "git", + "url": "https://github.com/colinmollenhour/Cm_Cache_Backend_Redis.git", + "reference": "d403f4473e1b3cc616fa59d187e817543b6620c1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/colinmollenhour/Cm_Cache_Backend_Redis/zipball/d403f4473e1b3cc616fa59d187e817543b6620c1", + "reference": "d403f4473e1b3cc616fa59d187e817543b6620c1", + "shasum": "" + }, + "require": { + "colinmollenhour/credis": "^1.14" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.4", + "phpunit/phpunit": "^9", + "zf1s/zend-cache": "~1.15" + }, + "time": "2023-12-21T21:56:18+00:00", + "type": "magento-module", + "installation-source": "dist", + "autoload": { + "classmap": [ + "Cm/Cache/Backend/Redis.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause-Modification" + ], + "authors": [ + { + "name": "Colin Mollenhour" + } + ], + "description": "Zend_Cache backend using Redis with full support for tags.", + "homepage": "https://github.com/colinmollenhour/Cm_Cache_Backend_Redis", + "support": { + "issues": "https://github.com/colinmollenhour/Cm_Cache_Backend_Redis/issues", + "source": "https://github.com/colinmollenhour/Cm_Cache_Backend_Redis/tree/1.17.1" + }, + "install-path": "../colinmollenhour/cache-backend-redis" + }, { "name": "colinmollenhour/credis", - "version": "v1.13.0", - "version_normalized": "1.13.0.0", + "version": "v1.16.0", + "version_normalized": "1.16.0.0", "source": { "type": "git", "url": "https://github.com/colinmollenhour/credis.git", - "reference": "afec8e58ec93d2291c127fa19709a048f28641e5" + "reference": "5641140e14a9679f5a6f66c97268727f9558b881" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/colinmollenhour/credis/zipball/afec8e58ec93d2291c127fa19709a048f28641e5", - "reference": "afec8e58ec93d2291c127fa19709a048f28641e5", + "url": "https://api.github.com/repos/colinmollenhour/credis/zipball/5641140e14a9679f5a6f66c97268727f9558b881", + "reference": "5641140e14a9679f5a6f66c97268727f9558b881", "shasum": "" }, "require": { @@ -21,7 +69,7 @@ "suggest": { "ext-redis": "Improved performance for communicating with redis" }, - "time": "2022-04-07T14:57:22+00:00", + "time": "2023-10-26T17:02:51+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -46,10 +94,57 @@ "homepage": "https://github.com/colinmollenhour/credis", "support": { "issues": "https://github.com/colinmollenhour/credis/issues", - "source": "https://github.com/colinmollenhour/credis/tree/v1.13.0" + "source": "https://github.com/colinmollenhour/credis/tree/v1.16.0" }, "install-path": "../colinmollenhour/credis" }, + { + "name": "colinmollenhour/php-redis-session-abstract", + "version": "v1.4.5", + "version_normalized": "1.4.5.0", + "source": { + "type": "git", + "url": "https://github.com/colinmollenhour/php-redis-session-abstract.git", + "reference": "77ad0c1637ae6ea059f1f8e9fbdac6469242a16d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/colinmollenhour/php-redis-session-abstract/zipball/77ad0c1637ae6ea059f1f8e9fbdac6469242a16d", + "reference": "77ad0c1637ae6ea059f1f8e9fbdac6469242a16d", + "shasum": "" + }, + "require": { + "colinmollenhour/credis": "~1.6", + "php": "^5.5 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9" + }, + "time": "2021-12-01T21:16:01+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-0": { + "Cm\\RedisSession\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin Mollenhour" + } + ], + "description": "A Redis-based session handler with optimistic locking", + "homepage": "https://github.com/colinmollenhour/php-redis-session-abstract", + "support": { + "issues": "https://github.com/colinmollenhour/php-redis-session-abstract/issues", + "source": "https://github.com/colinmollenhour/php-redis-session-abstract/tree/v1.4.5" + }, + "install-path": "../colinmollenhour/php-redis-session-abstract" + }, { "name": "mustache/mustache", "version": "v2.14.1", diff --git a/system/vendor/composer/installed.php b/system/vendor/composer/installed.php index 62cfc5c..28b9a54 100644 --- a/system/vendor/composer/installed.php +++ b/system/vendor/composer/installed.php @@ -5,7 +5,7 @@ 'type' => 'library', 'install_path' => __DIR__ . '/../../../', 'aliases' => array(), - 'reference' => '7d0624c01565cbad4144c8b6d2004348d01e86b1', + 'reference' => '1bad005e7c2659614810ae38a0ad11ca6d7c7bde', 'name' => '__root__', 'dev' => true, ), @@ -16,16 +16,34 @@ 'type' => 'library', 'install_path' => __DIR__ . '/../../../', 'aliases' => array(), - 'reference' => '7d0624c01565cbad4144c8b6d2004348d01e86b1', + 'reference' => '1bad005e7c2659614810ae38a0ad11ca6d7c7bde', + 'dev_requirement' => false, + ), + 'colinmollenhour/cache-backend-redis' => array( + 'pretty_version' => '1.17.1', + 'version' => '1.17.1.0', + 'type' => 'magento-module', + 'install_path' => __DIR__ . '/../colinmollenhour/cache-backend-redis', + 'aliases' => array(), + 'reference' => 'd403f4473e1b3cc616fa59d187e817543b6620c1', 'dev_requirement' => false, ), 'colinmollenhour/credis' => array( - 'pretty_version' => 'v1.13.0', - 'version' => '1.13.0.0', + 'pretty_version' => 'v1.16.0', + 'version' => '1.16.0.0', 'type' => 'library', 'install_path' => __DIR__ . '/../colinmollenhour/credis', 'aliases' => array(), - 'reference' => 'afec8e58ec93d2291c127fa19709a048f28641e5', + 'reference' => '5641140e14a9679f5a6f66c97268727f9558b881', + 'dev_requirement' => false, + ), + 'colinmollenhour/php-redis-session-abstract' => array( + 'pretty_version' => 'v1.4.5', + 'version' => '1.4.5.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../colinmollenhour/php-redis-session-abstract', + 'aliases' => array(), + 'reference' => '77ad0c1637ae6ea059f1f8e9fbdac6469242a16d', 'dev_requirement' => false, ), 'mustache/mustache' => array( diff --git a/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Config/Config.php b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Config/Config.php new file mode 100644 index 0000000..eb322f9 --- /dev/null +++ b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Config/Config.php @@ -0,0 +1,25 @@ + https://www.phpfastcache.com + * @author Georges.L (Geolim4) + * + */ +declare(strict_types=1); +namespace Phpfastcache\Config; + +/** + * Class Config + * Alias of ConfigurationOption + * @package phpFastCache\Config + * @see ConfigurationOption + */ +class Config extends ConfigurationOption +{ +} \ No newline at end of file diff --git a/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Apc/Config.php b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Apc/Config.php new file mode 100644 index 0000000..30b9e7c --- /dev/null +++ b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Apc/Config.php @@ -0,0 +1,24 @@ + https://www.phpfastcache.com + * @author Georges.L (Geolim4) + * + */ + +declare(strict_types=1); + +namespace Phpfastcache\Drivers\Apc; + +use Phpfastcache\Config\ConfigurationOption; + +class Config extends ConfigurationOption +{ + +} \ No newline at end of file diff --git a/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Apcu/Config.php b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Apcu/Config.php new file mode 100644 index 0000000..b333aea --- /dev/null +++ b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Apcu/Config.php @@ -0,0 +1,24 @@ + https://www.phpfastcache.com + * @author Georges.L (Geolim4) + * + */ + +declare(strict_types=1); + +namespace Phpfastcache\Drivers\Apcu; + +use Phpfastcache\Config\ConfigurationOption; + +class Config extends ConfigurationOption +{ + +} \ No newline at end of file diff --git a/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Cassandra/Config.php b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Cassandra/Config.php new file mode 100644 index 0000000..82253ca --- /dev/null +++ b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Cassandra/Config.php @@ -0,0 +1,177 @@ + https://www.phpfastcache.com + * @author Georges.L (Geolim4) + * + */ + +declare(strict_types=1); + +namespace Phpfastcache\Drivers\Cassandra; + +use Phpfastcache\Config\ConfigurationOption; + +class Config extends ConfigurationOption +{ + /** + * @var string + */ + protected $host = '127.0.0.1'; + /** + * @var int + */ + protected $port = 9042; + /** + * @var int + */ + protected $timeout = 2; + /** + * @var string + */ + protected $username = ''; + /** + * @var string + */ + protected $password = ''; + /** + * @var bool + */ + protected $sslEnabled = false; + /** + * @var bool + */ + protected $sslVerify = false; + + /** + * @return string + */ + public function getHost(): string + { + return $this->host; + } + + /** + * @param string $host + * @return self + */ + public function setHost(string $host): self + { + $this->host = $host; + return $this; + } + + /** + * @return int + */ + public function getPort(): int + { + return $this->port; + } + + /** + * @param int $port + * @return self + */ + public function setPort(int $port): self + { + $this->port = $port; + return $this; + } + + /** + * @return int + */ + public function getTimeout(): int + { + return $this->timeout; + } + + /** + * @param int $timeout + * @return self + */ + public function setTimeout(int $timeout): self + { + $this->timeout = $timeout; + return $this; + } + + /** + * @return string + */ + public function getUsername(): string + { + return $this->username; + } + + /** + * @param string $username + * @return self + */ + public function setUsername(string $username): self + { + $this->username = $username; + return $this; + } + + /** + * @return string + */ + public function getPassword(): string + { + return $this->password; + } + + /** + * @param string $password + * @return self + */ + public function setPassword(string $password): self + { + $this->password = $password; + return $this; + } + + /** + * @return bool + */ + public function isSslEnabled(): bool + { + return $this->sslEnabled; + } + + /** + * @param bool $sslEnabled + * @return self + */ + public function setSslEnabled(bool $sslEnabled): self + { + $this->sslEnabled = $sslEnabled; + return $this; + } + + /** + * @return bool + */ + public function isSslVerify(): bool + { + return $this->sslVerify; + } + + /** + * @param bool $sslVerify + * @return self + */ + public function setSslVerify(bool $sslVerify): self + { + $this->sslVerify = $sslVerify; + return $this; + } +} \ No newline at end of file diff --git a/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Cookie/Config.php b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Cookie/Config.php new file mode 100644 index 0000000..de4099c --- /dev/null +++ b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Cookie/Config.php @@ -0,0 +1,23 @@ + https://www.phpfastcache.com + * @author Georges.L (Geolim4) + * + */ +declare(strict_types=1); + +namespace Phpfastcache\Drivers\Cookie; + +use Phpfastcache\Config\ConfigurationOption; + +class Config extends ConfigurationOption +{ + +} \ No newline at end of file diff --git a/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Couchbase/Config.php b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Couchbase/Config.php new file mode 100644 index 0000000..d70cfd2 --- /dev/null +++ b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Couchbase/Config.php @@ -0,0 +1,137 @@ + https://www.phpfastcache.com + * @author Georges.L (Geolim4) + * + */ + +declare(strict_types=1); + +namespace Phpfastcache\Drivers\Couchbase; + +use Phpfastcache\Config\ConfigurationOption; + +class Config extends ConfigurationOption +{ + /** + * @var string + */ + protected $host = '127.0.0.1'; + + /** + * @var int + */ + protected $port = 8091;// SSL: 18091 + + /** + * @var string + */ + protected $username = ''; + + /** + * @var string + */ + protected $password = ''; + + /** + * @var string + */ + protected $bucketName = 'default'; + + /** + * @return string + */ + public function getHost(): string + { + return $this->host; + } + + /** + * @param string $host + * @return Config + */ + public function setHost(string $host): Config + { + $this->host = $host; + return $this; + } + + /** + * @return int|null + */ + public function getPort() + { + return $this->port; + } + + /** + * @param int $port + * @return Config + */ + public function setPort(int $port = null): Config + { + $this->port = $port; + return $this; + } + + /** + * @return string + */ + public function getUsername(): string + { + return $this->username; + } + + /** + * @param string $username + * @return Config + */ + public function setUsername(string $username): Config + { + $this->username = $username; + return $this; + } + + /** + * @return string + */ + public function getPassword(): string + { + return $this->password; + } + + /** + * @param string $password + * @return Config + */ + public function setPassword(string $password): Config + { + $this->password = $password; + return $this; + } + + /** + * @return string + */ + public function getBucketName(): string + { + return $this->bucketName; + } + + /** + * @param string $bucketName + * @return Config + */ + public function setBucketName(string $bucketName): Config + { + $this->bucketName = $bucketName; + return $this; + } +} \ No newline at end of file diff --git a/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Couchdb/Config.php b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Couchdb/Config.php new file mode 100644 index 0000000..07f2bfb --- /dev/null +++ b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Couchdb/Config.php @@ -0,0 +1,184 @@ + https://www.phpfastcache.com + * @author Georges.L (Geolim4) + * + */ + +declare(strict_types=1); + +namespace Phpfastcache\Drivers\Couchdb; + +use Phpfastcache\Config\ConfigurationOption; + +class Config extends ConfigurationOption +{ + /** + * @var string + */ + protected $host = '127.0.0.1'; + /** + * @var int + */ + protected $port = 5984; + + /** + * @var string + */ + protected $path = '/'; + + /** + * @var string + */ + protected $username = ''; + /** + * @var string + */ + protected $password = ''; + /** + * @var bool + */ + protected $ssl = false; + /** + * @var int + */ + protected $timeout = 10; + + /** + * @var string + */ + protected $database = Driver::COUCHDB_DEFAULT_DB_NAME; + + /** + * @return string + */ + public function getDatabase(): string + { + return $this->database; + } + + /** + * @param string $database + * @return Config + */ + public function setDatabase(string $database): Config + { + $this->database = $database; + return $this; + } + + /** + * @return string + */ + public function getHost(): string + { + return $this->host; + } + + /** + * @param string $host + * @return self + */ + public function setHost(string $host): self + { + $this->host = $host; + return $this; + } + + /** + * @return int + */ + public function getPort(): int + { + return $this->port; + } + + /** + * @param int $port + * @return self + */ + public function setPort(int $port): self + { + $this->port = $port; + return $this; + } + + /** + * @return string + */ + public function getUsername(): string + { + return $this->username; + } + + /** + * @param string $username + * @return self + */ + public function setUsername(string $username): self + { + $this->username = $username; + return $this; + } + + /** + * @return string + */ + public function getPassword(): string + { + return $this->password; + } + + /** + * @param string $password + * @return self + */ + public function setPassword(string $password): self + { + $this->password = $password; + return $this; + } + + /** + * @return bool + */ + public function isSsl(): bool + { + return $this->ssl; + } + + /** + * @param bool $ssl + * @return self + */ + public function setSsl(bool $ssl): self + { + $this->ssl = $ssl; + return $this; + } + + /** + * @return int + */ + public function getTimeout(): int + { + return $this->timeout; + } + + /** + * @param int $timeout + * @return self + */ + public function setTimeout(int $timeout): self + { + $this->timeout = $timeout; + return $this; + } +} \ No newline at end of file diff --git a/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Devfalse/Config.php b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Devfalse/Config.php new file mode 100644 index 0000000..05b0e13 --- /dev/null +++ b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Devfalse/Config.php @@ -0,0 +1,24 @@ + https://www.phpfastcache.com + * @author Georges.L (Geolim4) + * + */ + +declare(strict_types=1); + +namespace Phpfastcache\Drivers\Devfalse; + +use Phpfastcache\Config\ConfigurationOption; + +class Config extends ConfigurationOption +{ + +} \ No newline at end of file diff --git a/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Devnull/Config.php b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Devnull/Config.php new file mode 100644 index 0000000..be5fab9 --- /dev/null +++ b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Devnull/Config.php @@ -0,0 +1,24 @@ + https://www.phpfastcache.com + * @author Georges.L (Geolim4) + * + */ + +declare(strict_types=1); + +namespace Phpfastcache\Drivers\Devnull; + +use Phpfastcache\Config\ConfigurationOption; + +class Config extends ConfigurationOption +{ + +} \ No newline at end of file diff --git a/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Devtrue/Config.php b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Devtrue/Config.php new file mode 100644 index 0000000..fc59c1f --- /dev/null +++ b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Devtrue/Config.php @@ -0,0 +1,24 @@ + https://www.phpfastcache.com + * @author Georges.L (Geolim4) + * + */ + +declare(strict_types=1); + +namespace Phpfastcache\Drivers\Devtrue; + +use Phpfastcache\Config\ConfigurationOption; + +class Config extends ConfigurationOption +{ + +} \ No newline at end of file diff --git a/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Files/Config.php b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Files/Config.php new file mode 100644 index 0000000..52fee20 --- /dev/null +++ b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Files/Config.php @@ -0,0 +1,26 @@ + https://www.phpfastcache.com + * @author Georges.L (Geolim4) + * + */ + +declare(strict_types=1); + +namespace Phpfastcache\Drivers\Files; + +use Phpfastcache\Config\{ + ConfigurationOption, IOConfigurationOptionTrait +}; + +class Config extends ConfigurationOption +{ + use IOConfigurationOptionTrait; +} \ No newline at end of file diff --git a/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Leveldb/Config.php b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Leveldb/Config.php new file mode 100644 index 0000000..58d2074 --- /dev/null +++ b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Leveldb/Config.php @@ -0,0 +1,45 @@ + https://www.phpfastcache.com + * @author Georges.L (Geolim4) + * + */ + +declare(strict_types=1); + +namespace Phpfastcache\Drivers\Leveldb; + +use Phpfastcache\Config\ConfigurationOption; + +class Config extends ConfigurationOption +{ + /** + * @var string + */ + protected $htaccess = true; + + /** + * @return string + */ + public function getHtaccess(): string + { + return $this->htaccess; + } + + /** + * @param string $htaccess + * @return self + */ + public function setHtaccess(string $htaccess): self + { + $this->htaccess = $htaccess; + return $this; + } +} \ No newline at end of file diff --git a/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Memcache/Config.php b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Memcache/Config.php new file mode 100644 index 0000000..cfcad6a --- /dev/null +++ b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Memcache/Config.php @@ -0,0 +1,163 @@ + https://www.phpfastcache.com + * @author Georges.L (Geolim4) + * + */ + +declare(strict_types=1); + +namespace Phpfastcache\Drivers\Memcache; + +use Phpfastcache\Config\ConfigurationOption; +use Phpfastcache\Exceptions\PhpfastcacheInvalidConfigurationException; + +class Config extends ConfigurationOption +{ + /** + * @var array + * + * Multiple server can be added this way: + * $cfg->setServers([ + * [ + * 'host' => '127.0.0.1', + * 'port' => 11211, + * 'saslUser' => false, + * 'saslPassword' => false, + * ] + * ]); + */ + protected $servers = []; + + /** + * @var string + */ + protected $host = '127.0.0.1'; + + /** + * @var int + */ + protected $port = 11211; + + /** + * @var string + */ + protected $saslUser = ''; + + /** + * @var string + */ + protected $saslPassword = ''; + + /** + * @return bool + */ + public function getSaslUser(): string + { + return $this->saslUser; + } + + /** + * @param string $saslUser + * @return self + */ + public function setSaslUser(string $saslUser): self + { + $this->saslUser = $saslUser; + return $this; + } + + /** + * @return string + */ + public function getSaslPassword(): string + { + return $this->saslPassword; + } + + /** + * @param string $saslPassword + * @return self + */ + public function setSaslPassword(string $saslPassword): self + { + $this->saslPassword = $saslPassword; + return $this; + } + + /** + * @return array + */ + public function getServers(): array + { + return $this->servers; + } + + /** + * @param array $servers + * @throws PhpfastcacheInvalidConfigurationException + * @return self + */ + public function setServers(array $servers): self + { + foreach ($servers as $server) { + if($diff = \array_diff(['host', 'port', 'saslUser', 'saslPassword'], \array_keys($server))){ + throw new PhpfastcacheInvalidConfigurationException('Missing keys for memcached server: '. \implode(', ', $diff)); + } + if($diff = \array_diff( \array_keys($server), ['host', 'port', 'saslUser', 'saslPassword'])){ + throw new PhpfastcacheInvalidConfigurationException('Unknown keys for memcached server: '. \implode(', ', $diff)); + } + if(!\is_string($server['host'])){ + throw new PhpfastcacheInvalidConfigurationException('Host must be a valid string in "$server" configuration array'); + } + if(!\is_int($server['port'])){ + throw new PhpfastcacheInvalidConfigurationException('Port must be a valid integer in "$server" configuration array'); + } + } + $this->servers = $servers; + return $this; + } + + /** + * @return string + */ + public function getHost(): string + { + return $this->host; + } + + /** + * @param string $host + * @return self + */ + public function setHost(string $host): self + { + $this->host = $host; + return $this; + } + + /** + * @return int + */ + public function getPort(): int + { + return $this->port; + } + + /** + * @param int $port + * @return self + */ + public function setPort(int $port): self + { + $this->port = $port; + return $this; + } +} \ No newline at end of file diff --git a/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Memcached/Config.php b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Memcached/Config.php new file mode 100644 index 0000000..c1bee5f --- /dev/null +++ b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Memcached/Config.php @@ -0,0 +1,163 @@ + https://www.phpfastcache.com + * @author Georges.L (Geolim4) + * + */ + +declare(strict_types=1); + +namespace Phpfastcache\Drivers\Memcached; + +use Phpfastcache\Config\ConfigurationOption; +use Phpfastcache\Exceptions\PhpfastcacheInvalidConfigurationException; + +class Config extends ConfigurationOption +{ + /** + * @var array + * + * Multiple server can be added this way: + * $cfg->setServers([ + * [ + * 'host' => '127.0.0.1', + * 'port' => 11211, + * 'saslUser' => false, + * 'saslPassword' => false, + * ] + * ]); + */ + protected $servers = []; + + /** + * @var string + */ + protected $host = '127.0.0.1'; + + /** + * @var int + */ + protected $port = 11211; + + /** + * @var string + */ + protected $saslUser = ''; + + /** + * @var string + */ + protected $saslPassword = ''; + + /** + * @return string + */ + public function getSaslUser(): string + { + return $this->saslUser; + } + + /** + * @param string $saslUser + * @return self + */ + public function setSaslUser(string $saslUser): self + { + $this->saslUser = $saslUser; + return $this; + } + + /** + * @return string + */ + public function getSaslPassword(): string + { + return $this->saslPassword; + } + + /** + * @param string $saslPassword + * @return self + */ + public function setSaslPassword(string $saslPassword): self + { + $this->saslPassword = $saslPassword; + return $this; + } + + /** + * @return array + */ + public function getServers(): array + { + return $this->servers; + } + + /** + * @param array $servers + * @throws PhpfastcacheInvalidConfigurationException + * @return self + */ + public function setServers(array $servers): self + { + foreach ($servers as $server) { + if($diff = \array_diff(['host', 'port', 'saslUser', 'saslPassword'], \array_keys($server))){ + throw new PhpfastcacheInvalidConfigurationException('Missing keys for memcached server: '. \implode(', ', $diff)); + } + if($diff = \array_diff( \array_keys($server), ['host', 'port', 'saslUser', 'saslPassword'])){ + throw new PhpfastcacheInvalidConfigurationException('Unknown keys for memcached server: '. \implode(', ', $diff)); + } + if(!\is_string($server['host'])){ + throw new PhpfastcacheInvalidConfigurationException('Host must be a valid string in "$server" configuration array'); + } + if(!\is_int($server['port'])){ + throw new PhpfastcacheInvalidConfigurationException('Port must be a valid integer in "$server" configuration array'); + } + } + $this->servers = $servers; + return $this; + } + + /** + * @return string + */ + public function getHost(): string + { + return $this->host; + } + + /** + * @param string $host + * @return self + */ + public function setHost(string $host): self + { + $this->host = $host; + return $this; + } + + /** + * @return int + */ + public function getPort(): int + { + return $this->port; + } + + /** + * @param int $port + * @return Config + */ + public function setPort(int $port): self + { + $this->port = $port; + return $this; + } +} \ No newline at end of file diff --git a/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Memstatic/Config.php b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Memstatic/Config.php new file mode 100644 index 0000000..72baab4 --- /dev/null +++ b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Memstatic/Config.php @@ -0,0 +1,24 @@ + https://www.phpfastcache.com + * @author Georges.L (Geolim4) + * + */ + +declare(strict_types=1); + +namespace Phpfastcache\Drivers\Memstatic; + +use Phpfastcache\Config\ConfigurationOption; + +class Config extends ConfigurationOption +{ + +} \ No newline at end of file diff --git a/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Mongodb/Config.php b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Mongodb/Config.php new file mode 100644 index 0000000..11f86da --- /dev/null +++ b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Mongodb/Config.php @@ -0,0 +1,276 @@ + https://www.phpfastcache.com + * @author Georges.L (Geolim4) + * + */ + +declare(strict_types=1); + +namespace Phpfastcache\Drivers\Mongodb; + +use Phpfastcache\Config\ConfigurationOption; + +class Config extends ConfigurationOption +{ + /** + * @var string + */ + protected $host = '127.0.0.1'; + + /** + * @var int + */ + protected $port = 27017; + + /** + * @var int + */ + protected $timeout = 3; + + /** + * @var string + */ + protected $username = ''; + + /** + * @var string + */ + protected $password = ''; + + /** + * @var array + */ + protected $servers = []; + + /** + * @var string + */ + protected $collectionName = 'Cache'; + + /** + * @var string + */ + protected $databaseName = Driver::MONGODB_DEFAULT_DB_NAME; + + /** + * @var array + */ + protected $options = []; + + /** + * @var array + */ + protected $driverOptions = []; + + /** + * @var string + */ + protected $protocol = 'mongodb'; + + /** + * @return string + */ + public function getHost(): string + { + return $this->host; + } + + /** + * @param string $host + * @return self + */ + public function setHost(string $host): self + { + $this->host = $host; + return $this; + } + + /** + * @return int + */ + public function getPort(): int + { + return $this->port; + } + + /** + * @param int $port + * @return self + */ + public function setPort(int $port): self + { + $this->port = $port; + return $this; + } + + /** + * @return int + */ + public function getTimeout(): int + { + return $this->timeout; + } + + /** + * @param int $timeout + * @return self + */ + public function setTimeout(int $timeout): self + { + $this->timeout = $timeout; + return $this; + } + + /** + * @return string + */ + public function getUsername(): string + { + return $this->username; + } + + /** + * @param string $username + * @return self + */ + public function setUsername(string $username): self + { + $this->username = $username; + return $this; + } + + /** + * @return string + */ + public function getPassword(): string + { + return $this->password; + } + + /** + * @param string $password + * @return self + */ + public function setPassword(string $password): self + { + $this->password = $password; + return $this; + } + + /** + * @return array + */ + public function getServers(): array + { + return $this->servers; + } + + /** + * @param array $servers + * @return self + */ + public function setServers(array $servers): self + { + $this->servers = $servers; + return $this; + } + + /** + * @return string + */ + public function getCollectionName(): string + { + return $this->collectionName; + } + + /** + * @param string $collectionName + * @return self + */ + public function setCollectionName(string $collectionName): self + { + $this->collectionName = $collectionName; + return $this; + } + + /** + * @return string + */ + public function getDatabaseName(): string + { + return $this->databaseName; + } + + /** + * @param string $databaseName + * @return self + */ + public function setDatabaseName(string $databaseName): self + { + $this->databaseName = $databaseName; + return $this; + } + + /** + * @return array + */ + public function getOptions(): array + { + return $this->options; + } + + /** + * @see https://docs.mongodb.com/manual/reference/connection-string/#connections-connection-options + * @param array $options + * @return Config + */ + public function setOptions(array $options): self + { + $this->options = $options; + return $this; + } + + /** + * @return array + */ + public function getDriverOptions(): array + { + return $this->driverOptions; + } + + /** + * @param array $driverOptions + * @return self + */ + public function setDriverOptions(array $driverOptions): self + { + $this->driverOptions = $driverOptions; + return $this; + } + + /** + * @return string + */ + public function getProtocol(): string + { + return $this->protocol; + } + + /** + * @param string $protocol + * @return self + */ + public function setProtocol(string $protocol): self + { + $this->protocol = $protocol; + return $this; + } +} \ No newline at end of file diff --git a/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Predis/Config.php b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Predis/Config.php new file mode 100644 index 0000000..c595913 --- /dev/null +++ b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Predis/Config.php @@ -0,0 +1,250 @@ + https://www.phpfastcache.com + * @author Georges.L (Geolim4) + * + */ + +declare(strict_types=1); + +namespace Phpfastcache\Drivers\Predis; + +use Phpfastcache\Config\ConfigurationOption; +use Phpfastcache\Exceptions\PhpfastcacheInvalidConfigurationException; + +class Config extends ConfigurationOption +{ + /** + * @var string + */ + protected $host = '127.0.0.1'; + + /** + * @var int + */ + protected $port = 6379; + + /** + * @var string + */ + protected $password = ''; + + /** + * @var int + */ + protected $database = 0; + + /** + * @var \Predis\Client + */ + protected $predisClient; + + /** + * @var string + */ + protected $optPrefix = ''; + + /** + * @var int + */ + protected $timeout = 5; + + /** + * @var bool + */ + protected $persistent = false; + + /** + * @var string + */ + protected $scheme = 'unix'; + + /** + * @return string + */ + public function getHost(): string + { + return $this->host; + } + + /** + * @param string $host + * @return Config + */ + public function setHost(string $host): self + { + $this->host = $host; + return $this; + } + + /** + * @return int + */ + public function getPort(): int + { + return $this->port; + } + + /** + * @param int $port + * @return Config + */ + public function setPort(int $port): self + { + $this->port = $port; + return $this; + } + + /** + * @return null + */ + public function getPassword() + { + return $this->password; + } + + /** + * @param null $password + * @return self + */ + public function setPassword(string $password): self + { + $this->password = $password; + return $this; + } + + /** + * @return int + */ + public function getDatabase(): int + { + return $this->database; + } + + /** + * @param int $database + * @return Config + */ + public function setDatabase(int $database): self + { + $this->database = $database; + return $this; + } + + /** + * @return array + */ + public function getPredisConfigArray(): array + { + return [ + 'host' => $this->getHost(), + 'port' => $this->getPort(), + 'password' => $this->getPassword() ?: null, + 'database' => $this->getDatabase(), + 'timeout' => $this->getTimeout(), + ]; + } + + /** + * @return \Predis\Client|null + */ + public function getPredisClient() + { + return $this->predisClient; + } + + /** + * @param \Predis\Client $predisClient |null + * @return Config + */ + public function setPredisClient(\Predis\Client $predisClient = null): Config + { + $this->predisClient = $predisClient; + return $this; + } + + /** + * @return string + * @since 7.0.2 + */ + public function getOptPrefix(): string + { + return $this->optPrefix; + } + + /** + * @param string $optPrefix + * @return Config + * @since 7.0.2 + */ + public function setOptPrefix(string $optPrefix): Config + { + $this->optPrefix = trim($optPrefix); + return $this; + } + + /** + * @return int + */ + public function getTimeout(): int + { + return $this->timeout; + } + + /** + * @param int $timeout + * @return self + */ + public function setTimeout(int $timeout): self + { + $this->timeout = $timeout; + return $this; + } + + /** + * @return bool + */ + public function isPersistent(): bool + { + return $this->persistent; + } + + /** + * @param bool $persistent + * @return Config + */ + public function setPersistent(bool $persistent): Config + { + $this->persistent = $persistent; + return $this; + } + + /** + * @return string + */ + public function getScheme(): string + { + return $this->scheme; + } + + /** + * @param string $scheme + * @return Config + * @throws PhpfastcacheInvalidConfigurationException + */ + public function setScheme(string $scheme): Config + { + if(!\in_array($scheme, ['unix', 'tls'], true)){ + throw new PhpfastcacheInvalidConfigurationException('Invalid scheme: ' . $scheme); + } + $this->scheme = $scheme; + return $this; + } +} \ No newline at end of file diff --git a/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Redis/Config.php b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Redis/Config.php new file mode 100644 index 0000000..2c94ceb --- /dev/null +++ b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Redis/Config.php @@ -0,0 +1,186 @@ + https://www.phpfastcache.com + * @author Georges.L (Geolim4) + * + */ + +declare(strict_types=1); + +namespace Phpfastcache\Drivers\Redis; + +use Phpfastcache\Config\ConfigurationOption; +use Redis as RedisClient; + +class Config extends ConfigurationOption +{ + /** + * @var string + */ + protected $host = '127.0.0.1'; + + /** + * @var int + */ + protected $port = 6379; + + /** + * @var string + */ + protected $password = ''; + + /** + * @var int + */ + protected $database = 0; + + /** + * @var int + */ + protected $timeout = 5; + + /** + * @var RedisClient + */ + protected $redisClient; + + /** + * @var string + */ + protected $optPrefix = ''; + + /** + * @return string + */ + public function getHost(): string + { + return $this->host; + } + + /** + * @param string $host + * @return self + */ + public function setHost(string $host): self + { + $this->host = $host; + return $this; + } + + /** + * @return int + */ + public function getPort(): int + { + return $this->port; + } + + /** + * @param int $port + * @return self + */ + public function setPort(int $port): self + { + $this->port = $port; + return $this; + } + + /** + * @return mixed + */ + public function getPassword() + { + return $this->password; + } + + /** + * @param string $password + * @return self + */ + public function setPassword(string $password): self + { + $this->password = $password; + return $this; + } + + /** + * @return int + */ + public function getDatabase(): int + { + return $this->database; + } + + /** + * @param int $database + * @return self + */ + public function setDatabase(int $database): self + { + $this->database = $database; + return $this; + } + + /** + * @return int + */ + public function getTimeout(): int + { + return $this->timeout; + } + + /** + * @param int $timeout + * @return self + */ + public function setTimeout(int $timeout): self + { + $this->timeout = $timeout; + return $this; + } + + /** + * @return RedisClient|null + */ + public function getRedisClient() + { + return $this->redisClient; + } + + /** + * @param RedisClient $predisClient |null + * @return Config + */ + public function setRedisClient(RedisClient $redisClient = null): Config + { + $this->redisClient = $redisClient; + return $this; + } + + /** + * @return string + * @since 7.0.2 + */ + public function getOptPrefix(): string + { + return $this->optPrefix; + } + + /** + * @param string $optPrefix + * @return Config + * @since 7.0.2 + */ + public function setOptPrefix(string $optPrefix): Config + { + $this->optPrefix = trim($optPrefix); + return $this; + } +} \ No newline at end of file diff --git a/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Riak/Config.php b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Riak/Config.php new file mode 100644 index 0000000..8d4a037 --- /dev/null +++ b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Riak/Config.php @@ -0,0 +1,114 @@ + https://www.phpfastcache.com + * @author Georges.L (Geolim4) + * + */ + +declare(strict_types=1); + +namespace Phpfastcache\Drivers\Riak; + +use Phpfastcache\Config\ConfigurationOption; + +class Config extends ConfigurationOption +{ + /** + * @var string + */ + protected $host = '127.0.0.1'; + + /** + * @var int + */ + protected $port = 8098; + + /** + * @var string + */ + protected $prefix = 'riak'; + + /** + * @var string + */ + protected $bucketName = Driver::RIAK_DEFAULT_BUCKET_NAME; + + /** + * @return string + */ + public function getHost(): string + { + return $this->host; + } + + /** + * @param string $host + * @return self + */ + public function setHost(string $host): self + { + $this->host = $host; + return $this; + } + + /** + * @return int + */ + public function getPort(): int + { + return $this->port; + } + + /** + * @param int $port + * @return self + */ + public function setPort(int $port): self + { + $this->port = $port; + return $this; + } + + /** + * @return string + */ + public function getPrefix(): string + { + return $this->prefix; + } + + /** + * @param string $prefix + * @return self + */ + public function setPrefix(string $prefix): self + { + $this->prefix = $prefix; + return $this; + } + + /** + * @return string + */ + public function getBucketName(): string + { + return $this->bucketName; + } + + /** + * @param string $bucketName + * @return self + */ + public function setBucketName(string $bucketName): self + { + $this->bucketName = $bucketName; + return $this; + } +} \ No newline at end of file diff --git a/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Sqlite/Config.php b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Sqlite/Config.php new file mode 100644 index 0000000..ce1dc8e --- /dev/null +++ b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Sqlite/Config.php @@ -0,0 +1,26 @@ + https://www.phpfastcache.com + * @author Georges.L (Geolim4) + * + */ + +declare(strict_types=1); + +namespace Phpfastcache\Drivers\Sqlite; + +use Phpfastcache\Config\{ + ConfigurationOption, IOConfigurationOptionTrait +}; + +class Config extends ConfigurationOption +{ + use IOConfigurationOptionTrait; +} \ No newline at end of file diff --git a/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Ssdb/Config.php b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Ssdb/Config.php new file mode 100644 index 0000000..f572eed --- /dev/null +++ b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Ssdb/Config.php @@ -0,0 +1,114 @@ + https://www.phpfastcache.com + * @author Georges.L (Geolim4) + * + */ + +declare(strict_types=1); + +namespace Phpfastcache\Drivers\Ssdb; + +use Phpfastcache\Config\ConfigurationOption; + +class Config extends ConfigurationOption +{ + /** + * @var string + */ + protected $host = '127.0.0.1'; + + /** + * @var int + */ + protected $port = 8888; + + /** + * @var string + */ + protected $password = ''; + + /** + * @var int + */ + protected $timeout = 2000; + + /** + * @return string + */ + public function getHost(): string + { + return $this->host; + } + + /** + * @param string $host + * @return Config + */ + public function setHost(string $host): Config + { + $this->host = $host; + return $this; + } + + /** + * @return int + */ + public function getPort(): int + { + return $this->port; + } + + /** + * @param int $port + * @return Config + */ + public function setPort(int $port): Config + { + $this->port = $port; + return $this; + } + + /** + * @return string + */ + public function getPassword(): string + { + return $this->password; + } + + /** + * @param string $password + * @return Config + */ + public function setPassword(string $password): Config + { + $this->password = $password; + return $this; + } + + /** + * @return int + */ + public function getTimeout(): int + { + return $this->timeout; + } + + /** + * @param int $timeout + * @return Config + */ + public function setTimeout(int $timeout): Config + { + $this->timeout = $timeout; + return $this; + } +} \ No newline at end of file diff --git a/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Wincache/Config.php b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Wincache/Config.php new file mode 100644 index 0000000..4d89fea --- /dev/null +++ b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Wincache/Config.php @@ -0,0 +1,24 @@ + https://www.phpfastcache.com + * @author Georges.L (Geolim4) + * + */ + +declare(strict_types=1); + +namespace Phpfastcache\Drivers\Wincache; + +use Phpfastcache\Config\ConfigurationOption; + +class Config extends ConfigurationOption +{ + +} \ No newline at end of file diff --git a/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Xcache/Config.php b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Xcache/Config.php new file mode 100644 index 0000000..1185f37 --- /dev/null +++ b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Xcache/Config.php @@ -0,0 +1,24 @@ + https://www.phpfastcache.com + * @author Georges.L (Geolim4) + * + */ + +declare(strict_types=1); + +namespace Phpfastcache\Drivers\Xcache; + +use Phpfastcache\Config\ConfigurationOption; + +class Config extends ConfigurationOption +{ + +} \ No newline at end of file diff --git a/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Zenddisk/Config.php b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Zenddisk/Config.php new file mode 100644 index 0000000..36c9057 --- /dev/null +++ b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Zenddisk/Config.php @@ -0,0 +1,24 @@ + https://www.phpfastcache.com + * @author Georges.L (Geolim4) + * + */ + +declare(strict_types=1); + +namespace Phpfastcache\Drivers\Zenddisk; + +use Phpfastcache\Config\ConfigurationOption; + +class Config extends ConfigurationOption +{ + +} \ No newline at end of file diff --git a/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Zendshm/Config.php b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Zendshm/Config.php new file mode 100644 index 0000000..ff43929 --- /dev/null +++ b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Zendshm/Config.php @@ -0,0 +1,24 @@ + https://www.phpfastcache.com + * @author Georges.L (Geolim4) + * + */ + +declare(strict_types=1); + +namespace Phpfastcache\Drivers\Zendshm; + +use Phpfastcache\Config\ConfigurationOption; + +class Config extends ConfigurationOption +{ + +} \ No newline at end of file