Allow plugins to leverage the Redis connection of the RedisCacheSource
authorTim Düsterhus <duesterhus@woltlab.com>
Wed, 28 Sep 2016 15:14:38 +0000 (17:14 +0200)
committerTim Düsterhus <duesterhus@woltlab.com>
Wed, 28 Sep 2016 15:18:59 +0000 (17:18 +0200)
This removes the need to manually parse the Redis DSN specified by
the administrator and saves socket connections by multiplexing the
commands over a single connection, without being able to break the
connection used by cache.

wcfsetup/install/files/lib/system/cache/source/RedisCacheSource.class.php
wcfsetup/install/files/lib/system/database/Redis.class.php [new file with mode: 0644]

index 960c0b217f2e2efa1109181035e47aa71ece4746..92cdb763bd67777c135929aeb9bf7039fb5a7b91 100644 (file)
@@ -1,8 +1,7 @@
 <?php
 namespace wcf\system\cache\source;
+use wcf\system\database\Redis;
 use wcf\system\exception\SystemException;
-use wcf\system\Regex;
-use wcf\util\StringUtil;
 
 /**
  * RedisCacheSource is an implementation of CacheSource that uses a Redis server to store cached variables.
@@ -16,7 +15,7 @@ use wcf\util\StringUtil;
 class RedisCacheSource implements ICacheSource {
        /**
         * Redis object
-        * @var \Redis
+        * @var Redis
         */
        protected $redis = null;
        
@@ -24,40 +23,12 @@ class RedisCacheSource implements ICacheSource {
         * Creates a new instance of Redis.
         */
        public function __construct() {
-               if (!class_exists('Redis')) {
-                       throw new SystemException('Redis support is not enabled.');
+               try {
+                       $this->redis = new Redis(CACHE_SOURCE_REDIS_HOST);
                }
-               
-               $this->redis = new \Redis();
-               
-               $regex = new Regex('^\[([a-z0-9\:\.]+)\](?::([0-9]{1,5}))?$', Regex::CASE_INSENSITIVE);
-               $host = StringUtil::trim(CACHE_SOURCE_REDIS_HOST);
-               $port = 6379; // default Redis port
-               
-               // check for IPv6
-               if ($regex->match($host)) {
-                       $matches = $regex->getMatches();
-                       $host = $matches[1];
-                       
-                       if (isset($matches[2])) {
-                               $port = $matches[2];
-                       }
-               }
-               else {
-                       // IPv4 or host, try to get port
-                       if (strpos($host, ':')) {
-                               $parsedHost = explode(':', $host);
-                               $host = $parsedHost[0];
-                               $port = $parsedHost[1];
-                       }
+               catch (\Exception $e) {
+                       throw new SystemException('Unable to create a Redis instance', 0, '', $e);
                }
-               
-               if (!$this->redis->connect($host, $port)) {
-                       throw new SystemException('Unable to connect to Redis server');
-               }
-               
-               // automatically prefix key names with the WCF UUID
-               $this->redis->setOption(\Redis::OPT_PREFIX, WCF_UUID.':');
        }
        
        /**
@@ -193,4 +164,13 @@ class RedisCacheSource implements ICacheSource {
                
                return $info['redis_version'];
        }
+
+       /**
+        * Returns the underlying Redis instance.
+        *
+        * @return      Redis
+        */
+       public function getRedis() {
+               return $this->redis;
+       }
 }
diff --git a/wcfsetup/install/files/lib/system/database/Redis.class.php b/wcfsetup/install/files/lib/system/database/Redis.class.php
new file mode 100644 (file)
index 0000000..73fd885
--- /dev/null
@@ -0,0 +1,101 @@
+<?php
+namespace wcf\system\database;
+use wcf\system\Regex;
+use wcf\util\StringUtil;
+
+/**
+ * Wrapper around the \Redis class of php redis.
+ * 
+ * @author     Tim Duesterhus
+ * @copyright  2001-2016 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Database
+ */
+class Redis {
+       /**
+        * wrapped redis connection
+        * @var \Redis
+        */
+       protected $redis = null;
+       
+       /**
+        * DSN string used to connect.
+        * @var string
+        */
+       protected $dsn = '';
+       
+       /**
+        * Connects to the redis server given by the DSN.
+        */
+       public function __construct($dsn) {
+               if (!class_exists('Redis')) {
+                       throw new \BadMethodCallException('Redis support is not enabled.');
+               }
+               
+               $this->dsn = $dsn;
+               
+               $this->redis = new \Redis();
+               
+               $regex = new Regex('^\[([a-z0-9\:\.]+)\](?::([0-9]{1,5}))?$', Regex::CASE_INSENSITIVE);
+               $host = StringUtil::trim($this->dsn);
+               $port = 6379; // default Redis port
+               
+               // check for IPv6
+               if ($regex->match($host)) {
+                       $matches = $regex->getMatches();
+                       $host = $matches[1];
+                       
+                       if (isset($matches[2])) {
+                               $port = $matches[2];
+                       }
+               }
+               else {
+                       // IPv4 or host, try to get port
+                       if (strpos($host, ':')) {
+                               $parsedHost = explode(':', $host);
+                               $host = $parsedHost[0];
+                               $port = $parsedHost[1];
+                       }
+               }
+               
+               if (!$this->redis->connect($host, $port)) {
+                       throw new \RuntimeException('Unable to connect to Redis server');
+               }
+               
+               // automatically prefix key names with the WCF UUID
+               $this->redis->setOption(\Redis::OPT_PREFIX, WCF_UUID.':');
+       }
+       
+       /**
+        * Passes all method calls down to the underlying Redis connection.
+        */
+       public function __call($name, array $arguments) {
+               switch ($name) {
+                       case 'setOption':
+                       case 'getOption':
+                       
+                       case 'open':
+                       case 'connect':
+                       
+                       case 'popen':
+                       case 'pconnect':
+                       
+                       case 'auth':
+                       
+                       case 'select':
+                       
+                       case 'close':
+                               throw new \BadMethodCallException('You must not use '.$name);
+               }
+               return call_user_func_array([$this->redis, $name], $arguments);
+       }
+       
+       /**
+        * Returns a new, raw, redis instance to the same server.
+        *
+        * @return      \Redis
+        */
+       public function unwrap() {
+               return (new self($this->dsn))->redis;
+       }
+}