From cbb4c8d87f7804e20a1b2372662ed89a1ed328b7 Mon Sep 17 00:00:00 2001 From: Thijs-jan Veldhuizen Date: Mon, 13 Apr 2020 09:33:52 +0200 Subject: [PATCH] Added service aliases to allow easier autowiring (#301) Added service aliases to allow easier autowiring --- .../EightPointsGuzzleExtension.php | 19 +++++++--- src/Resources/doc/autowiring-clients.md | 38 +++++++++++++++++++ .../EightPointsGuzzleExtensionTest.php | 12 ++++++ 3 files changed, 64 insertions(+), 5 deletions(-) diff --git a/src/DependencyInjection/EightPointsGuzzleExtension.php b/src/DependencyInjection/EightPointsGuzzleExtension.php index 94b6105..7cdd11a 100644 --- a/src/DependencyInjection/EightPointsGuzzleExtension.php +++ b/src/DependencyInjection/EightPointsGuzzleExtension.php @@ -4,6 +4,7 @@ use EightPoints\Bundle\GuzzleBundle\Log\Logger; use EightPoints\Bundle\GuzzleBundle\Twig\Extension\DebugExtension; +use GuzzleHttp\ClientInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; use Symfony\Component\DependencyInjection\Reference; @@ -91,13 +92,21 @@ public function load(array $configs, ContainerBuilder $container) // set service name based on client name $serviceName = sprintf('%s.client.%s', $this->getAlias(), $name); $container->setDefinition($serviceName, $client); + + // Allowed only for Symfony 4.2+ + if (method_exists($container, 'registerAliasForArgument')) { + if ('%eight_points_guzzle.http_client.class%' !== $options['class']) { + $container->registerAliasForArgument($serviceName, $options['class'], $name . 'Client'); + } + $container->registerAliasForArgument($serviceName, ClientInterface::class, $name . 'Client'); + } } - $clientsWithLogging = array_filter($config['clients'], function ($options) use ($logging) { + $clientsWithLogging = array_filter($config['clients'], function($options) use ($logging) { return $options['logging'] !== false && $logging !== false; }); - if (count($clientsWithLogging)>0) { + if (count($clientsWithLogging) > 0) { $this->defineTwigDebugExtension($container); $this->defineDataCollector($container, $config['slow_response_time'] / 1000); $this->defineFormatter($container); @@ -125,7 +134,7 @@ protected function createHandler(ContainerBuilder $container, string $clientName $container->setDefinition($eventServiceName, $eventService); // Create the event Dispatch Middleware - $eventExpression = new Expression(sprintf("service('%s').dispatchEvent()", $eventServiceName)); + $eventExpression = new Expression(sprintf("service('%s').dispatchEvent()", $eventServiceName)); $handler = new Definition(HandlerStack::class); $handler->setFactory([HandlerStack::class, 'create']); @@ -173,7 +182,7 @@ protected function createHandler(ContainerBuilder $container, string $clientName */ private function convertLogMode($logMode) : int { - if ($logMode === true){ + if ($logMode === true) { return Logger::LOG_MODE_REQUEST_AND_RESPONSE; } elseif ($logMode === false) { return Logger::LOG_MODE_NONE; @@ -230,7 +239,7 @@ protected function defineLogger(ContainerBuilder $container, int $logMode, strin protected function defineDataCollector(ContainerBuilder $container, float $slowResponseTime) : void { $dataCollectorDefinition = new Definition('%eight_points_guzzle.data_collector.class%'); - $dataCollectorDefinition->addArgument(array_map(function ($loggerId) : Reference { + $dataCollectorDefinition->addArgument(array_map(function($loggerId) : Reference { return new Reference($loggerId); }, array_keys($container->findTaggedServiceIds('eight_points_guzzle.logger')))); diff --git a/src/Resources/doc/autowiring-clients.md b/src/Resources/doc/autowiring-clients.md index edc58db..94be7f2 100644 --- a/src/Resources/doc/autowiring-clients.md +++ b/src/Resources/doc/autowiring-clients.md @@ -4,6 +4,44 @@ Autowiring was introduced in Symfony 3.3 and let's read how [Symfony Documentati > Autowiring allows you to manage services in the container with minimal configuration. It reads the type-hints on your constructor (or other methods) and automatically passes the correct services to each method. Symfony's autowiring is designed to be predictable: if it is not absolutely clear which dependency should be passed, you'll see an actionable exception. +## Symfony >= 4.2 +In Symfony 4.2, it is made possible to [bind services by type and name](https://symfony.com/blog/new-in-symfony-4-2-autowiring-by-type-and-name). This feature makes using Guzzle clients a lot easier. Given the following configuration: + +```yaml +eight_points_guzzle: + clients: + api_payment: + base_url: "http://api.domain1.tld" + api_crm: + class: App\Client\ApiCrmClient + base_url: "http://api.domain2.tld" +``` +The clients can be autowired without further configuration (but mandatory variable names), like this: + +```php +namespace App\Controller; + +use GuzzleHttp\ClientInterface; +use App\Client\ApiCrmClient; + +class FooController extends AbstractController +{ + public function bar(ClientInterface $apiPaymentClient) + { + // Default Client class + } + + public function baz(ApiCrmClient $apiCrmClient) + { + // Custom Client class (must extend GuzzleHttp\Client) + } +} +``` + +Autowiring takes place by the combination of the class name and the variable name, as described in [this blog]. + +## Symfony < 4.2 + Getting in consideration, that Guzzle Bundle creates clients of same class, it becomes obvious that Symfony will not be able to guess what to inject. With some small configurations we can help Symfony to do it. diff --git a/tests/DependencyInjection/EightPointsGuzzleExtensionTest.php b/tests/DependencyInjection/EightPointsGuzzleExtensionTest.php index 8c65b31..f39a470 100644 --- a/tests/DependencyInjection/EightPointsGuzzleExtensionTest.php +++ b/tests/DependencyInjection/EightPointsGuzzleExtensionTest.php @@ -34,6 +34,13 @@ public function testGuzzleExtension() $this->assertInstanceOf(Client::class, $testApi); $this->assertEquals(new Uri('//api.domain.tld/path'), $testApi->getConfig('base_uri')); + if (method_exists($container, 'registerAliasForArgument')) { + $this->assertTrue($container->hasAlias(ClientInterface::class . ' $testApiClient')); + $this->assertSame($testApi, $container->get(ClientInterface::class . ' $testApiClient')); + + $this->assertFalse($container->hasAlias('%eight_points_guzzle.http_client.class% $testApiClient')); + } + // test Services $this->assertTrue($container->hasDefinition('eight_points_guzzle.middleware.event_dispatch.test_api')); @@ -42,6 +49,11 @@ public function testGuzzleExtension() $definition = $container->getDefinition('eight_points_guzzle.client.test_api_with_custom_class'); $this->assertSame(CustomClient::class, $definition->getClass()); + if (method_exists($container, 'registerAliasForArgument')) { + $testApi = $container->get('eight_points_guzzle.client.test_api_with_custom_class'); + $this->assertTrue($container->hasAlias(CustomClient::class . ' $testApiWithCustomClassClient')); + $this->assertSame($testApi, $container->get(ClientInterface::class . ' $testApiWithCustomClassClient')); + } // test Client with custom handler $this->assertTrue($container->hasDefinition('eight_points_guzzle.client.test_api_with_custom_handler'));