diff --git a/README.md b/README.md index 08e5b97e..7b813274 100644 --- a/README.md +++ b/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: diff --git a/src/Connection/AbstractConnection.php b/src/Connection/AbstractConnection.php index 46ec1ab6..5fabc0b3 100644 --- a/src/Connection/AbstractConnection.php +++ b/src/Connection/AbstractConnection.php @@ -58,14 +58,14 @@ abstract class AbstractConnection implements NodeConnectionInterface */ protected function assertParameters(ParametersInterface $parameters) { - $scheme = $parameters->scheme; + switch ($parameters->scheme) { + case 'tcp': + case 'redis': + case 'unix': + break; - 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.'); + default: + throw new \InvalidArgumentException("Invalid scheme: '$parameters->scheme'."); } return $parameters; diff --git a/src/Connection/Factory.php b/src/Connection/Factory.php index f26a1df3..c7d991b5 100644 --- a/src/Connection/Factory.php +++ b/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', ); /** diff --git a/src/Connection/Parameters.php b/src/Connection/Parameters.php index 2dea94df..8c8a0d52 100644 --- a/src/Connection/Parameters.php +++ b/src/Connection/Parameters.php @@ -77,12 +77,16 @@ 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"); } @@ -96,6 +100,49 @@ class Parameters implements ParametersInterface 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"); + } + + if (isset($parsed['query'])) { + parse_str($parsed['query'], $queryarray); + unset($parsed['query']); + + $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; + } + /** * Validates and converts each value of the connection parameters array. * diff --git a/src/Connection/PhpiredisSocketConnection.php b/src/Connection/PhpiredisSocketConnection.php index 4d93367f..b6382e67 100644 --- a/src/Connection/PhpiredisSocketConnection.php +++ b/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; } /** diff --git a/src/Connection/PhpiredisStreamConnection.php b/src/Connection/PhpiredisStreamConnection.php index 36f3dfce..73c8d7c3 100644 --- a/src/Connection/PhpiredisStreamConnection.php +++ b/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'. diff --git a/src/Connection/StreamConnection.php b/src/Connection/StreamConnection.php index 5b8aa3d6..8dddb0f5 100644 --- a/src/Connection/StreamConnection.php +++ b/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; diff --git a/tests/Predis/Connection/CompositeStreamConnectionTest.php b/tests/Predis/Connection/CompositeStreamConnectionTest.php index 0ee61616..416883e3 100644 --- a/tests/Predis/Connection/CompositeStreamConnectionTest.php +++ b/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 */ diff --git a/tests/Predis/Connection/FactoryTest.php b/tests/Predis/Connection/FactoryTest.php index eee5b4b3..cb3138df 100644 --- a/tests/Predis/Connection/FactoryTest.php +++ b/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'); } /** diff --git a/tests/Predis/Connection/ParametersTest.php b/tests/Predis/Connection/ParametersTest.php index 43bc6096..5b202324 100644 --- a/tests/Predis/Connection/ParametersTest.php +++ b/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'; diff --git a/tests/Predis/Connection/PhpiredisSocketConnectionTest.php b/tests/Predis/Connection/PhpiredisSocketConnectionTest.php index cf6d6ad7..177f1b40 100644 --- a/tests/Predis/Connection/PhpiredisSocketConnectionTest.php +++ b/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 */ diff --git a/tests/Predis/Connection/PhpiredisStreamConnectionTest.php b/tests/Predis/Connection/PhpiredisStreamConnectionTest.php index ddcc6ffa..2998c123 100644 --- a/tests/Predis/Connection/PhpiredisStreamConnectionTest.php +++ b/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 */ diff --git a/tests/Predis/Connection/StreamConnectionTest.php b/tests/Predis/Connection/StreamConnectionTest.php index cf880dcc..6c7b3b79 100644 --- a/tests/Predis/Connection/StreamConnectionTest.php +++ b/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 */ diff --git a/tests/Predis/Connection/WebdisConnectionTest.php b/tests/Predis/Connection/WebdisConnectionTest.php index c59deae4..d24a8aca 100644 --- a/tests/Predis/Connection/WebdisConnectionTest.php +++ b/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