diff --git a/composer.json b/composer.json index a71fd586f..d57b35757 100644 --- a/composer.json +++ b/composer.json @@ -78,9 +78,6 @@ }, "exclude-from-classmap": [ "/Tests/" - ], - "files": [ - "pkg/enqueue/functions_include.php" ] }, "autoload-dev": { diff --git a/docs/transport/amqp.md b/docs/transport/amqp.md index 9ff8e9900..7c073d407 100644 --- a/docs/transport/amqp.md +++ b/docs/transport/amqp.md @@ -63,9 +63,9 @@ $factory = new AmqpConnectionFactory([ $psrContext = $factory->createContext(); -// if you have enqueue/enqueue library installed you can use a function from there to create the context -$psrContext = \Enqueue\dsn_to_context('amqp:'); -$psrContext = \Enqueue\dsn_to_context('amqp+ext:'); +// if you have enqueue/enqueue library installed you can use a factory to build context from DSN +$psrContext = (new \Enqueue\ConnectionFactoryFactory())->create('amqp:')->createContext(); +$psrContext = (new \Enqueue\ConnectionFactoryFactory())->create('amqp+ext:')->createContext(); ``` ## Declare topic. diff --git a/docs/transport/amqp_bunny.md b/docs/transport/amqp_bunny.md index 72dc3184d..241cfe836 100644 --- a/docs/transport/amqp_bunny.md +++ b/docs/transport/amqp_bunny.md @@ -53,9 +53,9 @@ $factory = new AmqpConnectionFactory('amqp://user:pass@example.com:10000/%2f'); $psrContext = $factory->createContext(); -// if you have enqueue/enqueue library installed you can use a function from there to create the context -$psrContext = \Enqueue\dsn_to_context('amqp:'); -$psrContext = \Enqueue\dsn_to_context('amqp+bunny:'); +// if you have enqueue/enqueue library installed you can use a factory to build context from DSN +$psrContext = (new \Enqueue\ConnectionFactoryFactory())->create('amqp:')->createContext(); +$psrContext = (new \Enqueue\ConnectionFactoryFactory())->create('amqp+bunny:')->createContext(); ``` ## Declare topic. diff --git a/docs/transport/amqp_lib.md b/docs/transport/amqp_lib.md index faccda01c..6f749233b 100644 --- a/docs/transport/amqp_lib.md +++ b/docs/transport/amqp_lib.md @@ -61,9 +61,9 @@ $factory = new AmqpConnectionFactory([ $psrContext = $factory->createContext(); -// if you have enqueue/enqueue library installed you can use a function from there to create the context -$psrContext = \Enqueue\dsn_to_context('amqp:'); -$psrContext = \Enqueue\dsn_to_context('amqp+lib:'); +// if you have enqueue/enqueue library installed you can use a factory to build context from DSN +$psrContext = (new \Enqueue\ConnectionFactoryFactory())->create('amqp:')->createContext(); +$psrContext = (new \Enqueue\ConnectionFactoryFactory())->create('amqp+lib:')->createContext(); ``` ## Declare topic. diff --git a/docs/transport/dbal.md b/docs/transport/dbal.md index 2d84782e1..9fcaab04a 100644 --- a/docs/transport/dbal.md +++ b/docs/transport/dbal.md @@ -49,8 +49,8 @@ $factory = new ManagerRegistryConnectionFactory($registry, [ $psrContext = $factory->createContext(); -// if you have enqueue/enqueue library installed you can use a function from there to create the context -$psrContext = \Enqueue\dsn_to_context('mysql:'); +// if you have enqueue/enqueue library installed you can use a factory to build context from DSN +$psrContext = (new \Enqueue\ConnectionFactoryFactory())->create('mysql:')->createContext(); ``` ## Init database diff --git a/docs/transport/filesystem.md b/docs/transport/filesystem.md index 825b7b5a2..02154546f 100644 --- a/docs/transport/filesystem.md +++ b/docs/transport/filesystem.md @@ -48,8 +48,8 @@ $connectionFactory = new FsConnectionFactory([ $psrContext = $connectionFactory->createContext(); -// if you have enqueue/enqueue library installed you can use a function from there to create the context -$psrContext = \Enqueue\dsn_to_context('file:'); +// if you have enqueue/enqueue library installed you can use a factory to build context from DSN +$psrContext = (new \Enqueue\ConnectionFactoryFactory())->create('file:')->createContext(); ``` ## Send message to topic diff --git a/docs/transport/gearman.md b/docs/transport/gearman.md index 0161048d5..1baca30e0 100644 --- a/docs/transport/gearman.md +++ b/docs/transport/gearman.md @@ -39,8 +39,8 @@ $factory = new GearmanConnectionFactory([ $psrContext = $factory->createContext(); -// if you have enqueue/enqueue library installed you can use a function from there to create the context -$psrContext = \Enqueue\dsn_to_context('gearman:'); +// if you have enqueue/enqueue library installed you can use a factory to build context from DSN +$psrContext = (new \Enqueue\ConnectionFactoryFactory())->create('gearman:')->createContext(); ``` ## Send message to topic diff --git a/docs/transport/gps.md b/docs/transport/gps.md index 7d0197f23..86269f9c5 100644 --- a/docs/transport/gps.md +++ b/docs/transport/gps.md @@ -32,8 +32,8 @@ $connectionFactory = new GpsConnectionFactory('gps:'); $psrContext = $connectionFactory->createContext(); -// if you have enqueue/enqueue library installed you can use a function from there to create the context -$psrContext = \Enqueue\dsn_to_context('gps:'); +// if you have enqueue/enqueue library installed you can use a factory to build context from DSN +$psrContext = (new \Enqueue\ConnectionFactoryFactory())->create('gps:')->createContext(); ``` ## Send message to topic diff --git a/docs/transport/kafka.md b/docs/transport/kafka.md index 77006f9d6..b2bb92e09 100644 --- a/docs/transport/kafka.md +++ b/docs/transport/kafka.md @@ -45,8 +45,8 @@ $connectionFactory = new RdKafkaConnectionFactory([ $psrContext = $connectionFactory->createContext(); -// if you have enqueue/enqueue library installed you can use a function from there to create the context -$psrContext = \Enqueue\dsn_to_context('kafka:'); +// if you have enqueue/enqueue library installed you can use a factory to build context from DSN +$psrContext = (new \Enqueue\ConnectionFactoryFactory())->create('kafka:')->createContext(); ``` ## Send message to topic diff --git a/docs/transport/mongodb.md b/docs/transport/mongodb.md index 315bb9bdb..0904c11ed 100644 --- a/docs/transport/mongodb.md +++ b/docs/transport/mongodb.md @@ -41,8 +41,8 @@ $factory = new MongodbConnectionFactory([ $psrContext = $factory->createContext(); -// if you have enqueue/enqueue library installed you can use a function from there to create the context -$psrContext = \Enqueue\dsn_to_context('mongodb:'); +// if you have enqueue/enqueue library installed you can use a factory to build context from DSN +$psrContext = (new \Enqueue\ConnectionFactoryFactory())->create('mongodb:')->createContext(); ``` ## Send message to topic diff --git a/docs/transport/pheanstalk.md b/docs/transport/pheanstalk.md index 4371c2966..e0ee49b8b 100644 --- a/docs/transport/pheanstalk.md +++ b/docs/transport/pheanstalk.md @@ -39,8 +39,8 @@ $factory = new PheanstalkConnectionFactory([ $psrContext = $factory->createContext(); -// if you have enqueue/enqueue library installed you can use a function from there to create the context -$psrContext = \Enqueue\dsn_to_context('beanstalk:'); +// if you have enqueue/enqueue library installed you can use a factory to build context from DSN +$psrContext = (new \Enqueue\ConnectionFactoryFactory())->create('beanstalk:')->createContext(); ``` ## Send message to topic diff --git a/docs/transport/redis.md b/docs/transport/redis.md index b1b715291..871a5d8d2 100644 --- a/docs/transport/redis.md +++ b/docs/transport/redis.md @@ -60,8 +60,8 @@ $factory = new RedisConnectionFactory('redis://example.com:1000?vendor=phpredis' $psrContext = $factory->createContext(); -// if you have enqueue/enqueue library installed you can use a function from there to create the context -$psrContext = \Enqueue\dsn_to_context('redis:'); +// if you have enqueue/enqueue library installed you can use a factory to build context from DSN +$psrContext = (new \Enqueue\ConnectionFactoryFactory())->create('redis:')->createContext(); // pass redis instance directly $redis = new \Enqueue\Redis\PhpRedis([ /** redis connection options */ ]); diff --git a/docs/transport/sqs.md b/docs/transport/sqs.md index 6706fae8d..01ecc698d 100644 --- a/docs/transport/sqs.md +++ b/docs/transport/sqs.md @@ -38,8 +38,8 @@ $psrContext = $factory->createContext(); $client = new Aws\Sqs\SqsClient([ /* ... */ ]); $factory = new SqsConnectionFactory($client); -// if you have enqueue/enqueue library installed you can use a function from there to create the context -$psrContext = \Enqueue\dsn_to_context('sqs:'); +// if you have enqueue/enqueue library installed you can use a factory to build context from DSN +$psrContext = (new \Enqueue\ConnectionFactoryFactory())->create('sqs:')->createContext(); ``` ## Declare queue. diff --git a/docs/transport/stomp.md b/docs/transport/stomp.md index fb9ea7694..251d6b135 100644 --- a/docs/transport/stomp.md +++ b/docs/transport/stomp.md @@ -39,8 +39,8 @@ $factory = new StompConnectionFactory('stomp://example.com:1000?login=theLogin') $psrContext = $factory->createContext(); -// if you have enqueue/enqueue library installed you can use a function from there to create the context -$psrContext = \Enqueue\dsn_to_context('stomp:'); +// if you have enqueue/enqueue library installed you can use a factory to build context from DSN +$psrContext = (new \Enqueue\ConnectionFactoryFactory())->create('stomp:')->createContext(); ``` ## Send message to topic diff --git a/pkg/dsn/Dsn.php b/pkg/dsn/Dsn.php index df5e5e608..48b117779 100644 --- a/pkg/dsn/Dsn.php +++ b/pkg/dsn/Dsn.php @@ -184,18 +184,18 @@ private function parse(string $dsn): void } list($scheme, $dsnWithoutScheme) = explode(':', $dsn, 2); - if (false == preg_match('/[\w\d+-.]/', $scheme)) { - throw new \LogicException('The DSN is invalid. Scheme contains illegal symbols.'); - } $scheme = strtolower($scheme); + if (false == preg_match('/^[a-z\d+-.]*$/', $scheme)) { + throw new \LogicException('The DSN is invalid. Scheme contains illegal symbols.'); + } $schemeParts = explode('+', $scheme); $this->scheme = $scheme; $this->schemeProtocol = $schemeParts[0]; unset($schemeParts[0]); - $this->schemeExtensions = $schemeParts; + $this->schemeExtensions = array_values($schemeParts); if ($host = parse_url($dsn, PHP_URL_HOST)) { $this->host = $host; diff --git a/pkg/dsn/Tests/DsnTest.php b/pkg/dsn/Tests/DsnTest.php index 81290fcf8..b7e43f711 100644 --- a/pkg/dsn/Tests/DsnTest.php +++ b/pkg/dsn/Tests/DsnTest.php @@ -2,8 +2,101 @@ namespace Enqueue\Dsn\Tests; +use Enqueue\Dsn\Dsn; use PHPUnit\Framework\TestCase; class DsnTest extends TestCase { + public function testCouldBeConstructedWithDsnAsFirstArgument() + { + new Dsn('foo://localhost:1234'); + } + + public function testThrowsIfSchemePartIsMissing() + { + $this->expectException(\LogicException::class); + $this->expectExceptionMessage('The DSN is invalid. It does not have scheme separator ":".'); + new Dsn('foobar'); + } + + public function testThrowsIfSchemeContainsIllegalSymbols() + { + $this->expectException(\LogicException::class); + $this->expectExceptionMessage('The DSN is invalid. Scheme contains illegal symbols.'); + new Dsn('foo_&%&^bar://localhost'); + } + + /** + * @dataProvider provideSchemes + */ + public function testShouldParseSchemeCorrectly(string $dsn, string $expectedScheme, string $expectedSchemeProtocol, array $expectedSchemeExtensions) + { + $dsn = new Dsn($dsn); + + $this->assertSame($expectedScheme, $dsn->getScheme()); + $this->assertSame($expectedSchemeProtocol, $dsn->getSchemeProtocol()); + $this->assertSame($expectedSchemeExtensions, $dsn->getSchemeExtensions()); + } + + public function testShouldParseUser() + { + $dsn = new Dsn('amqp+ext://theUser:thePass@theHost:1267/thePath'); + + $this->assertSame('theUser', $dsn->getUser()); + } + + public function testShouldParsePassword() + { + $dsn = new Dsn('amqp+ext://theUser:thePass@theHost:1267/thePath'); + + $this->assertSame('thePass', $dsn->getPassword()); + } + + public function testShouldParseHost() + { + $dsn = new Dsn('amqp+ext://theUser:thePass@theHost:1267/thePath'); + + $this->assertSame('theHost', $dsn->getHost()); + } + + public function testShouldParsePort() + { + $dsn = new Dsn('amqp+ext://theUser:thePass@theHost:1267/thePath'); + + $this->assertSame(1267, $dsn->getPort()); + } + + public function testShouldParsePath() + { + $dsn = new Dsn('amqp+ext://theUser:thePass@theHost:1267/thePath'); + + $this->assertSame('/thePath', $dsn->getPath()); + } + + public function testShouldParseQuery() + { + $dsn = new Dsn('amqp+ext://theUser:thePass@theHost:1267/thePath?foo=fooVal&bar=bar%2fVal'); + + $this->assertSame('foo=fooVal&bar=bar%2fVal', $dsn->getQueryString()); + $this->assertSame(['foo' => 'fooVal', 'bar' => 'bar/Val'], $dsn->getQuery()); + } + + public static function provideSchemes() + { + yield [':', '', '', []]; + + yield ['FOO:', 'foo', 'foo', []]; + + yield ['foo:', 'foo', 'foo', []]; + + yield ['foo+bar:', 'foo+bar', 'foo', ['bar']]; + + yield ['foo+bar+baz:', 'foo+bar+baz', 'foo', ['bar', 'baz']]; + + yield ['foo:?bar=barVal', 'foo', 'foo', []]; + + yield ['amqp+ext://guest:guest@localhost:5672/%2f', 'amqp+ext', 'amqp', ['ext']]; + + yield ['amqp+ext+rabbitmq:', 'amqp+ext+rabbitmq', 'amqp', ['ext', 'rabbitmq']]; + } } diff --git a/pkg/enqueue/ConnectionFactoryFactory.php b/pkg/enqueue/ConnectionFactoryFactory.php index 43fcd931f..dd91fd238 100644 --- a/pkg/enqueue/ConnectionFactoryFactory.php +++ b/pkg/enqueue/ConnectionFactoryFactory.php @@ -8,48 +8,59 @@ class ConnectionFactoryFactory { /** - * @param string|array $config + * @param string * * @return PsrConnectionFactory */ - public function create($config): PsrConnectionFactory + public function create(string $dsn): PsrConnectionFactory { - if (is_string($config)) { - $config = ['dsn' => $config]; - } + $dsn = new Dsn($dsn); - if (false == is_array($config)) { - throw new \InvalidArgumentException(sprintf('Config must be either string or array. Got %s', gettype($config))); - } + $availableSchemes = Resources::getAvailableSchemes(); - if (false == array_key_exists('dsn', $config)) { - throw new \InvalidArgumentException('The config must have dsn field set.'); - } + if (false == array_key_exists($dsn->getScheme(), $availableSchemes)) { + $knownSchemes = Resources::getKnownSchemes(); + if (array_key_exists($dsn->getScheme(), $knownSchemes)) { + $knownConnections = Resources::getKnownConnections(); - $dsn = new Dsn($config['dsn']); + throw new \LogicException(sprintf( + 'A transport "%s" is not installed. Run "composer req %s" to add it.', + $knownSchemes[$dsn->getScheme()], + $knownConnections['package'] + )); + } - $availableSchemes = Resources::getAvailableSchemes(); + throw new \LogicException(sprintf( + 'A transport is not known. Make sure you registered it with "%s" if it is custom one.', + Resources::class + )); + } - if (array_key_exists($dsn->getScheme(), $availableSchemes)) { + $dsnSchemeExtensions = $dsn->getSchemeExtensions(); + if (false == $dsnSchemeExtensions) { $factoryClass = $availableSchemes[$dsn->getScheme()]; - return new $factoryClass($config); + return new $factoryClass((string) $dsn); } - $knownSchemes = Resources::getKnownSchemes(); - if (array_key_exists($dsn->getScheme(), $knownSchemes)) { - $knownConnections = Resources::getKnownConnections(); + $protocol = $dsn->getSchemeProtocol(); + foreach ($availableSchemes as $driverClass => $info) { + if (false == in_array($protocol, $info['schemes'], true)) { + continue; + } - throw new \LogicException(sprintf( - 'A transport "%s" is not installed. Run "composer req %s" to add it.', - $knownSchemes[$dsn->getScheme()], - $knownConnections['package'] - )); + if (empty($info['supportedSchemeExtensions'])) { + continue; + } + + $diff = array_diff($dsnSchemeExtensions, $info['supportedSchemeExtensions']); + if (empty($diff)) { + $factoryClass = $availableSchemes[$dsn->getScheme()]; + + return new $factoryClass((string) $dsn); + } } - throw new \LogicException(sprintf( - 'A transport is not known. Make sure you registered it with "%s" if it is custom one.', - Resources::class - )); + throw new \LogicException(sprintf('There is no factory that supports scheme "%s"', $dsn->getScheme())); } } diff --git a/pkg/enqueue/Resources.php b/pkg/enqueue/Resources.php index 5caaee178..9676cbe18 100644 --- a/pkg/enqueue/Resources.php +++ b/pkg/enqueue/Resources.php @@ -81,17 +81,24 @@ public static function getKnownConnections(): array if (null === self::$knownConnections) { $map = []; - $map[FsConnectionFactory::class] = ['schemes' => ['file'], 'package' => 'enqueue/fs']; + $map[FsConnectionFactory::class] = [ + 'schemes' => ['file'], + 'supportedSchemeExtensions' => [], + 'package' => 'enqueue/fs', + ]; $map[AmqpExtConnectionFactory::class] = [ - 'schemes' => ['amqp+ext', 'amqp', 'amqps', 'amqps+ext', 'amqp+rabbitmq', 'amqps+rabbitmq', 'amqp+rabbitmq+ext', 'amqps+rabbitmq+ext'], + 'schemes' => ['amqp', 'amqps'], + 'supportedSchemeExtensions' => ['ext', 'rabiitmq'], 'package' => 'enqueue/amqp-ext', ]; $map[AmqpBunnyConnectionFactory::class] = [ - 'schemes' => ['amqp+bunny', 'amqp', 'rabbitmq', 'amqp+rabbitmq', 'amqp+rabbitmq+bunny'], + 'schemes' => ['amqp'], + 'supportedSchemeExtensions' => ['bunny', 'rabiitmq'], 'package' => 'enqueue/amqp-bunny', ]; $map[AmqpLibConnectionFactory::class] = [ - 'schemes' => ['amqp+lib', 'amqp', 'amqps', 'amqps+lib', 'amqp+rabbitmq', 'amqps+rabbitmq', 'amqp+rabbitmq+lib', 'amqps+rabbitmq+lib'], + 'schemes' => ['amqp', 'amqps'], + 'supportedSchemeExtensions' => ['lib', 'rabiitmq'], 'package' => 'enqueue/amqp-lib', ]; @@ -111,18 +118,52 @@ public static function getKnownConnections(): array 'sqlite3', 'pdo_sqlite', ], + 'supportedSchemeExtensions' => [], 'package' => 'enqueue/dbal', ]; - $map[NullConnectionFactory::class] = ['schemes' => ['null'], 'package' => 'enqueue/null']; - $map[GearmanConnectionFactory::class] = ['schemes' => ['gearman'], 'package' => 'enqueue/gearman']; - $map[PheanstalkConnectionFactory::class] = ['schemes' => ['beanstalk'], 'package' => 'enqueue/pheanstalk']; - $map[RdKafkaConnectionFactory::class] = ['schemes' => ['kafka', 'rdkafka'], 'package' => 'enqueue/rdkafka']; - $map[RedisConnectionFactory::class] = ['schemes' => ['redis'], 'package' => 'enqueue/redis']; - $map[StompConnectionFactory::class] = ['schemes' => ['stomp', 'stomp+rabbitmq'], 'package' => 'enqueue/stomp']; - $map[SqsConnectionFactory::class] = ['schemes' => ['sqs'], 'package' => 'enqueue/sqs']; - $map[GpsConnectionFactory::class] = ['schemes' => ['gps'], 'package' => 'enqueue/gps']; - $map[MongodbConnectionFactory::class] = ['schemes' => ['mongodb'], 'package' => 'enqueue/mongodb']; + $map[NullConnectionFactory::class] = [ + 'schemes' => ['null'], + 'supportedSchemeExtensions' => [], + 'package' => 'enqueue/null', + ]; + $map[GearmanConnectionFactory::class] = [ + 'schemes' => ['gearman'], + 'supportedSchemeExtensions' => [], + 'package' => 'enqueue/gearman', + ]; + $map[PheanstalkConnectionFactory::class] = [ + 'schemes' => ['beanstalk'], + 'supportedSchemeExtensions' => ['pheanstalk'], + 'package' => 'enqueue/pheanstalk', + ]; + $map[RdKafkaConnectionFactory::class] = [ + 'schemes' => ['kafka', 'rdkafka'], + 'supportedSchemeExtensions' => ['rdkafka'], + 'package' => 'enqueue/rdkafka', + ]; + $map[RedisConnectionFactory::class] = [ + 'schemes' => ['redis'], + 'supportedSchemeExtensions' => ['predis', 'phpredis'], + 'package' => 'enqueue/redis', + ]; + $map[StompConnectionFactory::class] = [ + 'schemes' => ['stomp'], + 'supportedSchemeExtensions' => ['rabbitmq'], + 'package' => 'enqueue/stomp', ]; + $map[SqsConnectionFactory::class] = [ + 'schemes' => ['sqs'], + 'supportedSchemeExtensions' => [], + 'package' => 'enqueue/sqs', ]; + $map[GpsConnectionFactory::class] = [ + 'schemes' => ['gps'], + 'supportedSchemeExtensions' => [], + 'package' => 'enqueue/gps', ]; + $map[MongodbConnectionFactory::class] = [ + 'schemes' => ['mongodb'], + 'supportedSchemeExtensions' => [], + 'package' => 'enqueue/mongodb', + ]; self::$knownConnections = $map; } @@ -130,9 +171,9 @@ public static function getKnownConnections(): array return self::$knownConnections; } - public static function addConnection(string $connectionFactoryClass, array $schemes, string $package): void + public static function addConnection(string $connectionFactoryClass, array $schemes, array $extensions, string $package): void { - if (class_exists($connectionFactoryClass)) { + if (false == class_exists($connectionFactoryClass)) { throw new \InvalidArgumentException(sprintf('The connection factory class "%s" does not exist.', $connectionFactoryClass)); } @@ -141,13 +182,17 @@ public static function addConnection(string $connectionFactoryClass, array $sche } if (empty($schemes)) { - throw new \InvalidArgumentException('Schemes could not be empty'); + throw new \InvalidArgumentException('Schemes could not be empty.'); } if (empty($package)) { - throw new \InvalidArgumentException('Package name could not be empty'); + throw new \InvalidArgumentException('Package name could not be empty.'); } self::getKnownConnections(); - self::$knownConnections[$connectionFactoryClass] = ['schemes' => $schemes, 'package' => $package]; + self::$knownConnections[$connectionFactoryClass] = [ + 'schemes' => $schemes, + 'supportedSchemeExtensions' => $extensions, + 'package' => $package, + ]; } } diff --git a/pkg/enqueue/Symfony/AmqpTransportFactory.php b/pkg/enqueue/Symfony/AmqpTransportFactory.php index e69bf44de..cf6f34310 100644 --- a/pkg/enqueue/Symfony/AmqpTransportFactory.php +++ b/pkg/enqueue/Symfony/AmqpTransportFactory.php @@ -6,13 +6,13 @@ use Enqueue\AmqpExt\AmqpConnectionFactory as AmqpExtConnectionFactory; use Enqueue\AmqpLib\AmqpConnectionFactory as AmqpLibConnectionFactory; use Enqueue\Client\Amqp\AmqpDriver; +use Enqueue\ConnectionFactoryFactory; use Interop\Amqp\AmqpConnectionFactory; use Interop\Amqp\AmqpContext; use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; -use function Enqueue\dsn_to_connection_factory; class AmqpTransportFactory implements TransportFactoryInterface, DriverFactoryInterface { @@ -221,7 +221,7 @@ public static function createConnectionFactoryFactory(array $config) } $dsn = array_key_exists('dsn', $config) ? $config['dsn'] : 'amqp:'; - $factory = dsn_to_connection_factory($dsn); + $factory = (new ConnectionFactoryFactory())->create($dsn); if (false == $factory instanceof AmqpConnectionFactory) { throw new \LogicException(sprintf('Factory must be instance of "%s" but got "%s"', AmqpConnectionFactory::class, get_class($factory))); diff --git a/pkg/enqueue/Symfony/DefaultTransportFactory.php b/pkg/enqueue/Symfony/DefaultTransportFactory.php index e87ff100e..a65f2bf13 100644 --- a/pkg/enqueue/Symfony/DefaultTransportFactory.php +++ b/pkg/enqueue/Symfony/DefaultTransportFactory.php @@ -42,12 +42,12 @@ public function addConfiguration(ArrayNodeDefinition $builder) } if (empty($v)) { - return 'null:'; + return ['dsn' => 'null:']; } if (is_string($v)) { return false !== strpos($v, ':') || false !== strpos($v, 'env_') ? - $v : + ['dsn' => $v] : ['alias' => $v] ; } @@ -66,14 +66,13 @@ public function addConfiguration(ArrayNodeDefinition $builder) public function createConnectionFactory(ContainerBuilder $container, array $config) { $factoryId = sprintf('enqueue.transport.%s.connection_factory', $this->getName()); - if (isset($config['alias'])) { $aliasId = sprintf('enqueue.transport.%s.connection_factory', $config['alias']); $container->setAlias($factoryId, new Alias($aliasId, true)); } else { $container->register($factoryId, PsrConnectionFactory::class) ->setFactory([new Reference('enqueue.connection_factory_factory'), 'create']) - ->addArgument($config) + ->addArgument($config['dsn']) ; } @@ -119,7 +118,7 @@ public function createDriver(ContainerBuilder $container, array $config) $container->register($driverId, DriverInterface::class) ->setFactory([new Reference('enqueue.client.driver_factory'), 'create']) ->addArgument(new Reference($factoryId)) - ->addArgument(is_string($config) ? $config : $config['dsn']) + ->addArgument($config['dsn']) ->addArgument($config) ; } diff --git a/pkg/enqueue/Tests/Functions/DsnToConnectionFactoryFunctionTest.php b/pkg/enqueue/Tests/Functions/DsnToConnectionFactoryFunctionTest.php deleted file mode 100644 index 2c65eb6d9..000000000 --- a/pkg/enqueue/Tests/Functions/DsnToConnectionFactoryFunctionTest.php +++ /dev/null @@ -1,104 +0,0 @@ -expectException(\LogicException::class); - $this->expectExceptionMessage('The scheme could not be parsed from DSN ""'); - - \Enqueue\dsn_to_connection_factory(''); - } - - public function testThrowIfDsnMissingScheme() - { - $this->expectException(\LogicException::class); - $this->expectExceptionMessage('The scheme could not be parsed from DSN "dsnMissingScheme"'); - - \Enqueue\dsn_to_connection_factory('dsnMissingScheme'); - } - - public function testThrowIfDsnNotSupported() - { - $this->expectException(\LogicException::class); - $this->expectExceptionMessage('The scheme "http" is not supported. Supported "file", "amqp+ext"'); - - \Enqueue\dsn_to_connection_factory('http://schemeNotSupported'); - } - - /** - * @dataProvider provideDSNs - * - * @param mixed $dsn - * @param mixed $expectedFactoryClass - */ - public function testReturnsExpectedFactoryInstance($dsn, $expectedFactoryClass) - { - $factory = \Enqueue\dsn_to_connection_factory($dsn); - - $this->assertInstanceOf($expectedFactoryClass, $factory); - } - - public static function provideDSNs() - { - yield ['amqp:', AmqpExtConnectionFactory::class]; - - yield ['amqps:', AmqpExtConnectionFactory::class]; - - yield ['amqp+ext:', AmqpExtConnectionFactory::class]; - - yield ['amqps+ext:', AmqpExtConnectionFactory::class]; - - yield ['amqp+lib:', AmqpLibConnectionFactory::class]; - - yield ['amqps+lib:', AmqpLibConnectionFactory::class]; - - yield ['amqp+bunny:', AmqpBunnyConnectionFactory::class]; - - yield ['amqp://user:pass@foo/vhost', AmqpExtConnectionFactory::class]; - - yield ['file:', FsConnectionFactory::class]; - - yield ['file:///foo/bar/baz', FsConnectionFactory::class]; - - yield ['null:', NullConnectionFactory::class]; - - yield ['mysql:', DbalConnectionFactory::class]; - - yield ['pgsql:', DbalConnectionFactory::class]; - - yield ['beanstalk:', PheanstalkConnectionFactory::class]; - -// yield ['gearman:', GearmanConnectionFactory::class]; - - yield ['kafka:', RdKafkaConnectionFactory::class]; - - yield ['redis:', RedisConnectionFactory::class]; - - yield ['stomp:', StompConnectionFactory::class]; - - yield ['sqs:', SqsConnectionFactory::class]; - - yield ['gps:', GpsConnectionFactory::class]; - - yield ['mongodb:', MongodbConnectionFactory::class]; - } -} diff --git a/pkg/enqueue/Tests/Functions/DsnToContextFunctionTest.php b/pkg/enqueue/Tests/Functions/DsnToContextFunctionTest.php deleted file mode 100644 index 2a8bc83bc..000000000 --- a/pkg/enqueue/Tests/Functions/DsnToContextFunctionTest.php +++ /dev/null @@ -1,73 +0,0 @@ -expectException(\LogicException::class); - $this->expectExceptionMessage('The scheme could not be parsed from DSN ""'); - - \Enqueue\dsn_to_context(''); - } - - public function testThrowIfDsnMissingScheme() - { - $this->expectException(\LogicException::class); - $this->expectExceptionMessage('The scheme could not be parsed from DSN "dsnMissingScheme"'); - - \Enqueue\dsn_to_context('dsnMissingScheme'); - } - - public function testThrowIfDsnNotSupported() - { - $this->expectException(\LogicException::class); - $this->expectExceptionMessage('The scheme "http" is not supported. Supported "file", "amqp+ext"'); - - \Enqueue\dsn_to_context('http://schemeNotSupported'); - } - - /** - * @dataProvider provideDSNs - * - * @param mixed $dsn - * @param mixed $expectedFactoryClass - */ - public function testReturnsExpectedFactoryInstance($dsn, $expectedFactoryClass) - { - $factory = \Enqueue\dsn_to_context($dsn); - - $this->assertInstanceOf($expectedFactoryClass, $factory); - } - - public static function provideDSNs() - { - yield ['amqp:', AmqpContext::class]; - - yield ['amqp://user:pass@foo/vhost', AmqpContext::class]; - - yield ['file:', FsContext::class]; - - yield ['file://'.sys_get_temp_dir(), FsContext::class]; - - yield ['null:', NullContext::class]; - - yield ['redis:', RedisContext::class]; - - yield ['stomp:', StompContext::class]; - - yield ['sqs:', SqsContext::class]; - - yield ['gps:', GpsContext::class]; - } -} diff --git a/pkg/enqueue/Tests/ResourcesTest.php b/pkg/enqueue/Tests/ResourcesTest.php new file mode 100644 index 000000000..fabd28af1 --- /dev/null +++ b/pkg/enqueue/Tests/ResourcesTest.php @@ -0,0 +1,124 @@ +assertTrue($rc->isFinal()); + } + + public function testShouldConstructorBePrivate() + { + $rc = new \ReflectionClass(Resources::class); + + $this->assertTrue($rc->getConstructor()->isPrivate()); + } + + public function testShouldGetAvailableConnectionsInExpectedFormat() + { + $availableConnections = Resources::getAvailableConnections(); + + $this->assertInternalType('array', $availableConnections); + $this->assertArrayHasKey(RedisConnectionFactory::class, $availableConnections); + + $connectionInfo = $availableConnections[RedisConnectionFactory::class]; + $this->assertArrayHasKey('schemes', $connectionInfo); + $this->assertSame(['redis'], $connectionInfo['schemes']); + + $this->assertArrayHasKey('supportedSchemeExtensions', $connectionInfo); + $this->assertSame(['predis', 'phpredis'], $connectionInfo['supportedSchemeExtensions']); + + $this->assertArrayHasKey('package', $connectionInfo); + $this->assertSame('enqueue/redis', $connectionInfo['package']); + } + + public function testShouldGetKnownConnectionsInExpectedFormat() + { + $availableConnections = Resources::getKnownConnections(); + + $this->assertInternalType('array', $availableConnections); + $this->assertArrayHasKey(RedisConnectionFactory::class, $availableConnections); + + $connectionInfo = $availableConnections[RedisConnectionFactory::class]; + $this->assertArrayHasKey('schemes', $connectionInfo); + $this->assertSame(['redis'], $connectionInfo['schemes']); + + $this->assertArrayHasKey('supportedSchemeExtensions', $connectionInfo); + $this->assertSame(['predis', 'phpredis'], $connectionInfo['supportedSchemeExtensions']); + + $this->assertArrayHasKey('package', $connectionInfo); + $this->assertSame('enqueue/redis', $connectionInfo['package']); + } + + public function testThrowsIfConnectionClassNotExistsOnAddConnection() + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('The connection factory class "classNotExist" does not exist.'); + + Resources::addConnection('classNotExist', [], [], 'foo'); + } + + public function testThrowsIfConnectionClassNotImplementConnectionFactoryInterfaceOnAddConnection() + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('The connection factory class "stdClass" must implement "Interop\Queue\PsrConnectionFactory" interface.'); + + Resources::addConnection(\stdClass::class, [], [], 'foo'); + } + + public function testThrowsIfNoSchemesProvidedOnAddConnection() + { + $connectionClass = $this->getMockClass(PsrConnectionFactory::class); + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Schemes could not be empty.'); + + Resources::addConnection($connectionClass, [], [], 'foo'); + } + + public function testThrowsIfNoPackageProvidedOnAddConnection() + { + $connectionClass = $this->getMockClass(PsrConnectionFactory::class); + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Package name could not be empty.'); + + Resources::addConnection($connectionClass, ['foo'], [], ''); + } + + public function testShouldAllowGetPreviouslyRegisteredConnection() + { + $connectionClass = $this->getMockClass(PsrConnectionFactory::class); + + Resources::addConnection( + $connectionClass, + ['fooscheme', 'barscheme'], + ['fooextension', 'barextension'], + 'foo/bar' + ); + + $availableConnections = Resources::getKnownConnections(); + + $this->assertInternalType('array', $availableConnections); + $this->assertArrayHasKey($connectionClass, $availableConnections); + + $connectionInfo = $availableConnections[$connectionClass]; + $this->assertArrayHasKey('schemes', $connectionInfo); + $this->assertSame(['fooscheme', 'barscheme'], $connectionInfo['schemes']); + + $this->assertArrayHasKey('supportedSchemeExtensions', $connectionInfo); + $this->assertSame(['fooextension', 'barextension'], $connectionInfo['supportedSchemeExtensions']); + + $this->assertArrayHasKey('package', $connectionInfo); + $this->assertSame('foo/bar', $connectionInfo['package']); + } +} diff --git a/pkg/enqueue/composer.json b/pkg/enqueue/composer.json index 5d603a859..e61270c7d 100644 --- a/pkg/enqueue/composer.json +++ b/pkg/enqueue/composer.json @@ -56,7 +56,6 @@ }, "autoload": { "psr-4": { "Enqueue\\": "" }, - "files": ["functions_include.php"], "exclude-from-classmap": [ "/Tests/" ] diff --git a/pkg/enqueue/functions.php b/pkg/enqueue/functions.php deleted file mode 100644 index 0b4c6f9c1..000000000 --- a/pkg/enqueue/functions.php +++ /dev/null @@ -1,64 +0,0 @@ -create($config); -} - -/** - * @param string $dsn - * - * @return PsrContext - */ -function dsn_to_context($dsn) -{ - return dsn_to_connection_factory($dsn)->createContext(); -} - -/** - * @param PsrContext $c - * @param string $topic - * @param string $body - */ -function send_topic(PsrContext $c, $topic, $body) -{ - $topic = $c->createTopic($topic); - $message = $c->createMessage($body); - - $c->createProducer()->send($topic, $message); -} - -/** - * @param PsrContext $c - * @param string $queue - * @param string $body - */ -function send_queue(PsrContext $c, $queue, $body) -{ - $queue = $c->createQueue($queue); - $message = $c->createMessage($body); - - $c->createProducer()->send($queue, $message); -} - -/** - * @param PsrContext $c - * @param string $queueName - * @param callable $callback - */ -function consume(PsrContext $c, string $queueName, callable $callback) -{ - $queueConsumer = new QueueConsumer($c); - $queueConsumer->bindCallback($queueName, $callback); - - $queueConsumer->consume(); -} diff --git a/pkg/enqueue/functions_include.php b/pkg/enqueue/functions_include.php deleted file mode 100644 index cf5502ab1..000000000 --- a/pkg/enqueue/functions_include.php +++ /dev/null @@ -1,6 +0,0 @@ -register('enqueue.connection_factory_factory', ConnectionFactoryFactory::class); + + $container->register('enqueue.client.driver_factory', DriverFactory::class) + ->addArgument(new Reference('enqueue.client.config')) + ->addArgument(new Reference('enqueue.client.meta.queue_meta_registry')) + ; + $container->register('enqueue.client.rpc_factory', RpcFactory::class) ->setPublic(true) ->setArguments([ new Reference('enqueue.transport.context'), - ]); + ]) + ; $container->register('enqueue.client.producer', Producer::class) ->setPublic(true) ->setArguments([ new Reference('enqueue.client.driver'), new Reference('enqueue.client.rpc_factory'), - ]); + ]) + ; $container->setAlias('enqueue.client.producer_v2', new Alias('enqueue.client.producer', true));