diff --git a/DependencyInjection/OldSoundRabbitMqExtension.php b/DependencyInjection/OldSoundRabbitMqExtension.php index b6e2e0f5..5a3b7022 100644 --- a/DependencyInjection/OldSoundRabbitMqExtension.php +++ b/DependencyInjection/OldSoundRabbitMqExtension.php @@ -2,14 +2,16 @@ namespace OldSound\RabbitMqBundle\DependencyInjection; +use OldSound\RabbitMqBundle\RabbitMq\ConsumerInterface; +use OldSound\RabbitMqBundle\RabbitMq\ProducerInterface; use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; -use Symfony\Component\DependencyInjection\ContainerInterface; -use Symfony\Component\DependencyInjection\Extension\Extension; +use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; -use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\Extension\Extension; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; -use Symfony\Component\Config\FileLocator; +use Symfony\Component\DependencyInjection\Reference; /** * OldSoundRabbitMqExtension. @@ -171,11 +173,32 @@ protected function loadProducers() if (null !== $producer['service_alias']) { $this->container->setAlias($producer['service_alias'], $producerServiceName); } + + // register alias for argument auto wiring + if (method_exists($this->container, 'registerAliasForArgument')) { + $argName = !str_ends_with(strtolower($key), 'producer') ? sprintf('%sProducer', $key) : $key; + $this->container + ->registerAliasForArgument($producerServiceName, ProducerInterface::class, $argName) + ->setPublic(false); + + $this->container + ->registerAliasForArgument($producerServiceName, $producer['class'], $argName) + ->setPublic(false); + } } } else { foreach ($this->config['producers'] as $key => $producer) { $definition = new Definition('%old_sound_rabbit_mq.fallback.class%'); - $this->container->setDefinition(sprintf('old_sound_rabbit_mq.%s_producer', $key), $definition); + $producerServiceName = sprintf('old_sound_rabbit_mq.%s_producer', $key); + $this->container->setDefinition($producerServiceName, $definition); + + // register alias for argumen auto wiring + if (method_exists($this->container, 'registerAliasForArgument')) { + $argName = !str_ends_with(strtolower($key), 'producer') ? sprintf('%sProducer', $key) : $key; + $this->container + ->registerAliasForArgument($producerServiceName, ProducerInterface::class, $argName) + ->setPublic(false); + } } } } @@ -242,6 +265,18 @@ protected function loadConsumers() $name = sprintf('old_sound_rabbit_mq.%s_consumer', $key); $this->container->setDefinition($name, $definition); $this->addDequeuerAwareCall($consumer['callback'], $name); + + // register alias for argument auto wiring + if (method_exists($this->container, 'registerAliasForArgument')) { + $argName = !str_ends_with(strtolower($key), 'consumer') ? sprintf('%sConsumer', $key) : $key; + $this->container + ->registerAliasForArgument($name, ConsumerInterface::class, $argName) + ->setPublic(false); + + $this->container + ->registerAliasForArgument($name, '%old_sound_rabbit_mq.consumer.class%', $argName) + ->setPublic(false); + } } } diff --git a/README.md b/README.md index b06f068e..f71acb01 100644 --- a/README.md +++ b/README.md @@ -534,6 +534,35 @@ consumers: qos_options: {prefetch_size: 0, prefetch_count: 1, global: false} ``` +### Autowiring producers and consumers ### + +If used with **Symfony 4.2+** bundle declares in container set of aliases for producers and regular consumers. Those are +used for arguments autowiring based on declared type and argument name. This allows you to change previous producer +example to: + +```php +public function indexAction($name, ProducerInteface $uploadPictureProducer) +{ + $msg = array('user_id' => 1235, 'image_path' => '/path/to/new/pic.png'); + $uploadPictureProducer->publish(serialize($msg)); +} +``` + +Name of argument is constructed from producer or consumer name from configuration and suffixed with producer or consumer +word according to type. In contrast to container items naming convention word suffix (producer or consumer) will not be +duplicated if name is already suffixed. `upload_picture` producer key will be changed to `$uploadPictureProducer` +argument name. `upload_picture_producer` producer key would also be aliased to `$uploadPictureProducer` argument name. +It is best to avoid names similar in such manner. + +All producers are aliased to `OldSound\RabbitMqBundle\RabbitMq\ProducerInterface` and producer class option from +configuration. In sandbox mode only ProducerInterface aliases are made. It is highly recommended to use ProducerInterface +class when type hinting arguments for producer injection. + +All consumers are aliased to 'OldSound\RabbitMqBundle\RabbitMq\ConsumerInterface' and '%old_sound_rabbit_mq.consumer.class%' +configuration option value. There is no difference between regular and sandbox mode. It is highly recommended to use +ConsumerInterface when type hinting arguments for client injection. + + ### Callbacks ### Here's an example callback: @@ -807,7 +836,7 @@ binding scenarios might include exchange to exchange bindings via `destination_i ```yaml bindings: - {exchange: foo, destination: bar, routing_key: 'baz.*' } - - {exchange: foo1, destination: foo, routing_key: 'baz.*' destination_is_exchange: true} + - {exchange: foo1, destination: foo, routing_key: 'baz.*', destination_is_exchange: true} ``` The rabbitmq:setup-fabric command will declare exchanges and queues as defined in your producer, consumer diff --git a/Tests/DependencyInjection/OldSoundRabbitMqExtensionTest.php b/Tests/DependencyInjection/OldSoundRabbitMqExtensionTest.php index e6f151bc..5406db03 100644 --- a/Tests/DependencyInjection/OldSoundRabbitMqExtensionTest.php +++ b/Tests/DependencyInjection/OldSoundRabbitMqExtensionTest.php @@ -2,14 +2,16 @@ namespace OldSound\RabbitMqBundle\Tests\DependencyInjection; +use OldSound\RabbitMqBundle\DependencyInjection\OldSoundRabbitMqExtension; +use OldSound\RabbitMqBundle\RabbitMq\ConsumerInterface; +use OldSound\RabbitMqBundle\RabbitMq\ProducerInterface; +use PHPUnit\Framework\TestCase; +use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; -use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; -use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; -use OldSound\RabbitMqBundle\DependencyInjection\OldSoundRabbitMqExtension; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; use Symfony\Component\DependencyInjection\Reference; -use PHPUnit\Framework\TestCase; class OldSoundRabbitMqExtensionTest extends TestCase { @@ -318,6 +320,35 @@ public function testFooProducerDefinition() $this->assertEquals('My\Foo\Producer', $definition->getClass()); } + public function testProducerArgumentAliases() + { + /** @var ContainerBuilder $container */ + $container = $this->getContainer('test.yml'); + + if (!method_exists($container, 'registerAliasForArgument')) { + // don't test if autowiring arguments functionality is not available + return; + } + + // test expected aliases + $expectedAliases = array( + ProducerInterface::class . ' $fooProducer' => 'old_sound_rabbit_mq.foo_producer_producer', + 'My\Foo\Producer $fooProducer' => 'old_sound_rabbit_mq.foo_producer_producer', + ProducerInterface::class . ' $fooProducerAliasedProducer' => 'old_sound_rabbit_mq.foo_producer_aliased_producer', + 'My\Foo\Producer $fooProducerAliasedProducer' => 'old_sound_rabbit_mq.foo_producer_aliased_producer', + ProducerInterface::class . ' $defaultProducer' => 'old_sound_rabbit_mq.default_producer_producer', + '%old_sound_rabbit_mq.producer.class% $defaultProducer' => 'old_sound_rabbit_mq.default_producer_producer', + ); + + foreach($expectedAliases as $id => $target) { + $this->assertTrue($container->hasAlias($id), sprintf('Container should have %s alias for autowiring support.', $id)); + + $alias = $container->getAlias($id); + $this->assertEquals($target, (string)$alias, sprintf('Autowiring for %s should use %s.', $id, $target)); + $this->assertFalse($alias->isPublic(), sprintf('Autowiring alias for %s should be private', $id)); + } + } + /** * @group alias */ @@ -427,6 +458,33 @@ public function testFooConsumerDefinition() $this->assertEquals('%old_sound_rabbit_mq.consumer.class%', $definition->getClass()); } + public function testConsumerArgumentAliases() + { + /** @var ContainerBuilder $container */ + $container = $this->getContainer('test.yml'); + + if (!method_exists($container, 'registerAliasForArgument')) { + // don't test if autowiring arguments functionality is not available + return; + } + + $expectedAliases = array( + ConsumerInterface::class . ' $fooConsumer' => 'old_sound_rabbit_mq.foo_consumer_consumer', + '%old_sound_rabbit_mq.consumer.class% $fooConsumer' => 'old_sound_rabbit_mq.foo_consumer_consumer', + ConsumerInterface::class . ' $defaultConsumer' => 'old_sound_rabbit_mq.default_consumer_consumer', + '%old_sound_rabbit_mq.consumer.class% $defaultConsumer' => 'old_sound_rabbit_mq.default_consumer_consumer', + ConsumerInterface::class . ' $qosTestConsumer' => 'old_sound_rabbit_mq.qos_test_consumer_consumer', + '%old_sound_rabbit_mq.consumer.class% $qosTestConsumer' => 'old_sound_rabbit_mq.qos_test_consumer_consumer' + ); + foreach($expectedAliases as $id => $target) { + $this->assertTrue($container->hasAlias($id), sprintf('Container should have %s alias for autowiring support.', $id)); + + $alias = $container->getAlias($id); + $this->assertEquals($target, (string)$alias, sprintf('Autowiring for %s should use %s.', $id, $target)); + $this->assertFalse($alias->isPublic(), sprintf('Autowiring alias for %s should be private', $id)); + } + } + public function testDefaultConsumerDefinition() { $container = $this->getContainer('test.yml');