From 40c96122bc149e11a63eddd41b4b2b5da8d462fe Mon Sep 17 00:00:00 2001 From: Maksim Kotlyar Date: Fri, 22 Sep 2017 16:50:55 +0300 Subject: [PATCH 1/6] [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', + ], + ]; + } +} From 2f68d6d45cf1d165399a3b0f3cf4dde1a6e481d2 Mon Sep 17 00:00:00 2001 From: Maksim Kotlyar Date: Mon, 25 Sep 2017 11:49:13 +0300 Subject: [PATCH 2/6] [redis] remote duplicated tests. --- .../Tests/RedisConnectionFactoryTest.php | 35 ++----------------- 1 file changed, 2 insertions(+), 33 deletions(-) diff --git a/pkg/redis/Tests/RedisConnectionFactoryTest.php b/pkg/redis/Tests/RedisConnectionFactoryTest.php index 4f4973861..b5d7347ab 100644 --- a/pkg/redis/Tests/RedisConnectionFactoryTest.php +++ b/pkg/redis/Tests/RedisConnectionFactoryTest.php @@ -6,8 +6,9 @@ use Enqueue\Redis\RedisContext; use Enqueue\Test\ClassExtensionTrait; use Interop\Queue\PsrConnectionFactory; +use PHPUnit\Framework\TestCase; -class RedisConnectionFactoryTest extends \PHPUnit\Framework\TestCase +class RedisConnectionFactoryTest extends TestCase { use ClassExtensionTrait; @@ -16,38 +17,6 @@ public function testShouldImplementConnectionFactoryInterface() $this->assertClassImplements(PsrConnectionFactory::class, RedisConnectionFactory::class); } - public function testCouldBeConstructedWithEmptyConfiguration() - { - $factory = new RedisConnectionFactory([]); - - $this->assertAttributeEquals([ - 'host' => null, - 'port' => null, - 'timeout' => null, - 'reserved' => null, - 'retry_interval' => null, - 'persisted' => false, - 'lazy' => true, - 'vendor' => 'phpredis', - ], 'config', $factory); - } - - public function testCouldBeConstructedWithCustomConfiguration() - { - $factory = new RedisConnectionFactory(['host' => 'theCustomHost']); - - $this->assertAttributeEquals([ - 'host' => 'theCustomHost', - 'port' => null, - 'timeout' => null, - 'reserved' => null, - 'retry_interval' => null, - 'persisted' => false, - 'lazy' => true, - 'vendor' => 'phpredis', - ], 'config', $factory); - } - public function testShouldCreateLazyContext() { $factory = new RedisConnectionFactory(['lazy' => true]); From afaffc0bbc82f37a3486376727a7e617f4766326 Mon Sep 17 00:00:00 2001 From: Maksim Kotlyar Date: Mon, 25 Sep 2017 12:07:17 +0300 Subject: [PATCH 3/6] [stomp] Add stom DSN. --- .../DsnToConnectionFactoryFunctionTest.php | 3 + .../Functions/DsnToContextFunctionTest.php | 6 + pkg/enqueue/functions.php | 5 + pkg/stomp/StompConnectionFactory.php | 90 ++++++++++-- .../StompConnectionFactoryConfigTest.php | 132 ++++++++++++++++++ .../Tests/StompConnectionFactoryTest.php | 34 ----- 6 files changed, 223 insertions(+), 47 deletions(-) create mode 100644 pkg/stomp/Tests/StompConnectionFactoryConfigTest.php diff --git a/pkg/enqueue/Tests/Functions/DsnToConnectionFactoryFunctionTest.php b/pkg/enqueue/Tests/Functions/DsnToConnectionFactoryFunctionTest.php index da8ba0156..4ebed778d 100644 --- a/pkg/enqueue/Tests/Functions/DsnToConnectionFactoryFunctionTest.php +++ b/pkg/enqueue/Tests/Functions/DsnToConnectionFactoryFunctionTest.php @@ -10,6 +10,7 @@ use Enqueue\Pheanstalk\PheanstalkConnectionFactory; use Enqueue\RdKafka\RdKafkaConnectionFactory; use Enqueue\Redis\RedisConnectionFactory; +use Enqueue\Stomp\StompConnectionFactory; use PHPUnit\Framework\TestCase; class DsnToConnectionFactoryFunctionTest extends TestCase @@ -74,5 +75,7 @@ public static function provideDSNs() yield ['rdkafka://', RdKafkaConnectionFactory::class]; yield ['redis:', RedisConnectionFactory::class]; + + yield ['stomp:', StompConnectionFactory::class]; } } diff --git a/pkg/enqueue/Tests/Functions/DsnToContextFunctionTest.php b/pkg/enqueue/Tests/Functions/DsnToContextFunctionTest.php index 54daf8c5f..350e401aa 100644 --- a/pkg/enqueue/Tests/Functions/DsnToContextFunctionTest.php +++ b/pkg/enqueue/Tests/Functions/DsnToContextFunctionTest.php @@ -5,6 +5,8 @@ use Enqueue\AmqpExt\AmqpContext; use Enqueue\Fs\FsContext; use Enqueue\Null\NullContext; +use Enqueue\Redis\RedisContext; +use Enqueue\Stomp\StompContext; use PHPUnit\Framework\TestCase; class DsnToContextFunctionTest extends TestCase @@ -57,5 +59,9 @@ public static function provideDSNs() yield ['file://'.sys_get_temp_dir(), FsContext::class]; yield ['null://', NullContext::class]; + + yield ['redis:', RedisContext::class]; + + yield ['stomp:', StompContext::class]; } } diff --git a/pkg/enqueue/functions.php b/pkg/enqueue/functions.php index 754b29f6d..7d9ef1eb7 100644 --- a/pkg/enqueue/functions.php +++ b/pkg/enqueue/functions.php @@ -13,6 +13,7 @@ use Enqueue\Pheanstalk\PheanstalkConnectionFactory; use Enqueue\RdKafka\RdKafkaConnectionFactory; use Enqueue\Redis\RedisConnectionFactory; +use Enqueue\Stomp\StompConnectionFactory; use Interop\Queue\PsrConnectionFactory; use Interop\Queue\PsrContext; @@ -85,6 +86,10 @@ function dsn_to_connection_factory($dsn) $map['redis'] = RedisConnectionFactory::class; } + if (class_exists(StompConnectionFactory::class)) { + $map['stomp'] = StompConnectionFactory::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/stomp/StompConnectionFactory.php b/pkg/stomp/StompConnectionFactory.php index 29fc35c49..ce2aae733 100644 --- a/pkg/stomp/StompConnectionFactory.php +++ b/pkg/stomp/StompConnectionFactory.php @@ -18,21 +18,37 @@ class StompConnectionFactory implements PsrConnectionFactory private $stomp; /** - * @param array $config + * $config = [ + * 'host' => null, + * 'port' => null, + * 'login' => null, + * 'password' => null, + * 'vhost' => null, + * 'buffer_size' => 1000, + * 'connection_timeout' => 1, + * 'sync' => false, + * 'lazy' => true, + * ]. + * + * or + * + * stomp: + * stomp:?buffer_size=100 + * + * @param array|string|null $config */ - public function __construct(array $config) + public function __construct($config = 'stomp:') { - $this->config = array_replace([ - 'host' => null, - 'port' => null, - 'login' => null, - 'password' => null, - 'vhost' => null, - 'buffer_size' => 1000, - 'connection_timeout' => 1, - 'sync' => false, - 'lazy' => true, - ], $config); + if (empty($config) || 'stomp:' === $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); } /** @@ -72,4 +88,52 @@ private function establishConnection() return $this->stomp; } + + /** + * @param string $dsn + * + * @return array + */ + private function parseDsn($dsn) + { + if (false === strpos($dsn, 'stomp:')) { + throw new \LogicException(sprintf('The given DSN "%s" is not supported. Must start with "stomp:".', $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['sync'] = empty($config['sync']) ? false : true; + $config['lazy'] = empty($config['lazy']) ? false : true; + + return $config; + } + + /** + * @return array + */ + private function defaultConfig() + { + return [ + 'host' => 'localhost', + 'port' => 61613, + 'login' => 'guest', + 'password' => 'guest', + 'vhost' => '/', + 'buffer_size' => 1000, + 'connection_timeout' => 1, + 'sync' => false, + 'lazy' => true, + ]; + } } diff --git a/pkg/stomp/Tests/StompConnectionFactoryConfigTest.php b/pkg/stomp/Tests/StompConnectionFactoryConfigTest.php new file mode 100644 index 000000000..61f8d0515 --- /dev/null +++ b/pkg/stomp/Tests/StompConnectionFactoryConfigTest.php @@ -0,0 +1,132 @@ +expectException(\LogicException::class); + $this->expectExceptionMessage('The config must be either an array of options, a DSN string or null'); + + new StompConnectionFactory(new \stdClass()); + } + + public function testThrowIfSchemeIsNotAmqp() + { + $this->expectException(\LogicException::class); + $this->expectExceptionMessage('The given DSN "http://example.com" is not supported. Must start with "stomp:".'); + + new StompConnectionFactory('http://example.com'); + } + + public function testThrowIfDsnCouldNotBeParsed() + { + $this->expectException(\LogicException::class); + $this->expectExceptionMessage('Failed to parse DSN "stomp://:@/"'); + + new StompConnectionFactory('stomp://:@/'); + } + + /** + * @dataProvider provideConfigs + * + * @param mixed $config + * @param mixed $expectedConfig + */ + public function testShouldParseConfigurationAsExpected($config, $expectedConfig) + { + $factory = new StompConnectionFactory($config); + + $this->assertAttributeEquals($expectedConfig, 'config', $factory); + } + + public static function provideConfigs() + { + yield [ + null, + [ + 'host' => 'localhost', + 'port' => 61613, + 'login' => 'guest', + 'password' => 'guest', + 'vhost' => '/', + 'buffer_size' => 1000, + 'connection_timeout' => 1, + 'sync' => false, + 'lazy' => true, + ], + ]; + + yield [ + 'stomp:', + [ + 'host' => 'localhost', + 'port' => 61613, + 'login' => 'guest', + 'password' => 'guest', + 'vhost' => '/', + 'buffer_size' => 1000, + 'connection_timeout' => 1, + 'sync' => false, + 'lazy' => true, + ], + ]; + + yield [ + [], + [ + 'host' => 'localhost', + 'port' => 61613, + 'login' => 'guest', + 'password' => 'guest', + 'vhost' => '/', + 'buffer_size' => 1000, + 'connection_timeout' => 1, + 'sync' => false, + 'lazy' => true, + ], + ]; + + yield [ + 'stomp://localhost:1234?foo=bar&lazy=0&sync=true', + [ + 'host' => 'localhost', + 'port' => 1234, + 'login' => 'guest', + 'password' => 'guest', + 'vhost' => '/', + 'buffer_size' => 1000, + 'connection_timeout' => 1, + 'sync' => true, + 'lazy' => false, + 'foo' => 'bar', + ], + ]; + + yield [ + ['host' => 'localhost', 'port' => 1234, 'foo' => 'bar'], + [ + 'host' => 'localhost', + 'port' => 1234, + 'login' => 'guest', + 'password' => 'guest', + 'vhost' => '/', + 'buffer_size' => 1000, + 'connection_timeout' => 1, + 'sync' => false, + 'lazy' => true, + 'foo' => 'bar', + ], + ]; + } +} diff --git a/pkg/stomp/Tests/StompConnectionFactoryTest.php b/pkg/stomp/Tests/StompConnectionFactoryTest.php index 9076d946d..8e57e5f57 100644 --- a/pkg/stomp/Tests/StompConnectionFactoryTest.php +++ b/pkg/stomp/Tests/StompConnectionFactoryTest.php @@ -16,40 +16,6 @@ public function testShouldImplementConnectionFactoryInterface() $this->assertClassImplements(PsrConnectionFactory::class, StompConnectionFactory::class); } - public function testCouldBeConstructedWithEmptyConfiguration() - { - $factory = new StompConnectionFactory([]); - - $this->assertAttributeEquals([ - 'host' => null, - 'port' => null, - 'login' => null, - 'password' => null, - 'vhost' => null, - 'buffer_size' => 1000, - 'connection_timeout' => 1, - 'sync' => false, - 'lazy' => true, - ], 'config', $factory); - } - - public function testCouldBeConstructedWithCustomConfiguration() - { - $factory = new StompConnectionFactory(['host' => 'theCustomHost']); - - $this->assertAttributeEquals([ - 'host' => 'theCustomHost', - 'port' => null, - 'login' => null, - 'password' => null, - 'vhost' => null, - 'buffer_size' => 1000, - 'connection_timeout' => 1, - 'sync' => false, - 'lazy' => true, - ], 'config', $factory); - } - public function testShouldCreateLazyContext() { $factory = new StompConnectionFactory(['lazy' => true]); From c81b4ca66b7728be696b8eb5c5f254a0660047bb Mon Sep 17 00:00:00 2001 From: Maksim Kotlyar Date: Mon, 25 Sep 2017 12:20:11 +0300 Subject: [PATCH 4/6] [sqs] Add Amazon SQS DSN. --- .../DsnToConnectionFactoryFunctionTest.php | 3 + .../Functions/DsnToContextFunctionTest.php | 3 + pkg/enqueue/functions.php | 5 + pkg/sqs/SqsConnectionFactory.php | 73 +++++++++-- .../Tests/SqsConnectionFactoryConfigTest.php | 120 ++++++++++++++++++ 5 files changed, 193 insertions(+), 11 deletions(-) create mode 100644 pkg/sqs/Tests/SqsConnectionFactoryConfigTest.php diff --git a/pkg/enqueue/Tests/Functions/DsnToConnectionFactoryFunctionTest.php b/pkg/enqueue/Tests/Functions/DsnToConnectionFactoryFunctionTest.php index 4ebed778d..fff8abf05 100644 --- a/pkg/enqueue/Tests/Functions/DsnToConnectionFactoryFunctionTest.php +++ b/pkg/enqueue/Tests/Functions/DsnToConnectionFactoryFunctionTest.php @@ -10,6 +10,7 @@ use Enqueue\Pheanstalk\PheanstalkConnectionFactory; use Enqueue\RdKafka\RdKafkaConnectionFactory; use Enqueue\Redis\RedisConnectionFactory; +use Enqueue\Sqs\SqsConnectionFactory; use Enqueue\Stomp\StompConnectionFactory; use PHPUnit\Framework\TestCase; @@ -77,5 +78,7 @@ public static function provideDSNs() yield ['redis:', RedisConnectionFactory::class]; yield ['stomp:', StompConnectionFactory::class]; + + yield ['sqs:', SqsConnectionFactory::class]; } } diff --git a/pkg/enqueue/Tests/Functions/DsnToContextFunctionTest.php b/pkg/enqueue/Tests/Functions/DsnToContextFunctionTest.php index 350e401aa..fd1260a4f 100644 --- a/pkg/enqueue/Tests/Functions/DsnToContextFunctionTest.php +++ b/pkg/enqueue/Tests/Functions/DsnToContextFunctionTest.php @@ -6,6 +6,7 @@ use Enqueue\Fs\FsContext; use Enqueue\Null\NullContext; use Enqueue\Redis\RedisContext; +use Enqueue\Sqs\SqsContext; use Enqueue\Stomp\StompContext; use PHPUnit\Framework\TestCase; @@ -63,5 +64,7 @@ public static function provideDSNs() yield ['redis:', RedisContext::class]; yield ['stomp:', StompContext::class]; + + yield ['sqs:', SqsContext::class]; } } diff --git a/pkg/enqueue/functions.php b/pkg/enqueue/functions.php index 7d9ef1eb7..c89a3b89e 100644 --- a/pkg/enqueue/functions.php +++ b/pkg/enqueue/functions.php @@ -13,6 +13,7 @@ use Enqueue\Pheanstalk\PheanstalkConnectionFactory; use Enqueue\RdKafka\RdKafkaConnectionFactory; use Enqueue\Redis\RedisConnectionFactory; +use Enqueue\Sqs\SqsConnectionFactory; use Enqueue\Stomp\StompConnectionFactory; use Interop\Queue\PsrConnectionFactory; use Interop\Queue\PsrContext; @@ -90,6 +91,10 @@ function dsn_to_connection_factory($dsn) $map['stomp'] = StompConnectionFactory::class; } + if (class_exists(SqsConnectionFactory::class)) { + $map['sqs'] = SqsConnectionFactory::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/sqs/SqsConnectionFactory.php b/pkg/sqs/SqsConnectionFactory.php index 966f746fa..8a67db7e8 100644 --- a/pkg/sqs/SqsConnectionFactory.php +++ b/pkg/sqs/SqsConnectionFactory.php @@ -28,19 +28,25 @@ class SqsConnectionFactory implements PsrConnectionFactory * 'lazy' => true, - Enable lazy connection (boolean) * ]. * - * @param $config + * or + * + * sqs: + * sqs::?key=aKey&secret=aSecret&token=aToken + * + * @param array|string|null $config */ - public function __construct(array $config = []) + public function __construct($config = 'sqs:') { - $this->config = array_replace([ - 'key' => null, - 'secret' => null, - 'token' => null, - 'region' => null, - 'retries' => 3, - 'version' => '2012-11-05', - 'lazy' => true, - ], $config); + if (empty($config) || 'sqs:' === $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); } /** @@ -96,4 +102,49 @@ private function establishConnection() return $this->client; } + + /** + * @param string $dsn + * + * @return array + */ + private function parseDsn($dsn) + { + if (false === strpos($dsn, 'sqs:')) { + throw new \LogicException(sprintf('The given DSN "%s" is not supported. Must start with "sqs:".', $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; + + return $config; + } + + /** + * @return array + */ + private function defaultConfig() + { + return [ + 'key' => null, + 'secret' => null, + 'token' => null, + 'region' => null, + 'retries' => 3, + 'version' => '2012-11-05', + 'lazy' => true, + ]; + } } diff --git a/pkg/sqs/Tests/SqsConnectionFactoryConfigTest.php b/pkg/sqs/Tests/SqsConnectionFactoryConfigTest.php new file mode 100644 index 000000000..5f74bcf2f --- /dev/null +++ b/pkg/sqs/Tests/SqsConnectionFactoryConfigTest.php @@ -0,0 +1,120 @@ +expectException(\LogicException::class); + $this->expectExceptionMessage('The config must be either an array of options, a DSN string or null'); + + new SqsConnectionFactory(new \stdClass()); + } + + public function testThrowIfSchemeIsNotAmqp() + { + $this->expectException(\LogicException::class); + $this->expectExceptionMessage('The given DSN "http://example.com" is not supported. Must start with "sqs:".'); + + new SqsConnectionFactory('http://example.com'); + } + + public function testThrowIfDsnCouldNotBeParsed() + { + $this->expectException(\LogicException::class); + $this->expectExceptionMessage('Failed to parse DSN "sqs://:@/"'); + + new SqsConnectionFactory('sqs://:@/'); + } + + /** + * @dataProvider provideConfigs + * + * @param mixed $config + * @param mixed $expectedConfig + */ + public function testShouldParseConfigurationAsExpected($config, $expectedConfig) + { + $factory = new SqsConnectionFactory($config); + + $this->assertAttributeEquals($expectedConfig, 'config', $factory); + } + + public static function provideConfigs() + { + yield [ + null, + [ + 'key' => null, + 'secret' => null, + 'token' => null, + 'region' => null, + 'retries' => 3, + 'version' => '2012-11-05', + 'lazy' => true, + ], + ]; + + yield [ + 'sqs:', + [ + 'key' => null, + 'secret' => null, + 'token' => null, + 'region' => null, + 'retries' => 3, + 'version' => '2012-11-05', + 'lazy' => true, + ], + ]; + + yield [ + [], + [ + 'key' => null, + 'secret' => null, + 'token' => null, + 'region' => null, + 'retries' => 3, + 'version' => '2012-11-05', + 'lazy' => true, + ], + ]; + + yield [ + 'sqs:?key=theKey&secret=theSecret&token=theToken&lazy=0', + [ + 'key' => 'theKey', + 'secret' => 'theSecret', + 'token' => 'theToken', + 'region' => null, + 'retries' => 3, + 'version' => '2012-11-05', + 'lazy' => false, + ], + ]; + + yield [ + ['key' => 'theKey', 'secret' => 'theSecret', 'token' => 'theToken', 'lazy' => false], + [ + 'key' => 'theKey', + 'secret' => 'theSecret', + 'token' => 'theToken', + 'region' => null, + 'retries' => 3, + 'version' => '2012-11-05', + 'lazy' => false, + ], + ]; + } +} From 62c0b76e41e4fd45c079bc60ccc846c2c3320802 Mon Sep 17 00:00:00 2001 From: Maksim Kotlyar Date: Mon, 25 Sep 2017 12:24:04 +0300 Subject: [PATCH 5/6] [docs] add docs for sqs and stomp DSNs --- docs/transport/sqs.md | 9 ++++++--- docs/transport/stomp.md | 25 ++++++++++++++++++------- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/docs/transport/sqs.md b/docs/transport/sqs.md index b799b1f21..2c35c8bc3 100644 --- a/docs/transport/sqs.md +++ b/docs/transport/sqs.md @@ -22,14 +22,17 @@ $ composer require enqueue/sqs ```php 'aKey', 'secret' => 'aSecret', 'region' => 'aRegion', ]); -$psrContext = $connectionFactory->createContext(); +// same as above but given as DSN string +$factory = new SqsConnectionFactory('sqs:?key=aKey&secret=aSecret®ion=aRegion'); + +$psrContext = $factory->createContext(); ``` ## Declare queue. diff --git a/docs/transport/stomp.md b/docs/transport/stomp.md index cb62191fd..1b40f96d1 100644 --- a/docs/transport/stomp.md +++ b/docs/transport/stomp.md @@ -18,15 +18,26 @@ $ composer require enqueue/stomp '127.0.0.1', - 'port' => 61613, - 'login' => 'guest', - 'password' => 'guest', - 'vhost' => '/', +// connects to localhost +$factory = new StompConnectionFactory(); + +// same as above +$factory = new StompConnectionFactory('stomp:'); + +// same as above +$factory = new StompConnectionFactory([]); + +// connect to stomp broker at example.com port 1000 using +$factory = new StompConnectionFactory([ + 'host' => 'example.com', + 'port' => 1000, + 'login' => 'theLogin', ]); -$psrContext = $connectionFactory->createContext(); +// same as above but given as DSN string +$factory = new StompConnectionFactory('stomp://example.com:1000?login=theLogin'); + +$psrContext = $factory->createContext(); ``` ## Send message to topic From 64a9cc13054b2dbb40d3a41d6ea82c79d098d163 Mon Sep 17 00:00:00 2001 From: Maksim Kotlyar Date: Mon, 25 Sep 2017 12:58:39 +0300 Subject: [PATCH 6/6] [symfony] Add missing supported DSNs to default transport factory. --- pkg/amqp-bunny/AmqpConnectionFactory.php | 7 +-- .../Tests/AmqpConnectionFactoryConfigTest.php | 17 ++++++ pkg/amqp-ext/AmqpConnectionFactory.php | 7 +-- .../Tests/AmqpConnectionFactoryConfigTest.php | 19 +++++++ pkg/amqp-lib/AmqpConnectionFactory.php | 7 +-- .../Tests/AmqpConnectionFactoryConfigTest.php | 27 ++++++++++ .../Symfony/DefaultTransportFactory.php | 52 ++++++++++++++++--- .../DsnToConnectionFactoryFunctionTest.php | 3 ++ .../Functions/DsnToContextFunctionTest.php | 3 ++ .../Symfony/DefaultTransportFactoryTest.php | 14 ++++- pkg/enqueue/functions.php | 7 ++- 11 files changed, 144 insertions(+), 19 deletions(-) diff --git a/pkg/amqp-bunny/AmqpConnectionFactory.php b/pkg/amqp-bunny/AmqpConnectionFactory.php index 422650d80..48f5fe742 100644 --- a/pkg/amqp-bunny/AmqpConnectionFactory.php +++ b/pkg/amqp-bunny/AmqpConnectionFactory.php @@ -45,11 +45,12 @@ class AmqpConnectionFactory implements InteropAmqpConnectionFactory, DelayStrate */ public function __construct($config = 'amqp://') { - if (is_string($config) && 0 === strpos($config, 'amqp+bunny://')) { - $config = str_replace('amqp+bunny://', 'amqp://', $config); + if (is_string($config) && 0 === strpos($config, 'amqp+bunny:')) { + $config = str_replace('amqp+bunny:', 'amqp:', $config); } - if (empty($config) || 'amqp://' === $config) { + // third argument is deprecated will be removed in 0.8 + if (empty($config) || 'amqp:' === $config || 'amqp://' === $config) { $config = []; } elseif (is_string($config)) { $config = $this->parseDsn($config); diff --git a/pkg/amqp-bunny/Tests/AmqpConnectionFactoryConfigTest.php b/pkg/amqp-bunny/Tests/AmqpConnectionFactoryConfigTest.php index e659cef68..4f761fd57 100644 --- a/pkg/amqp-bunny/Tests/AmqpConnectionFactoryConfigTest.php +++ b/pkg/amqp-bunny/Tests/AmqpConnectionFactoryConfigTest.php @@ -79,6 +79,23 @@ public static function provideConfigs() // some examples from Appendix A: Examples (https://www.rabbitmq.com/uri-spec.html) + yield [ + 'amqp+bunny:', + [ + 'host' => 'localhost', + 'port' => 5672, + 'vhost' => '/', + 'user' => 'guest', + 'pass' => 'guest', + 'receive_method' => 'basic_get', + 'heartbeat' => 0, + 'qos_prefetch_size' => 0, + 'qos_prefetch_count' => 1, + 'qos_global' => false, + 'lazy' => true, + ], + ]; + yield [ 'amqp+bunny://user:pass@host:10000/vhost', [ diff --git a/pkg/amqp-ext/AmqpConnectionFactory.php b/pkg/amqp-ext/AmqpConnectionFactory.php index ebf4d40df..3002ab0db 100644 --- a/pkg/amqp-ext/AmqpConnectionFactory.php +++ b/pkg/amqp-ext/AmqpConnectionFactory.php @@ -47,11 +47,12 @@ class AmqpConnectionFactory implements InteropAmqpConnectionFactory, DelayStrate */ public function __construct($config = 'amqp://') { - if (is_string($config) && 0 === strpos($config, 'amqp+ext://')) { - $config = str_replace('amqp+ext://', 'amqp://', $config); + if (is_string($config) && 0 === strpos($config, 'amqp+ext:')) { + $config = str_replace('amqp+ext:', 'amqp:', $config); } - if (empty($config) || 'amqp://' === $config) { + // third argument is deprecated will be removed in 0.8 + if (empty($config) || 'amqp:' === $config || 'amqp://' === $config) { $config = []; } elseif (is_string($config)) { $config = $this->parseDsn($config); diff --git a/pkg/amqp-ext/Tests/AmqpConnectionFactoryConfigTest.php b/pkg/amqp-ext/Tests/AmqpConnectionFactoryConfigTest.php index ba95a5f61..99950be4a 100644 --- a/pkg/amqp-ext/Tests/AmqpConnectionFactoryConfigTest.php +++ b/pkg/amqp-ext/Tests/AmqpConnectionFactoryConfigTest.php @@ -81,6 +81,25 @@ public static function provideConfigs() // some examples from Appendix A: Examples (https://www.rabbitmq.com/uri-spec.html) + yield [ + 'amqp+ext:', + [ + 'host' => 'localhost', + 'port' => 5672, + 'vhost' => '/', + 'user' => 'guest', + 'pass' => 'guest', + 'read_timeout' => null, + 'write_timeout' => null, + 'connect_timeout' => null, + 'persisted' => false, + 'lazy' => true, + 'pre_fetch_count' => null, + 'pre_fetch_size' => null, + 'receive_method' => 'basic_get', + ], + ]; + yield [ 'amqp+ext://user:pass@host:10000/vhost', [ diff --git a/pkg/amqp-lib/AmqpConnectionFactory.php b/pkg/amqp-lib/AmqpConnectionFactory.php index c00998c6f..0ecc1e52c 100644 --- a/pkg/amqp-lib/AmqpConnectionFactory.php +++ b/pkg/amqp-lib/AmqpConnectionFactory.php @@ -50,11 +50,12 @@ class AmqpConnectionFactory implements InteropAmqpConnectionFactory, DelayStrate */ public function __construct($config = 'amqp://') { - if (is_string($config) && 0 === strpos($config, 'amqp+lib://')) { - $config = str_replace('amqp+lib://', 'amqp://', $config); + if (is_string($config) && 0 === strpos($config, 'amqp+lib:')) { + $config = str_replace('amqp+lib:', 'amqp:', $config); } - if (empty($config) || 'amqp://' === $config) { + // third argument is deprecated will be removed in 0.8 + if (empty($config) || 'amqp:' === $config || 'amqp://' === $config) { $config = []; } elseif (is_string($config)) { $config = $this->parseDsn($config); diff --git a/pkg/amqp-lib/Tests/AmqpConnectionFactoryConfigTest.php b/pkg/amqp-lib/Tests/AmqpConnectionFactoryConfigTest.php index 71597c9a7..50dcc12cc 100644 --- a/pkg/amqp-lib/Tests/AmqpConnectionFactoryConfigTest.php +++ b/pkg/amqp-lib/Tests/AmqpConnectionFactoryConfigTest.php @@ -89,6 +89,33 @@ public static function provideConfigs() // some examples from Appendix A: Examples (https://www.rabbitmq.com/uri-spec.html) + yield [ + 'amqp+lib:', + [ + 'host' => 'localhost', + 'port' => 5672, + 'vhost' => '/', + 'user' => 'guest', + 'pass' => 'guest', + 'read_timeout' => 3, + 'write_timeout' => 3, + 'lazy' => true, + 'receive_method' => 'basic_get', + 'stream' => true, + 'insist' => false, + 'login_method' => 'AMQPLAIN', + 'login_response' => null, + 'locale' => 'en_US', + 'keepalive' => false, + 'heartbeat' => 0, + 'connection_timeout' => 3.0, + 'read_write_timeout' => 3.0, + 'qos_prefetch_size' => 0, + 'qos_prefetch_count' => 1, + 'qos_global' => false, + ], + ]; + yield [ 'amqp+lib://user:pass@host:10000/vhost', [ diff --git a/pkg/enqueue/Symfony/DefaultTransportFactory.php b/pkg/enqueue/Symfony/DefaultTransportFactory.php index 146197098..b79499c9c 100644 --- a/pkg/enqueue/Symfony/DefaultTransportFactory.php +++ b/pkg/enqueue/Symfony/DefaultTransportFactory.php @@ -2,14 +2,26 @@ namespace Enqueue\Symfony; -use Enqueue\AmqpExt\AmqpConnectionFactory; +use Enqueue\AmqpBunny\AmqpConnectionFactory as AmqpBunnyConnectionFactory; +use Enqueue\AmqpBunny\Symfony\AmqpBunnyTransportFactory; +use Enqueue\AmqpExt\AmqpConnectionFactory as AmqpExtConnectionFactory; use Enqueue\AmqpExt\Symfony\AmqpTransportFactory; +use Enqueue\AmqpLib\AmqpConnectionFactory as AmqpLibConnectionFactory; +use Enqueue\AmqpLib\Symfony\AmqpLibTransportFactory; use Enqueue\Dbal\DbalConnectionFactory; use Enqueue\Dbal\Symfony\DbalTransportFactory; use Enqueue\Fs\FsConnectionFactory; use Enqueue\Fs\Symfony\FsTransportFactory; +use Enqueue\Gps\GpsConnectionFactory; +use Enqueue\Gps\Symfony\GpsTransportFactory; use Enqueue\Null\NullConnectionFactory; use Enqueue\Null\Symfony\NullTransportFactory; +use Enqueue\Redis\RedisConnectionFactory; +use Enqueue\Redis\Symfony\RedisTransportFactory; +use Enqueue\Sqs\SqsConnectionFactory; +use Enqueue\Sqs\Symfony\SqsTransportFactory; +use Enqueue\Stomp\StompConnectionFactory; +use Enqueue\Stomp\Symfony\StompTransportFactory; use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; use Symfony\Component\DependencyInjection\ContainerBuilder; use function Enqueue\dsn_to_connection_factory; @@ -167,27 +179,51 @@ private function resolveDSN(ContainerBuilder $container, $dsn) */ private function findFactory($dsn) { - $connectionFactory = dsn_to_connection_factory($dsn); + $factory = dsn_to_connection_factory($dsn); - if ($connectionFactory instanceof AmqpConnectionFactory) { - return new AmqpTransportFactory('default_amqp'); + if ($factory instanceof AmqpExtConnectionFactory) { + return new AmqpTransportFactory('default_amqp_ext'); } - if ($connectionFactory instanceof FsConnectionFactory) { + if ($factory instanceof AmqpLibConnectionFactory) { + return new AmqpLibTransportFactory('default_amqp_lib'); + } + + if ($factory instanceof AmqpBunnyConnectionFactory) { + return new AmqpBunnyTransportFactory('default_amqp_bunny'); + } + + if ($factory instanceof FsConnectionFactory) { return new FsTransportFactory('default_fs'); } - if ($connectionFactory instanceof DbalConnectionFactory) { + if ($factory instanceof DbalConnectionFactory) { return new DbalTransportFactory('default_dbal'); } - if ($connectionFactory instanceof NullConnectionFactory) { + if ($factory instanceof NullConnectionFactory) { return new NullTransportFactory('default_null'); } + if ($factory instanceof GpsConnectionFactory) { + return new GpsTransportFactory('default_gps'); + } + + if ($factory instanceof RedisConnectionFactory) { + return new RedisTransportFactory('default_redis'); + } + + if ($factory instanceof SqsConnectionFactory) { + return new SqsTransportFactory('default_sqs'); + } + + if ($factory instanceof StompConnectionFactory) { + return new StompTransportFactory('default_stomp'); + } + throw new \LogicException(sprintf( 'There is no supported transport factory for the connection factory "%s" created from DSN "%s"', - get_class($connectionFactory), + get_class($factory), $dsn )); } diff --git a/pkg/enqueue/Tests/Functions/DsnToConnectionFactoryFunctionTest.php b/pkg/enqueue/Tests/Functions/DsnToConnectionFactoryFunctionTest.php index fff8abf05..4b7df3852 100644 --- a/pkg/enqueue/Tests/Functions/DsnToConnectionFactoryFunctionTest.php +++ b/pkg/enqueue/Tests/Functions/DsnToConnectionFactoryFunctionTest.php @@ -6,6 +6,7 @@ use Enqueue\Dbal\DbalConnectionFactory; use Enqueue\Fs\FsConnectionFactory; use Enqueue\Gearman\GearmanConnectionFactory; +use Enqueue\Gps\GpsConnectionFactory; use Enqueue\Null\NullConnectionFactory; use Enqueue\Pheanstalk\PheanstalkConnectionFactory; use Enqueue\RdKafka\RdKafkaConnectionFactory; @@ -80,5 +81,7 @@ public static function provideDSNs() yield ['stomp:', StompConnectionFactory::class]; yield ['sqs:', SqsConnectionFactory::class]; + + yield ['gps:', GpsConnectionFactory::class]; } } diff --git a/pkg/enqueue/Tests/Functions/DsnToContextFunctionTest.php b/pkg/enqueue/Tests/Functions/DsnToContextFunctionTest.php index fd1260a4f..4e8b4c601 100644 --- a/pkg/enqueue/Tests/Functions/DsnToContextFunctionTest.php +++ b/pkg/enqueue/Tests/Functions/DsnToContextFunctionTest.php @@ -4,6 +4,7 @@ use Enqueue\AmqpExt\AmqpContext; use Enqueue\Fs\FsContext; +use Enqueue\Gps\GpsContext; use Enqueue\Null\NullContext; use Enqueue\Redis\RedisContext; use Enqueue\Sqs\SqsContext; @@ -66,5 +67,7 @@ public static function provideDSNs() yield ['stomp:', StompContext::class]; yield ['sqs:', SqsContext::class]; + + yield ['gps:', GpsContext::class]; } } diff --git a/pkg/enqueue/Tests/Symfony/DefaultTransportFactoryTest.php b/pkg/enqueue/Tests/Symfony/DefaultTransportFactoryTest.php index 953145988..5ee18c211 100644 --- a/pkg/enqueue/Tests/Symfony/DefaultTransportFactoryTest.php +++ b/pkg/enqueue/Tests/Symfony/DefaultTransportFactoryTest.php @@ -250,7 +250,11 @@ public function testShouldCreateDriverFromDsn($dsn, $expectedName) public static function provideDSNs() { - yield ['amqp://', 'default_amqp']; + yield ['amqp+ext://', 'default_amqp_ext']; + + yield ['amqp+lib:', 'default_amqp_lib']; + + yield ['amqp+bunny://', 'default_amqp_bunny']; yield ['null://', 'default_null']; @@ -259,5 +263,13 @@ public static function provideDSNs() yield ['mysql://', 'default_dbal']; yield ['pgsql://', 'default_dbal']; + + yield ['gps:', 'default_gps']; + + yield ['sqs:', 'default_sqs']; + + yield ['redis:', 'default_redis']; + + yield ['stomp:', 'default_stomp']; } } diff --git a/pkg/enqueue/functions.php b/pkg/enqueue/functions.php index c89a3b89e..fef445f25 100644 --- a/pkg/enqueue/functions.php +++ b/pkg/enqueue/functions.php @@ -9,6 +9,7 @@ use Enqueue\Dbal\DbalConnectionFactory; use Enqueue\Fs\FsConnectionFactory; use Enqueue\Gearman\GearmanConnectionFactory; +use Enqueue\Gps\GpsConnectionFactory; use Enqueue\Null\NullConnectionFactory; use Enqueue\Pheanstalk\PheanstalkConnectionFactory; use Enqueue\RdKafka\RdKafkaConnectionFactory; @@ -35,7 +36,7 @@ function dsn_to_connection_factory($dsn) $map['amqp+ext'] = AmqpExtConnectionFactory::class; } if (class_exists(AmqpBunnyConnectionFactory::class)) { - $map['amqp+lib'] = AmqpBunnyConnectionFactory::class; + $map['amqp+lib'] = AmqpLibConnectionFactory::class; } if (class_exists(AmqpLibConnectionFactory::class)) { $map['amqp+bunny'] = AmqpBunnyConnectionFactory::class; @@ -95,6 +96,10 @@ function dsn_to_connection_factory($dsn) $map['sqs'] = SqsConnectionFactory::class; } + if (class_exists(GpsConnectionFactory::class)) { + $map['gps'] = GpsConnectionFactory::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));