Browse Source

Add support for the 'redis://' scheme in URI strings.

The URI string will be handled following the rules as described by the
the provisional IANA registration document that can be found on IANA's
website: http://www.iana.org/assignments/uri-schemes/prov/redis.
Daniele Alessandri 9 years ago
parent
commit
baaf26fe5b

+ 3 - 0
README.md

@@ -95,6 +95,9 @@ $client = new Predis\Client([
 $client = new Predis\Client('tcp://10.0.0.1:6379');
 ```
 
+Starting with Predis v1.0.2 the client also understands the `redis` scheme in URI strings as defined
+by the [provisional IANA registration](http://www.iana.org/assignments/uri-schemes/prov/redis).
+
 When an array of connection parameters is provided, Predis automatically works in cluster mode using
 client-side sharding. Both named arrays and URI strings can be mixed when providing configurations
 for each node:

+ 8 - 8
src/Connection/AbstractConnection.php

@@ -58,14 +58,14 @@ abstract class AbstractConnection implements NodeConnectionInterface
      */
     protected function assertParameters(ParametersInterface $parameters)
     {
-        $scheme = $parameters->scheme;
-
-        if ($scheme !== 'tcp' && $scheme !== 'unix') {
-            throw new InvalidArgumentException("Invalid scheme: '$scheme'.");
-        }
-
-        if ($scheme === 'unix' && !isset($parameters->path)) {
-            throw new InvalidArgumentException('Missing UNIX domain socket path.');
+        switch ($parameters->scheme) {
+            case 'tcp':
+            case 'redis':
+            case 'unix':
+                break;
+
+            default:
+                throw new \InvalidArgumentException("Invalid scheme: '$parameters->scheme'.");
         }
 
         return $parameters;

+ 4 - 3
src/Connection/Factory.php

@@ -24,9 +24,10 @@ use Predis\Command\RawCommand;
 class Factory implements FactoryInterface
 {
     protected $schemes = array(
-        'tcp'  => 'Predis\Connection\StreamConnection',
-        'unix' => 'Predis\Connection\StreamConnection',
-        'http' => 'Predis\Connection\WebdisConnection',
+        'tcp'   => 'Predis\Connection\StreamConnection',
+        'unix'  => 'Predis\Connection\StreamConnection',
+        'redis' => 'Predis\Connection\StreamConnection',
+        'http'  => 'Predis\Connection\WebdisConnection',
     );
 
     /**

+ 48 - 1
src/Connection/Parameters.php

@@ -77,12 +77,49 @@ class Parameters implements ParametersInterface
      */
     public static function parse($uri)
     {
+        if (stripos($uri, 'redis') === 0) {
+            return static::parseIANA($uri);
+        }
+
         if (stripos($uri, 'unix') === 0) {
             // Hack to support URIs for UNIX sockets with minimal effort.
             $uri = str_ireplace('unix:///', 'unix://localhost/', $uri);
         }
 
-        if (!($parsed = parse_url($uri)) || !isset($parsed['host'])) {
+        if (!$parsed = parse_url($uri)) {
+            throw new InvalidArgumentException("Invalid parameters URI: $uri");
+        }
+
+        if (isset($parsed['query'])) {
+            parse_str($parsed['query'], $queryarray);
+            unset($parsed['query']);
+
+            $parsed = array_merge($parsed, $queryarray);
+        }
+
+        return $parsed;
+    }
+
+    /**
+     * Parses an URI string as defined by the "redis" and "rediss" URL schemes
+     * registered with the IANA.
+     *
+     * When the URI has a password in the "user-information" part or a database
+     * number the "path" part, their values override the values of "password"
+     * and "database" in the "query" part of the same URI.
+     *
+     * @link http://www.iana.org/assignments/uri-schemes/prov/redis
+     * @link http://www.iana.org/assignments/uri-schemes/prov/redis
+     *
+     * @param string $uri URI string.
+     *
+     * @return array
+     *
+     * @throws \InvalidArgumentException
+     */
+    public static function parseIANA($uri)
+    {
+        if (!$parsed = parse_url($uri)) {
             throw new InvalidArgumentException("Invalid parameters URI: $uri");
         }
 
@@ -93,6 +130,16 @@ class Parameters implements ParametersInterface
             $parsed = array_merge($parsed, $queryarray);
         }
 
+        if (isset($parsed['pass'])) {
+            $parsed['password'] = $parsed['pass'];
+            unset($parsed['pass']);
+        }
+
+        if (isset($parsed['path']) && preg_match('/^\/(\d+)\/?/', $parsed['path'], $path)) {
+            $parsed['database'] = $path[1];
+            unset($parsed['path']);
+        }
+
         return $parsed;
     }
 

+ 4 - 2
src/Connection/PhpiredisSocketConnection.php

@@ -32,7 +32,7 @@ use Predis\Response\Status as StatusResponse;
  *
  * The connection parameters supported by this class are:
  *
- *  - scheme: it can be either 'tcp' or 'unix'.
+ *  - scheme: it can be either 'redis', 'tcp' or 'unix'.
  *  - host: hostname or IP address of the server.
  *  - port: TCP port of the server.
  *  - path: path of a UNIX domain socket when scheme is 'unix'.
@@ -92,13 +92,15 @@ class PhpiredisSocketConnection extends AbstractConnection
      */
     protected function assertParameters(ParametersInterface $parameters)
     {
+        parent::assertParameters($parameters);
+
         if (isset($parameters->persistent)) {
             throw new NotSupportedException(
                 "Persistent connections are not supported by this connection backend."
             );
         }
 
-        return parent::assertParameters($parameters);
+        return $parameters;
     }
 
     /**

+ 1 - 1
src/Connection/PhpiredisStreamConnection.php

@@ -32,7 +32,7 @@ use Predis\Response\Status as StatusResponse;
  *
  * The connection parameters supported by this class are:
  *
- *  - scheme: it can be either 'tcp' or 'unix'.
+ *  - scheme: it can be either 'redis', 'tcp' or 'unix'.
  *  - host: hostname or IP address of the server.
  *  - port: TCP port of the server.
  *  - path: path of a UNIX domain socket when scheme is 'unix'.

+ 15 - 4
src/Connection/StreamConnection.php

@@ -19,7 +19,7 @@ use Predis\Response\Status as StatusResponse;
  * Standard connection to Redis servers implemented on top of PHP's streams.
  * The connection parameters supported by this class are:
  *
- *  - scheme: it can be either 'tcp' or 'unix'.
+ *  - scheme: it can be either 'redis', 'tcp' or 'unix'.
  *  - host: hostname or IP address of the server.
  *  - port: TCP port of the server.
  *  - path: path of a UNIX domain socket when scheme is 'unix'.
@@ -52,10 +52,17 @@ class StreamConnection extends AbstractConnection
      */
     protected function createResource()
     {
-        $initializer = "{$this->parameters->scheme}StreamInitializer";
-        $resource = $this->$initializer($this->parameters);
+        switch ($this->parameters->scheme) {
+            case 'tcp':
+            case 'redis':
+                return $this->tcpStreamInitializer($this->parameters);
 
-        return $resource;
+            case 'unix':
+                return $this->unixStreamInitializer($this->parameters);
+
+            default:
+                throw new \InvalidArgumentException("Invalid scheme: '{$this->parameters->scheme}'.");
+        }
     }
 
     /**
@@ -110,6 +117,10 @@ class StreamConnection extends AbstractConnection
      */
     protected function unixStreamInitializer(ParametersInterface $parameters)
     {
+        if ($scheme === 'unix' && !isset($parameters->path)) {
+            throw new InvalidArgumentException('Missing UNIX domain socket path.');
+        }
+
         $uri = "unix://{$parameters->path}";
         $flags = STREAM_CLIENT_CONNECT;
 

+ 37 - 4
tests/Predis/Connection/CompositeStreamConnectionTest.php

@@ -29,12 +29,34 @@ class CompositeStreamConnectionTest extends PredisConnectionTestCase
     /**
      * @group disconnected
      */
-    public function testExposesParameters()
+    public function testSupportsSchemeTCP()
     {
-        $parameters = $this->getParameters();
-        $connection = new CompositeStreamConnection($parameters);
+        $parameters = $this->getParameters(array('scheme' => 'tcp'));
+        $connection = new StreamConnection($parameters);
 
-        $this->assertSame($parameters, $connection->getParameters());
+        $this->assertInstanceOf('Predis\Connection\NodeConnectionInterface', $connection);
+    }
+
+    /**
+     * @group disconnected
+     */
+    public function testSupportsSchemeRedis()
+    {
+        $parameters = $this->getParameters(array('scheme' => 'redis'));
+        $connection = new StreamConnection($parameters);
+
+        $this->assertInstanceOf('Predis\Connection\NodeConnectionInterface', $connection);
+    }
+
+    /**
+     * @group disconnected
+     */
+    public function testSupportsSchemeUnix()
+    {
+        $parameters = $this->getParameters(array('scheme' => 'unix'));
+        $connection = new StreamConnection($parameters);
+
+        $this->assertInstanceOf('Predis\Connection\NodeConnectionInterface', $connection);
     }
 
     /**
@@ -48,6 +70,17 @@ class CompositeStreamConnectionTest extends PredisConnectionTestCase
         new CompositeStreamConnection($parameters);
     }
 
+    /**
+     * @group disconnected
+     */
+    public function testExposesParameters()
+    {
+        $parameters = $this->getParameters();
+        $connection = new CompositeStreamConnection($parameters);
+
+        $this->assertSame($parameters, $connection->getParameters());
+    }
+
     /**
      * @group disconnected
      */

+ 17 - 5
tests/Predis/Connection/FactoryTest.php

@@ -47,6 +47,18 @@ class FactoryTest extends PredisTestCase
         $this->assertEquals($tcp->host, $parameters->host);
         $this->assertEquals($tcp->database, $parameters->database);
 
+        $tcp = new Parameters(array(
+            'scheme' => 'redis',
+            'host' => 'locahost',
+        ));
+
+        $connection = $factory->create($tcp);
+        $parameters = $connection->getParameters();
+        $this->assertInstanceOf('Predis\Connection\StreamConnection', $connection);
+        $this->assertEquals($tcp->scheme, $parameters->scheme);
+        $this->assertEquals($tcp->host, $parameters->host);
+        $this->assertEquals($tcp->database, $parameters->database);
+
         $unix = new Parameters(array(
             'scheme' => 'unix',
             'path' => '/tmp/redis.sock',
@@ -246,7 +258,7 @@ class FactoryTest extends PredisTestCase
     /**
      * @group disconnected
      * @expectedException \InvalidArgumentException
-     * @expecteExceptionMessage Unknown connection scheme: 'redis'.
+     * @expecteExceptionMessage Unknown connection scheme: 'test'.
      */
     public function testDefineAndUndefineConnection()
     {
@@ -254,11 +266,11 @@ class FactoryTest extends PredisTestCase
 
         $factory = new Factory();
 
-        $factory->define('redis', $connectionClass);
-        $this->assertInstanceOf($connectionClass, $factory->create('redis://127.0.0.1'));
+        $factory->define('test', $connectionClass);
+        $this->assertInstanceOf($connectionClass, $factory->create('test://127.0.0.1'));
 
-        $factory->undefine('redis');
-        $factory->create('redis://127.0.0.1');
+        $factory->undefine('test');
+        $factory->create('test://127.0.0.1');
     }
 
     /**

+ 42 - 2
tests/Predis/Connection/ParametersTest.php

@@ -137,7 +137,7 @@ class ParametersTest extends PredisTestCase
      */
     public function testParsingURI()
     {
-        $uri = 'tcp://10.10.10.10:6400?timeout=0.5&persistent=1';
+        $uri = 'tcp://10.10.10.10:6400?timeout=0.5&persistent=1&database=5&password=secret';
 
         $expected = array(
             'scheme' => 'tcp',
@@ -145,6 +145,8 @@ class ParametersTest extends PredisTestCase
             'port' => 6400,
             'timeout' => '0.5',
             'persistent' => '1',
+            'database' => '5',
+            'password' => 'secret',
         );
 
         $this->assertSame($expected, Parameters::parse($uri));
@@ -153,7 +155,45 @@ class ParametersTest extends PredisTestCase
     /**
      * @group disconnected
      */
-    public function testParsingUnixDomainURI()
+    public function testParsingURIWithRedisScheme()
+    {
+        $uri = 'redis://:secret@10.10.10.10:6400/5?timeout=0.5&persistent=1';
+
+        $expected = array(
+            'scheme' => 'redis',
+            'host' => '10.10.10.10',
+            'port' => 6400,
+            'timeout' => '0.5',
+            'persistent' => '1',
+            'password' => 'secret',
+            'database' => '5',
+        );
+
+        $parameters = Parameters::parse($uri);
+
+        // TODO: parse_url() in PHP >= 5.6 returns an empty "user" entry in the
+        // dictionary when no username has been provided in the URI string. This
+        // actually makes sense, but let's keep the test ugly & simple for now.
+        unset($parameters['user']);
+
+        $this->assertSame($expected, $parameters);
+    }
+
+    /**
+     * @group disconnected
+     */
+    public function testRedisSchemeOverridesPasswordAndDatabaseInQueryString()
+    {
+        $parameters = Parameters::parse('redis://:secret@10.10.10.10/5?password=ignored&database=4');
+
+        $this->assertSame('secret', $parameters['password']);
+        $this->assertSame('5', $parameters['database']);
+    }
+
+    /**
+     * @group disconnected
+     */
+    public function testParsingURIWithUnixDomain()
     {
         $uri = 'unix:///tmp/redis.sock?timeout=0.5&persistent=1';
 

+ 36 - 3
tests/Predis/Connection/PhpiredisSocketConnectionTest.php

@@ -29,12 +29,34 @@ class PhpiredisSocketConnectionTest extends PredisConnectionTestCase
     /**
      * @group disconnected
      */
-    public function testExposesParameters()
+    public function testSupportsSchemeTCP()
     {
-        $parameters = $this->getParameters();
+        $parameters = $this->getParameters(array('scheme' => 'tcp'));
         $connection = new PhpiredisSocketConnection($parameters);
 
-        $this->assertSame($parameters, $connection->getParameters());
+        $this->assertInstanceOf('Predis\Connection\NodeConnectionInterface', $connection);
+    }
+
+    /**
+     * @group disconnected
+     */
+    public function testSupportsSchemeRedis()
+    {
+        $parameters = $this->getParameters(array('scheme' => 'redis'));
+        $connection = new PhpiredisSocketConnection($parameters);
+
+        $this->assertInstanceOf('Predis\Connection\NodeConnectionInterface', $connection);
+    }
+
+    /**
+     * @group disconnected
+     */
+    public function testSupportsSchemeUnix()
+    {
+        $parameters = $this->getParameters(array('scheme' => 'unix'));
+        $connection = new PhpiredisSocketConnection($parameters);
+
+        $this->assertInstanceOf('Predis\Connection\NodeConnectionInterface', $connection);
     }
 
     /**
@@ -48,6 +70,17 @@ class PhpiredisSocketConnectionTest extends PredisConnectionTestCase
         new PhpiredisSocketConnection($parameters);
     }
 
+    /**
+     * @group disconnected
+     */
+    public function testExposesParameters()
+    {
+        $parameters = $this->getParameters();
+        $connection = new PhpiredisSocketConnection($parameters);
+
+        $this->assertSame($parameters, $connection->getParameters());
+    }
+
     /**
      * @group disconnected
      */

+ 36 - 3
tests/Predis/Connection/PhpiredisStreamConnectionTest.php

@@ -29,12 +29,34 @@ class PhpiredisStreamConnectionTest extends PredisConnectionTestCase
     /**
      * @group disconnected
      */
-    public function testExposesParameters()
+    public function testSupportsSchemeTCP()
     {
-        $parameters = $this->getParameters();
+        $parameters = $this->getParameters(array('scheme' => 'tcp'));
         $connection = new PhpiredisStreamConnection($parameters);
 
-        $this->assertSame($parameters, $connection->getParameters());
+        $this->assertInstanceOf('Predis\Connection\NodeConnectionInterface', $connection);
+    }
+
+    /**
+     * @group disconnected
+     */
+    public function testSupportsSchemeRedis()
+    {
+        $parameters = $this->getParameters(array('scheme' => 'redis'));
+        $connection = new PhpiredisStreamConnection($parameters);
+
+        $this->assertInstanceOf('Predis\Connection\NodeConnectionInterface', $connection);
+    }
+
+    /**
+     * @group disconnected
+     */
+    public function testSupportsSchemeUnix()
+    {
+        $parameters = $this->getParameters(array('scheme' => 'unix'));
+        $connection = new PhpiredisStreamConnection($parameters);
+
+        $this->assertInstanceOf('Predis\Connection\NodeConnectionInterface', $connection);
     }
 
     /**
@@ -48,6 +70,17 @@ class PhpiredisStreamConnectionTest extends PredisConnectionTestCase
         new PhpiredisStreamConnection($parameters);
     }
 
+    /**
+     * @group disconnected
+     */
+    public function testExposesParameters()
+    {
+        $parameters = $this->getParameters();
+        $connection = new PhpiredisStreamConnection($parameters);
+
+        $this->assertSame($parameters, $connection->getParameters());
+    }
+
     /**
      * @group disconnected
      */

+ 36 - 3
tests/Predis/Connection/StreamConnectionTest.php

@@ -29,12 +29,34 @@ class StreamConnectionTest extends PredisConnectionTestCase
     /**
      * @group disconnected
      */
-    public function testExposesParameters()
+    public function testSupportsSchemeTCP()
     {
-        $parameters = $this->getParameters();
+        $parameters = $this->getParameters(array('scheme' => 'tcp'));
         $connection = new StreamConnection($parameters);
 
-        $this->assertSame($parameters, $connection->getParameters());
+        $this->assertInstanceOf('Predis\Connection\NodeConnectionInterface', $connection);
+    }
+
+    /**
+     * @group disconnected
+     */
+    public function testSupportsSchemeRedis()
+    {
+        $parameters = $this->getParameters(array('scheme' => 'redis'));
+        $connection = new StreamConnection($parameters);
+
+        $this->assertInstanceOf('Predis\Connection\NodeConnectionInterface', $connection);
+    }
+
+    /**
+     * @group disconnected
+     */
+    public function testSupportsSchemeUnix()
+    {
+        $parameters = $this->getParameters(array('scheme' => 'unix'));
+        $connection = new StreamConnection($parameters);
+
+        $this->assertInstanceOf('Predis\Connection\NodeConnectionInterface', $connection);
     }
 
     /**
@@ -48,6 +70,17 @@ class StreamConnectionTest extends PredisConnectionTestCase
         new StreamConnection($parameters);
     }
 
+    /**
+     * @group disconnected
+     */
+    public function testExposesParameters()
+    {
+        $parameters = $this->getParameters();
+        $connection = new StreamConnection($parameters);
+
+        $this->assertSame($parameters, $connection->getParameters());
+    }
+
     /**
      * @group disconnected
      */

+ 22 - 0
tests/Predis/Connection/WebdisConnectionTest.php

@@ -32,6 +32,28 @@ class WebdisConnectionTest extends PredisTestCase
         $this->assertTrue($connection->isConnected());
     }
 
+    /**
+     * @group disconnected
+     */
+    public function testSupportsSchemeUnix()
+    {
+        $parameters = $this->getParameters(array('scheme' => 'http'));
+        $connection = new WebdisConnection($parameters);
+
+        $this->assertInstanceOf('Predis\Connection\NodeConnectionInterface', $connection);
+    }
+
+    /**
+     * @group disconnected
+     * @expectedException \InvalidArgumentException
+     * @expectedExceptionMessage Invalid scheme: 'tcp'.
+     */
+    public function testThrowsExceptionOnInvalidScheme()
+    {
+        $parameters = $this->getParameters(array('scheme' => 'tcp'));
+        new WebdisConnection($parameters);
+    }
+
     /**
      * @group disconnected
      * @expectedException \Predis\NotSupportedException