From e62fd4920ff48c77e58d06503d4053963efef1fb Mon Sep 17 00:00:00 2001 From: Ruud Kamphuis Date: Mon, 18 Nov 2024 15:57:41 +0100 Subject: [PATCH] Introduce PluginConfigurator This allows for configuring plugins via the normal config. When you have a plugin that does not require config, the old behavior is the easiest. But when you do want custom config per client, it will be cumbersome to create separate services and reference them everywhere. It would be easier to have the same type of configuration as the built-in clients. That's now possible with the PluginConfigurator. Define the plugins as follows: ```yaml 'plugins' => [ [ 'reference' => [ 'id' => CustomPluginConfigurator::class, 'config' => [ 'name' => 'foo', ] ], ], ], ``` The `CustomPluginConfigurator` looks like this: ```php final class CustomPluginConfigurator implements PluginConfigurator { public static function getConfigTreeBuilder() : TreeBuilder { $treeBuilder = new TreeBuilder('custom_plugin'); $rootNode = $treeBuilder->getRootNode(); $rootNode ->children() ->scalarNode('name') ->isRequired() ->cannotBeEmpty() ->end() ->end(); return $treeBuilder; } public function create(array $config) : CustomPlugin { return new CustomPlugin($config['name']); } } ``` On compile time, the config will be evaluated. It will create the service definition by calling the `create` method with the given config. On runtime you will have the CustomPlugin instantiated with the custom config. --- src/DependencyInjection/Configuration.php | 5 +++ src/DependencyInjection/HttplugExtension.php | 15 ++++++++ src/PluginConfigurator.php | 20 +++++++++++ tests/Resources/CustomPlugin.php | 24 +++++++++++++ tests/Resources/CustomPluginConfigurator.php | 32 +++++++++++++++++ tests/Resources/Fixtures/config/full.php | 11 ++++++ tests/Resources/Fixtures/config/full.xml | 7 ++++ tests/Resources/Fixtures/config/full.yml | 5 +++ .../DependencyInjection/ConfigurationTest.php | 12 +++++++ .../HttplugExtensionTest.php | 36 +++++++++++++++++++ 10 files changed, 167 insertions(+) create mode 100644 src/PluginConfigurator.php create mode 100644 tests/Resources/CustomPlugin.php create mode 100644 tests/Resources/CustomPluginConfigurator.php diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 8456049c..c7e2d4c0 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -273,6 +273,11 @@ private function createClientPluginNode(): ArrayNodeDefinition ->isRequired() ->cannotBeEmpty() ->end() + ->arrayNode('config') + ->normalizeKeys(false) + ->prototype('variable') + ->end() + ->end() ->end() ->end() ->arrayNode('add_host') diff --git a/src/DependencyInjection/HttplugExtension.php b/src/DependencyInjection/HttplugExtension.php index 559a21c9..b932ce92 100644 --- a/src/DependencyInjection/HttplugExtension.php +++ b/src/DependencyInjection/HttplugExtension.php @@ -16,12 +16,14 @@ use Http\Client\HttpAsyncClient; use Http\Client\Plugin\Vcr\RecordPlugin; use Http\Client\Plugin\Vcr\ReplayPlugin; +use Http\HttplugBundle\PluginConfigurator; use Http\Message\Authentication\BasicAuth; use Http\Message\Authentication\Bearer; use Http\Message\Authentication\Header; use Http\Message\Authentication\QueryParam; use Http\Message\Authentication\Wsse; use Http\Mock\Client as MockClient; +use LogicException; use Psr\Http\Client\ClientInterface; use Psr\Http\Message\UriInterface; use Symfony\Component\Config\Definition\ConfigurationInterface; @@ -427,6 +429,19 @@ private function configureClient(ContainerBuilder $container, string $clientName switch ($pluginName) { case 'reference': + if ($pluginConfig['config'] !== []) { + if (! is_a($pluginConfig['id'], PluginConfigurator::class, true)) { + throw new LogicException(sprintf('The plugin "%s" is not a valid PluginConfigurator.', $pluginConfig['id'])); + } + + $config = $pluginConfig['id']::getConfigTreeBuilder()->buildTree()->finalize($pluginConfig['config']); + + $definition = new Definition(null, [$config]); + $definition->setFactory([new Reference($pluginConfig['id']), 'create']); + + $plugins[] = $definition; + break; + } $plugins[] = new Reference($pluginConfig['id']); break; case 'authentication': diff --git a/src/PluginConfigurator.php b/src/PluginConfigurator.php new file mode 100644 index 00000000..8d15d983 --- /dev/null +++ b/src/PluginConfigurator.php @@ -0,0 +1,20 @@ + $config + */ + public function create(array $config) : Plugin; +} diff --git a/tests/Resources/CustomPlugin.php b/tests/Resources/CustomPlugin.php new file mode 100644 index 00000000..62d29255 --- /dev/null +++ b/tests/Resources/CustomPlugin.php @@ -0,0 +1,24 @@ +name = $name; + } + + public function handleRequest(RequestInterface $request, callable $next, callable $first) : Promise + { + return $next($request); + } +} diff --git a/tests/Resources/CustomPluginConfigurator.php b/tests/Resources/CustomPluginConfigurator.php new file mode 100644 index 00000000..297642f5 --- /dev/null +++ b/tests/Resources/CustomPluginConfigurator.php @@ -0,0 +1,32 @@ +getRootNode(); + + $rootNode + ->children() + ->scalarNode('name') + ->isRequired() + ->cannotBeEmpty() + ->end() + ->end(); + + return $treeBuilder; + } + + public function create(array $config) : CustomPlugin + { + return new CustomPlugin($config['name']); + } +} diff --git a/tests/Resources/Fixtures/config/full.php b/tests/Resources/Fixtures/config/full.php index b468f177..cbffb4de 100644 --- a/tests/Resources/Fixtures/config/full.php +++ b/tests/Resources/Fixtures/config/full.php @@ -2,6 +2,9 @@ declare(strict_types=1); +use Http\HttplugBundle\Tests\Resources\CustomPlugin; +use Http\HttplugBundle\Tests\Resources\CustomPluginConfigurator; + $container->loadFromExtension('httplug', [ 'default_client_autowiring' => false, 'main_alias' => [ @@ -27,6 +30,14 @@ 'http_methods_client' => true, 'plugins' => [ 'httplug.plugin.redirect', + [ + 'reference' => [ + 'id' => CustomPluginConfigurator::class, + 'config' => [ + 'name' => 'foo', + ] + ], + ], [ 'add_host' => [ 'host' => 'http://localhost', diff --git a/tests/Resources/Fixtures/config/full.xml b/tests/Resources/Fixtures/config/full.xml index d47867d7..edf60c09 100644 --- a/tests/Resources/Fixtures/config/full.xml +++ b/tests/Resources/Fixtures/config/full.xml @@ -22,6 +22,13 @@ httplug.plugin.redirect + + + + foo + + + diff --git a/tests/Resources/Fixtures/config/full.yml b/tests/Resources/Fixtures/config/full.yml index 7e054599..72a89318 100644 --- a/tests/Resources/Fixtures/config/full.yml +++ b/tests/Resources/Fixtures/config/full.yml @@ -21,6 +21,11 @@ httplug: http_methods_client: true plugins: - 'httplug.plugin.redirect' + - + reference: + id: Http\HttplugBundle\Tests\Resources\CustomPluginConfigurator + config: + name: foo - add_host: host: http://localhost diff --git a/tests/Unit/DependencyInjection/ConfigurationTest.php b/tests/Unit/DependencyInjection/ConfigurationTest.php index b7654501..6ff7ebaa 100644 --- a/tests/Unit/DependencyInjection/ConfigurationTest.php +++ b/tests/Unit/DependencyInjection/ConfigurationTest.php @@ -7,6 +7,8 @@ use Http\Adapter\Guzzle7\Client; use Http\HttplugBundle\DependencyInjection\Configuration; use Http\HttplugBundle\DependencyInjection\HttplugExtension; +use Http\HttplugBundle\Tests\Resources\CustomPlugin; +use Http\HttplugBundle\Tests\Resources\CustomPluginConfigurator; use Matthias\SymfonyDependencyInjectionTest\PhpUnit\AbstractExtensionConfigurationTestCase; use Nyholm\Psr7\Factory\Psr17Factory; use Symfony\Component\Config\Definition\ConfigurationInterface; @@ -153,6 +155,16 @@ public function testSupportsAllConfigFormats(): void 'reference' => [ 'enabled' => true, 'id' => 'httplug.plugin.redirect', + 'config' => [], + ], + ], + [ + 'reference' => [ + 'enabled' => true, + 'id' => CustomPluginConfigurator::class, + 'config' => [ + 'name' => 'foo', + ], ], ], [ diff --git a/tests/Unit/DependencyInjection/HttplugExtensionTest.php b/tests/Unit/DependencyInjection/HttplugExtensionTest.php index 8e105771..29704f3e 100644 --- a/tests/Unit/DependencyInjection/HttplugExtensionTest.php +++ b/tests/Unit/DependencyInjection/HttplugExtensionTest.php @@ -7,8 +7,10 @@ use Http\Adapter\Guzzle7\Client; use Http\Client\Plugin\Vcr\Recorder\InMemoryRecorder; use Http\HttplugBundle\DependencyInjection\HttplugExtension; +use Http\HttplugBundle\Tests\Resources\CustomPluginConfigurator; use Matthias\SymfonyDependencyInjectionTest\PhpUnit\AbstractExtensionTestCase; use Psr\Http\Client\ClientInterface; +use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; /** @@ -203,6 +205,40 @@ public function testClientPlugins(): void $this->assertContainerBuilderHasService('httplug.client.mock'); } + public function testPluginConfiguratorConfig(): void + { + $config = [ + 'clients' => [ + 'acme' => [ + 'factory' => 'httplug.factory.curl', + 'plugins' => [ + [ + 'reference' => [ + 'id' => CustomPluginConfigurator::class, + 'config' => [ + 'name' => 'foo', + ] + ], + ], + ], + ], + ], + ]; + + $this->load($config); + + $definition = new Definition(null, [ + ['name' => 'foo'], + ]); + $definition->setFactory([CustomPluginConfigurator::class, 'create']); + + $this->assertContainerBuilderHasService('httplug.client.acme'); + $this->assertContainerBuilderHasServiceDefinitionWithArgument('httplug.client.acme', 1, [ + $definition + ]); + $this->assertContainerBuilderHasService('httplug.client.mock'); + } + public function testNoProfilingWhenNotInDebugMode(): void { $this->setParameter('kernel.debug', false);