Skip to content

Commit 451c065

Browse files
authored
Introduce PluginConfigurator (#477)
* Store references in plugins array * 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' => [ [ 'configurator' => [ '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.
1 parent fb077ec commit 451c065

File tree

11 files changed

+194
-10
lines changed

11 files changed

+194
-10
lines changed

Diff for: CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ The change log describes what is "Added", "Removed", "Changed" or "Fixed" betwee
44

55
# Version 2
66

7+
# 2.1.0
8+
9+
- Added [PluginConfigurator](https://docs.php-http.org/en/latest/integrations/symfony-bundle.html#configure-a-custom-plugin)
10+
711
# 2.0.0 - 2024-09-16
812

913
- Increased min PHP version to 8.1

Diff for: src/DependencyInjection/Configuration.php

+16
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,22 @@ private function createClientPluginNode(): ArrayNodeDefinition
275275
->end()
276276
->end()
277277
->end()
278+
->arrayNode('configurator')
279+
->canBeEnabled()
280+
->info('Configure a plugin with a configurator')
281+
->children()
282+
->scalarNode('id')
283+
->info('Service id of a plugin configurator')
284+
->isRequired()
285+
->cannotBeEmpty()
286+
->end()
287+
->arrayNode('config')
288+
->normalizeKeys(false)
289+
->prototype('variable')
290+
->end()
291+
->end()
292+
->end()
293+
->end()
278294
->arrayNode('add_host')
279295
->canBeEnabled()
280296
->addDefaultsIfNotSet()

Diff for: src/DependencyInjection/HttplugExtension.php

+30-10
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use Http\Client\HttpAsyncClient;
1717
use Http\Client\Plugin\Vcr\RecordPlugin;
1818
use Http\Client\Plugin\Vcr\ReplayPlugin;
19+
use Http\HttplugBundle\PluginConfigurator;
1920
use Http\Message\Authentication\BasicAuth;
2021
use Http\Message\Authentication\Bearer;
2122
use Http\Message\Authentication\Header;
@@ -427,17 +428,41 @@ private function configureClient(ContainerBuilder $container, string $clientName
427428

428429
switch ($pluginName) {
429430
case 'reference':
430-
$plugins[] = $pluginConfig['id'];
431+
$plugins[] = new Reference($pluginConfig['id']);
432+
break;
433+
case 'configurator':
434+
if (!is_a($pluginConfig['id'], PluginConfigurator::class, true)) {
435+
throw new \LogicException(sprintf('The plugin "%s" is not a valid PluginConfigurator.', $pluginConfig['id']));
436+
}
437+
438+
$config = $pluginConfig['id']::getConfigTreeBuilder()->buildTree()->finalize($pluginConfig['config']);
439+
440+
$definition = new Definition(null, [$config]);
441+
$definition->setFactory([new Reference($pluginConfig['id']), 'create']);
442+
443+
$plugins[] = $definition;
431444
break;
432445
case 'authentication':
433-
$plugins = array_merge($plugins, $this->configureAuthentication($container, $pluginConfig, $serviceId.'.authentication'));
446+
$plugins = array_merge(
447+
$plugins,
448+
array_map(
449+
fn ($id) => new Reference($id),
450+
$this->configureAuthentication($container, $pluginConfig, $serviceId.'.authentication')
451+
)
452+
);
434453
break;
435454
case 'vcr':
436455
$this->useVcrPlugin = true;
437-
$plugins = array_merge($plugins, $this->configureVcrPlugin($container, $pluginConfig, $serviceId.'.vcr'));
456+
$plugins = array_merge(
457+
$plugins,
458+
array_map(
459+
fn ($id) => new Reference($id),
460+
$this->configureVcrPlugin($container, $pluginConfig, $serviceId.'.vcr'),
461+
),
462+
);
438463
break;
439464
default:
440-
$plugins[] = $this->configurePlugin($container, $serviceId, $pluginName, $pluginConfig);
465+
$plugins[] = new Reference($this->configurePlugin($container, $serviceId, $pluginName, $pluginConfig));
441466
}
442467
}
443468

@@ -456,12 +481,7 @@ private function configureClient(ContainerBuilder $container, string $clientName
456481
->register($serviceId, PluginClient::class)
457482
->setFactory([new Reference(PluginClientFactory::class), 'createClient'])
458483
->addArgument(new Reference($serviceId.'.client'))
459-
->addArgument(
460-
array_map(
461-
fn ($id) => new Reference($id),
462-
$plugins
463-
)
464-
)
484+
->addArgument($plugins)
465485
->addArgument([
466486
'client_name' => $clientName,
467487
])

Diff for: src/PluginConfigurator.php

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Http\HttplugBundle;
6+
7+
use Http\Client\Common\Plugin;
8+
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
9+
10+
interface PluginConfigurator
11+
{
12+
public static function getConfigTreeBuilder(): TreeBuilder;
13+
14+
/**
15+
* Creates the plugin with the given config.
16+
*
17+
* @param array<mixed> $config
18+
*/
19+
public function create(array $config): Plugin;
20+
}

Diff for: tests/Resources/CustomPlugin.php

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Http\HttplugBundle\Tests\Resources;
6+
7+
use Http\Client\Common\Plugin;
8+
use Http\Promise\Promise;
9+
use Psr\Http\Message\RequestInterface;
10+
11+
final class CustomPlugin implements Plugin
12+
{
13+
private string $name;
14+
15+
public function __construct(string $name)
16+
{
17+
$this->name = $name;
18+
}
19+
20+
public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise
21+
{
22+
return $next($request);
23+
}
24+
}

Diff for: tests/Resources/CustomPluginConfigurator.php

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Http\HttplugBundle\Tests\Resources;
6+
7+
use Http\HttplugBundle\PluginConfigurator;
8+
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
9+
10+
final class CustomPluginConfigurator implements PluginConfigurator
11+
{
12+
public static function getConfigTreeBuilder(): TreeBuilder
13+
{
14+
$treeBuilder = new TreeBuilder('custom_plugin');
15+
$rootNode = $treeBuilder->getRootNode();
16+
17+
$rootNode
18+
->children()
19+
->scalarNode('name')
20+
->isRequired()
21+
->cannotBeEmpty()
22+
->end()
23+
->end();
24+
25+
return $treeBuilder;
26+
}
27+
28+
public function create(array $config): CustomPlugin
29+
{
30+
return new CustomPlugin($config['name']);
31+
}
32+
}

Diff for: tests/Resources/Fixtures/config/full.php

+10
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
declare(strict_types=1);
44

5+
use Http\HttplugBundle\Tests\Resources\CustomPluginConfigurator;
6+
57
$container->loadFromExtension('httplug', [
68
'default_client_autowiring' => false,
79
'main_alias' => [
@@ -27,6 +29,14 @@
2729
'http_methods_client' => true,
2830
'plugins' => [
2931
'httplug.plugin.redirect',
32+
[
33+
'configurator' => [
34+
'id' => CustomPluginConfigurator::class,
35+
'config' => [
36+
'name' => 'foo',
37+
],
38+
],
39+
],
3040
[
3141
'add_host' => [
3242
'host' => 'http://localhost',

Diff for: tests/Resources/Fixtures/config/full.xml

+7
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@
2222
</classes>
2323
<client name="test" factory="httplug.factory.guzzle7" http-methods-client="true">
2424
<plugin>httplug.plugin.redirect</plugin>
25+
<plugin>
26+
<configurator id="Http\HttplugBundle\Tests\Resources\CustomPluginConfigurator">
27+
<config>
28+
<name>foo</name>
29+
</config>
30+
</configurator>
31+
</plugin>
2532
<plugin>
2633
<add-host host="http://localhost"/>
2734
</plugin>

Diff for: tests/Resources/Fixtures/config/full.yml

+5
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ httplug:
2121
http_methods_client: true
2222
plugins:
2323
- 'httplug.plugin.redirect'
24+
-
25+
configurator:
26+
id: Http\HttplugBundle\Tests\Resources\CustomPluginConfigurator
27+
config:
28+
name: foo
2429
-
2530
add_host:
2631
host: http://localhost

Diff for: tests/Unit/DependencyInjection/ConfigurationTest.php

+10
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Http\Adapter\Guzzle7\Client;
88
use Http\HttplugBundle\DependencyInjection\Configuration;
99
use Http\HttplugBundle\DependencyInjection\HttplugExtension;
10+
use Http\HttplugBundle\Tests\Resources\CustomPluginConfigurator;
1011
use Matthias\SymfonyDependencyInjectionTest\PhpUnit\AbstractExtensionConfigurationTestCase;
1112
use Nyholm\Psr7\Factory\Psr17Factory;
1213
use Symfony\Component\Config\Definition\ConfigurationInterface;
@@ -155,6 +156,15 @@ public function testSupportsAllConfigFormats(): void
155156
'id' => 'httplug.plugin.redirect',
156157
],
157158
],
159+
[
160+
'configurator' => [
161+
'enabled' => true,
162+
'id' => CustomPluginConfigurator::class,
163+
'config' => [
164+
'name' => 'foo',
165+
],
166+
],
167+
],
158168
[
159169
'add_host' => [
160170
'enabled' => true,

Diff for: tests/Unit/DependencyInjection/HttplugExtensionTest.php

+36
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@
77
use Http\Adapter\Guzzle7\Client;
88
use Http\Client\Plugin\Vcr\Recorder\InMemoryRecorder;
99
use Http\HttplugBundle\DependencyInjection\HttplugExtension;
10+
use Http\HttplugBundle\Tests\Resources\CustomPluginConfigurator;
1011
use Matthias\SymfonyDependencyInjectionTest\PhpUnit\AbstractExtensionTestCase;
1112
use Psr\Http\Client\ClientInterface;
13+
use Symfony\Component\DependencyInjection\Definition;
1214
use Symfony\Component\DependencyInjection\Reference;
1315

1416
/**
@@ -203,6 +205,40 @@ public function testClientPlugins(): void
203205
$this->assertContainerBuilderHasService('httplug.client.mock');
204206
}
205207

208+
public function testPluginConfiguratorConfig(): void
209+
{
210+
$config = [
211+
'clients' => [
212+
'acme' => [
213+
'factory' => 'httplug.factory.curl',
214+
'plugins' => [
215+
[
216+
'configurator' => [
217+
'id' => CustomPluginConfigurator::class,
218+
'config' => [
219+
'name' => 'foo',
220+
],
221+
],
222+
],
223+
],
224+
],
225+
],
226+
];
227+
228+
$this->load($config);
229+
230+
$definition = new Definition(null, [
231+
['name' => 'foo'],
232+
]);
233+
$definition->setFactory([CustomPluginConfigurator::class, 'create']);
234+
235+
$this->assertContainerBuilderHasService('httplug.client.acme');
236+
$this->assertContainerBuilderHasServiceDefinitionWithArgument('httplug.client.acme', 1, [
237+
$definition,
238+
]);
239+
$this->assertContainerBuilderHasService('httplug.client.mock');
240+
}
241+
206242
public function testNoProfilingWhenNotInDebugMode(): void
207243
{
208244
$this->setParameter('kernel.debug', false);

0 commit comments

Comments
 (0)