From 40c96122bc149e11a63eddd41b4b2b5da8d462fe Mon Sep 17 00:00:00 2001 From: Maksim Kotlyar Date: Fri, 22 Sep 2017 16:50:55 +0300 Subject: [PATCH] [redis] add dsn support for redis transport. --- docs/transport/redis.md | 21 ++- .../DsnToConnectionFactoryFunctionTest.php | 3 + pkg/enqueue/functions.php | 9 +- pkg/redis/RedisConnectionFactory.php | 76 ++++++++-- .../RedisConnectionFactoryConfigTest.php | 135 ++++++++++++++++++ 5 files changed, 226 insertions(+), 18 deletions(-) create mode 100644 pkg/redis/Tests/RedisConnectionFactoryConfigTest.php diff --git a/docs/transport/redis.md b/docs/transport/redis.md index 42e347f6f..3ded64a36 100644 --- a/docs/transport/redis.md +++ b/docs/transport/redis.md @@ -38,13 +38,26 @@ $ composer require enqueue/redis predis/predis:^1 'localhost', - 'port' => 6379, +// connects to localhost +$factory = new RedisConnectionFactory(); + +// same as above +$factory = new RedisConnectionFactory('redis:'); + +// same as above +$factory = new RedisConnectionFactory([]); + +// connect to Redis at example.com port 1000 using phpredis extension +$factory = new RedisConnectionFactory([ + 'host' => 'example.com', + 'port' => 1000, 'vendor' => 'phpredis', ]); -$psrContext = $connectionFactory->createContext(); +// same as above but given as DSN string +$factory = new RedisConnectionFactory('redis://example.com:1000?vendor=phpredis'); + +$psrContext = $factory->createContext(); ``` * With predis library: diff --git a/pkg/enqueue/Tests/Functions/DsnToConnectionFactoryFunctionTest.php b/pkg/enqueue/Tests/Functions/DsnToConnectionFactoryFunctionTest.php index fd455b15d..da8ba0156 100644 --- a/pkg/enqueue/Tests/Functions/DsnToConnectionFactoryFunctionTest.php +++ b/pkg/enqueue/Tests/Functions/DsnToConnectionFactoryFunctionTest.php @@ -9,6 +9,7 @@ use Enqueue\Null\NullConnectionFactory; use Enqueue\Pheanstalk\PheanstalkConnectionFactory; use Enqueue\RdKafka\RdKafkaConnectionFactory; +use Enqueue\Redis\RedisConnectionFactory; use PHPUnit\Framework\TestCase; class DsnToConnectionFactoryFunctionTest extends TestCase @@ -71,5 +72,7 @@ public static function provideDSNs() // yield ['gearman://', GearmanConnectionFactory::class]; yield ['rdkafka://', RdKafkaConnectionFactory::class]; + + yield ['redis:', RedisConnectionFactory::class]; } } diff --git a/pkg/enqueue/functions.php b/pkg/enqueue/functions.php index 215899c50..754b29f6d 100644 --- a/pkg/enqueue/functions.php +++ b/pkg/enqueue/functions.php @@ -12,6 +12,7 @@ use Enqueue\Null\NullConnectionFactory; use Enqueue\Pheanstalk\PheanstalkConnectionFactory; use Enqueue\RdKafka\RdKafkaConnectionFactory; +use Enqueue\Redis\RedisConnectionFactory; use Interop\Queue\PsrConnectionFactory; use Interop\Queue\PsrContext; @@ -80,8 +81,12 @@ function dsn_to_connection_factory($dsn) $map['rdkafka'] = RdKafkaConnectionFactory::class; } - list($scheme) = explode('://', $dsn); - if (false == $scheme || false === strpos($dsn, '://')) { + if (class_exists(RedisConnectionFactory::class)) { + $map['redis'] = RedisConnectionFactory::class; + } + + list($scheme) = explode(':', $dsn, 2); + if (false == $scheme || false === strpos($dsn, ':')) { throw new \LogicException(sprintf('The scheme could not be parsed from DSN "%s"', $dsn)); } diff --git a/pkg/redis/RedisConnectionFactory.php b/pkg/redis/RedisConnectionFactory.php index 6c520fc4a..122bf0bef 100644 --- a/pkg/redis/RedisConnectionFactory.php +++ b/pkg/redis/RedisConnectionFactory.php @@ -29,20 +29,25 @@ class RedisConnectionFactory implements PsrConnectionFactory * 'lazy' => the connection will be performed as later as possible, if the option set to true * ]. * - * @param $config + * or + * + * redis: + * redis:?vendor=predis + * + * @param array|string|null $config */ - public function __construct(array $config) + public function __construct($config = 'redis:') { - $this->config = array_replace([ - 'host' => null, - 'port' => null, - 'timeout' => null, - 'reserved' => null, - 'retry_interval' => null, - 'vendor' => 'phpredis', - 'persisted' => false, - 'lazy' => true, - ], $config); + if (empty($config) || 'redis:' === $config) { + $config = []; + } elseif (is_string($config)) { + $config = $this->parseDsn($config); + } elseif (is_array($config)) { + } else { + throw new \LogicException('The config must be either an array of options, a DSN string or null'); + } + + $this->config = array_replace($this->defaultConfig(), $config); $supportedVendors = ['predis', 'phpredis']; if (false == in_array($this->config['vendor'], $supportedVendors, true)) { @@ -89,4 +94,51 @@ private function createRedis() return $this->redis; } + + /** + * @param string $dsn + * + * @return array + */ + private function parseDsn($dsn) + { + if (false === strpos($dsn, 'redis:')) { + throw new \LogicException(sprintf('The given DSN "%s" is not supported. Must start with "redis:".', $dsn)); + } + + if (false === $config = parse_url($dsn)) { + throw new \LogicException(sprintf('Failed to parse DSN "%s"', $dsn)); + } + + if ($query = parse_url($dsn, PHP_URL_QUERY)) { + $queryConfig = []; + parse_str($query, $queryConfig); + + $config = array_replace($queryConfig, $config); + } + + unset($config['query'], $config['scheme']); + + $config['lazy'] = empty($config['lazy']) ? false : true; + $config['persisted'] = empty($config['persisted']) ? false : true; + + return $config; + } + + /** + * @return array + */ + private function defaultConfig() + { + return [ + 'host' => 'localhost', + 'port' => 6379, + 'timeout' => null, + 'reserved' => null, + 'retry_interval' => null, + 'vendor' => 'phpredis', + 'persisted' => false, + 'lazy' => true, + ]; + } } diff --git a/pkg/redis/Tests/RedisConnectionFactoryConfigTest.php b/pkg/redis/Tests/RedisConnectionFactoryConfigTest.php new file mode 100644 index 000000000..f3375e0de --- /dev/null +++ b/pkg/redis/Tests/RedisConnectionFactoryConfigTest.php @@ -0,0 +1,135 @@ +expectException(\LogicException::class); + $this->expectExceptionMessage('The config must be either an array of options, a DSN string or null'); + + new RedisConnectionFactory(new \stdClass()); + } + + public function testThrowIfSchemeIsNotAmqp() + { + $this->expectException(\LogicException::class); + $this->expectExceptionMessage('The given DSN "http://example.com" is not supported. Must start with "redis:".'); + + new RedisConnectionFactory('http://example.com'); + } + + public function testThrowIfDsnCouldNotBeParsed() + { + $this->expectException(\LogicException::class); + $this->expectExceptionMessage('Failed to parse DSN "redis://:@/"'); + + new RedisConnectionFactory('redis://:@/'); + } + + public function testThrowIfVendorIsInvalid() + { + $this->expectException(\LogicException::class); + $this->expectExceptionMessage('Unsupported redis vendor given. It must be either "predis", "phpredis". Got "invalidVendor"'); + + new RedisConnectionFactory(['vendor' => 'invalidVendor']); + } + + /** + * @dataProvider provideConfigs + * + * @param mixed $config + * @param mixed $expectedConfig + */ + public function testShouldParseConfigurationAsExpected($config, $expectedConfig) + { + $factory = new RedisConnectionFactory($config); + + $this->assertAttributeEquals($expectedConfig, 'config', $factory); + } + + public static function provideConfigs() + { + yield [ + null, + [ + 'host' => 'localhost', + 'port' => 6379, + 'timeout' => null, + 'reserved' => null, + 'retry_interval' => null, + 'vendor' => 'phpredis', + 'persisted' => false, + 'lazy' => true, + ], + ]; + + yield [ + 'redis:', + [ + 'host' => 'localhost', + 'port' => 6379, + 'timeout' => null, + 'reserved' => null, + 'retry_interval' => null, + 'vendor' => 'phpredis', + 'persisted' => false, + 'lazy' => true, + ], + ]; + + yield [ + [], + [ + 'host' => 'localhost', + 'port' => 6379, + 'timeout' => null, + 'reserved' => null, + 'retry_interval' => null, + 'vendor' => 'phpredis', + 'persisted' => false, + 'lazy' => true, + ], + ]; + + yield [ + 'redis://localhost:1234?foo=bar&lazy=0&persisted=true', + [ + 'host' => 'localhost', + 'port' => 1234, + 'timeout' => null, + 'reserved' => null, + 'retry_interval' => null, + 'vendor' => 'phpredis', + 'persisted' => true, + 'lazy' => false, + 'foo' => 'bar', + ], + ]; + + yield [ + ['host' => 'localhost', 'port' => 1234, 'foo' => 'bar'], + [ + 'host' => 'localhost', + 'port' => 1234, + 'timeout' => null, + 'reserved' => null, + 'retry_interval' => null, + 'vendor' => 'phpredis', + 'persisted' => false, + 'lazy' => true, + 'foo' => 'bar', + ], + ]; + } +}