Completed
Push — master ( 8bcd1b...1fddc9 )
by David
05:22
created

HttplugExtension::configurePlugins()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 4.016

Importance

Changes 0
Metric Value
dl 0
loc 16
ccs 9
cts 10
cp 0.9
rs 9.7333
c 0
b 0
f 0
cc 4
nc 6
nop 2
crap 4.016
1
<?php
2
3
namespace Http\HttplugBundle\DependencyInjection;
4
5
use Http\Client\Common\BatchClient;
6
use Http\Client\Common\BatchClientInterface;
7
use Http\Client\Common\FlexibleHttpClient;
8
use Http\Client\Common\HttpMethodsClient;
9
use Http\Client\Common\HttpMethodsClientInterface;
10
use Http\Client\Common\Plugin\AuthenticationPlugin;
11
use Http\Client\Common\PluginClient;
12
use Http\Client\Common\PluginClientFactory;
13
use Http\Client\HttpAsyncClient;
14
use Http\Client\HttpClient;
15
use Http\Message\Authentication\BasicAuth;
16
use Http\Message\Authentication\Bearer;
17
use Http\Message\Authentication\QueryParam;
18
use Http\Message\Authentication\Wsse;
19
use Http\Mock\Client as MockClient;
20
use Psr\Http\Message\UriInterface;
21
use Symfony\Component\Config\FileLocator;
22
use Symfony\Component\DependencyInjection\Alias;
23
use Symfony\Component\DependencyInjection\ChildDefinition;
24
use Symfony\Component\DependencyInjection\ContainerBuilder;
25
use Symfony\Component\DependencyInjection\Definition;
26
use Symfony\Component\DependencyInjection\DefinitionDecorator;
27
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
28
use Symfony\Component\DependencyInjection\Reference;
29
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
30
use Twig\Environment as TwigEnvironment;
31
32
/**
33
 * @author David Buchmann <[email protected]>
34
 * @author Tobias Nyholm <[email protected]>
35
 */
36
class HttplugExtension extends Extension
37
{
38
    /**
39
     * {@inheritdoc}
40
     */
41 28
    public function load(array $configs, ContainerBuilder $container)
42
    {
43 28
        $configuration = $this->getConfiguration($configs, $container);
44 28
        $config = $this->processConfiguration($configuration, $configs);
45
46 28
        $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
47
48 28
        $loader->load('services.xml');
49 28
        $loader->load('plugins.xml');
50 28
        if (\class_exists(MockClient::class)) {
51 28
            $loader->load('mock-client.xml');
52
        }
53
54
        // Register default services
55 28
        foreach ($config['classes'] as $service => $class) {
56 28
            if (!empty($class)) {
57 1
                $container->register(sprintf('httplug.%s.default', $service), $class);
58
            }
59
        }
60
61
        // Set main aliases
62 28
        foreach ($config['main_alias'] as $type => $id) {
63 28
            $container->setAlias(sprintf('httplug.%s', $type), new Alias($id, true));
64
        }
65
66
        // Configure toolbar
67 28
        $profilingEnabled = $this->isConfigEnabled($container, $config['profiling']);
68 28
        if ($profilingEnabled) {
69 25
            $loader->load('data-collector.xml');
70
71 25
            if (!empty($config['profiling']['formatter'])) {
72
                // Add custom formatter
73
                $container
74 1
                    ->getDefinition('httplug.collector.formatter')
75 1
                    ->replaceArgument(0, new Reference($config['profiling']['formatter']))
76
                ;
77
            }
78
79
            $container
80 25
                ->getDefinition('httplug.formatter.full_http_message')
81 25
                ->addArgument($config['profiling']['captured_body_length'])
82
            ;
83
84 25
            if (!class_exists(TwigEnvironment::class) && !class_exists(\Twig_Environment::class)) {
85
                $container->removeDefinition('httplug.collector.twig.http_message');
86
            }
87
        }
88
89 28
        $this->configureClients($container, $config);
90 28
        $this->configurePlugins($container, $config['plugins']); // must be after clients, as clients.X.plugins might use plugins as templates that will be removed
91 28
        $this->configureAutoDiscoveryClients($container, $config);
92
93 28
        if (!$config['default_client_autowiring']) {
94 1
            $container->removeAlias(HttpAsyncClient::class);
95 1
            $container->removeAlias(HttpClient::class);
96
        }
97 28
    }
98
99
    /**
100
     * Configure client services.
101
     *
102
     * @param ContainerBuilder $container
103
     * @param array            $config
104
     */
105 28
    private function configureClients(ContainerBuilder $container, array $config)
106
    {
107 28
        $first = null;
108 28
        $clients = [];
109
110 28
        foreach ($config['clients'] as $name => $arguments) {
111 18
            if (null === $first) {
112
                // Save the name of the first configured client.
113 18
                $first = $name;
114
            }
115
116 18
            $this->configureClient($container, $name, $arguments);
117 18
            $clients[] = $name;
118
        }
119
120
        // If we have clients configured
121 28
        if (null !== $first) {
122
            // If we do not have a client named 'default'
123 18
            if (!array_key_exists('default', $config['clients'])) {
124 18
                $serviceId = 'httplug.client.'.$first;
125
                // Alias the first client to httplug.client.default
126 18
                $container->setAlias('httplug.client.default', $serviceId);
127 18
                $default = $first;
128
            } else {
129
                $default = 'default';
130
                $serviceId = 'httplug.client.'.$default;
131
            }
132
133
            // Autowiring alias for special clients, if they are enabled on the default client
134 18
            if ($config['clients'][$default]['flexible_client']) {
135 2
                $container->setAlias(FlexibleHttpClient::class, $serviceId.'.flexible');
136
            }
137 18
            if ($config['clients'][$default]['http_methods_client']) {
138 2
                if (\interface_exists(HttpMethodsClientInterface::class)) {
139
                    // support for client-common 1.9
140 2
                    $container->setAlias(HttpMethodsClientInterface::class, $serviceId.'.http_methods');
141
                }
142
            }
143 18
            if ($config['clients'][$default]['batch_client']) {
144 2
                if (\interface_exists(BatchClientInterface::class)) {
145
                    // support for client-common 1.9
146 2
                    $container->setAlias(BatchClientInterface::class, $serviceId.'.batch_client');
147
                }
148
            }
149
        }
150 28
    }
151
152
    /**
153
     * Configure all Httplug plugins or remove their service definition.
154
     *
155
     * @param ContainerBuilder $container
156
     * @param array            $config
157
     */
158 28
    private function configurePlugins(ContainerBuilder $container, array $config)
159
    {
160 28
        if (!empty($config['authentication'])) {
161
            $this->configureAuthentication($container, $config['authentication']);
162
        }
163 28
        unset($config['authentication']);
164
165 28
        foreach ($config as $name => $pluginConfig) {
166 28
            $pluginId = 'httplug.plugin.'.$name;
167
168 28
            if ($this->isConfigEnabled($container, $pluginConfig)) {
169 28
                $def = $container->getDefinition($pluginId);
170 28
                $this->configurePluginByName($name, $def, $pluginConfig, $container, $pluginId);
171
            }
172
        }
173 28
    }
174
175
    /**
176
     * @param string           $name
177
     * @param Definition       $definition
178
     * @param array            $config
179
     * @param ContainerBuilder $container  In case we need to add additional services for this plugin
180
     * @param string           $serviceId  service id of the plugin, in case we need to add additional services for this plugin
181
     */
182 28
    private function configurePluginByName($name, Definition $definition, array $config, ContainerBuilder $container, $serviceId)
183
    {
184 28
        switch ($name) {
185 28
            case 'cache':
186 2
                $options = $config['config'];
187 2
                if (!empty($options['cache_key_generator'])) {
188 1
                    $options['cache_key_generator'] = new Reference($options['cache_key_generator']);
189
                }
190
191
                $definition
192 2
                    ->replaceArgument(0, new Reference($config['cache_pool']))
193 2
                    ->replaceArgument(1, new Reference($config['stream_factory']))
194 2
                    ->replaceArgument(2, $options);
195
196 2
                break;
197
198 28
            case 'cookie':
199
                $definition->replaceArgument(0, new Reference($config['cookie_jar']));
200
201
                break;
202
203 28
            case 'decoder':
204 28
                $definition->addArgument([
205 28
                    'use_content_encoding' => $config['use_content_encoding'],
206
                ]);
207
208 28
                break;
209
210 28
            case 'history':
211
                $definition->replaceArgument(0, new Reference($config['journal']));
212
213
                break;
214
215 28
            case 'logger':
216 28
                $definition->replaceArgument(0, new Reference($config['logger']));
217 28
                if (!empty($config['formatter'])) {
218
                    $definition->replaceArgument(1, new Reference($config['formatter']));
219
                }
220
221 28
                break;
222
223 28
            case 'redirect':
224 28
                $definition->addArgument([
225 28
                    'preserve_header' => $config['preserve_header'],
226 28
                    'use_default_for_multiple' => $config['use_default_for_multiple'],
227
                ]);
228
229 28
                break;
230
231 28
            case 'retry':
232 28
                $definition->addArgument([
233 28
                    'retries' => $config['retry'],
234
                ]);
235
236 28
                break;
237
238 28
            case 'stopwatch':
239 28
                $definition->replaceArgument(0, new Reference($config['stopwatch']));
240
241 28
                break;
242
243
            /* client specific plugins */
244
245 6 View Code Duplication
            case 'add_host':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
246 6
                $hostUriService = $serviceId.'.host_uri';
247 6
                $this->createUri($container, $hostUriService, $config['host']);
248 6
                $definition->replaceArgument(0, new Reference($hostUriService));
249 6
                $definition->replaceArgument(1, [
250 6
                    'replace' => $config['replace'],
251
                ]);
252
253 6
                break;
254
255 1
            case 'add_path':
256
                $pathUriService = $serviceId.'.path_uri';
257
                $this->createUri($container, $pathUriService, $config['path']);
258
                $definition->replaceArgument(0, new Reference($pathUriService));
259
260
                break;
261
262 1 View Code Duplication
            case 'base_uri':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
263
                $baseUriService = $serviceId.'.base_uri';
264
                $this->createUri($container, $baseUriService, $config['uri']);
265
                $definition->replaceArgument(0, new Reference($baseUriService));
266
                $definition->replaceArgument(1, [
267
                    'replace' => $config['replace'],
268
                ]);
269
270
                break;
271
272 1
            case 'content_type':
273 1
                $definition->replaceArgument(0, $config);
274 1
                break;
275
276 1
            case 'header_append':
277 1
            case 'header_defaults':
278 1
            case 'header_set':
279 1
            case 'header_remove':
280 1
                $definition->replaceArgument(0, $config['headers']);
281
282 1
                break;
283
284 1
            case 'query_defaults':
285 1
                $definition->replaceArgument(0, $config['parameters']);
286
287 1
                break;
288
289
            default:
290
                throw new \InvalidArgumentException(sprintf('Internal exception: Plugin %s is not handled', $name));
291
        }
292 28
    }
293
294
    /**
295
     * @param ContainerBuilder $container
296
     * @param array            $config
297
     *
298
     * @return array list of service ids for the authentication plugins
299
     */
300 6
    private function configureAuthentication(ContainerBuilder $container, array $config, $servicePrefix = 'httplug.plugin.authentication')
301
    {
302 6
        $pluginServices = [];
303
304 6
        foreach ($config as $name => $values) {
305 6
            $authServiceKey = sprintf($servicePrefix.'.%s.auth', $name);
306 6
            switch ($values['type']) {
307 6
                case 'bearer':
308
                    $container->register($authServiceKey, Bearer::class)
309
                        ->addArgument($values['token']);
310
311
                    break;
312 6
                case 'basic':
313 6
                    $container->register($authServiceKey, BasicAuth::class)
314 6
                        ->addArgument($values['username'])
315 6
                        ->addArgument($values['password']);
316
317 6
                    break;
318
                case 'wsse':
319
                    $container->register($authServiceKey, Wsse::class)
320
                        ->addArgument($values['username'])
321
                        ->addArgument($values['password']);
322
323
                    break;
324
                case 'query_param':
325
                    $container->register($authServiceKey, QueryParam::class)
326
                        ->addArgument($values['params']);
327
328
                    break;
329
                case 'service':
330
                    $authServiceKey = $values['service'];
331
332
                    break;
333
                default:
334
                    throw new \LogicException(sprintf('Unknown authentication type: "%s"', $values['type']));
335
            }
336
337 6
            $pluginServiceKey = $servicePrefix.'.'.$name;
338 6
            $container->register($pluginServiceKey, AuthenticationPlugin::class)
339 6
                ->addArgument(new Reference($authServiceKey))
340
            ;
341 6
            $pluginServices[] = $pluginServiceKey;
342
        }
343
344 6
        return $pluginServices;
345
    }
346
347
    /**
348
     * @param ContainerBuilder $container
349
     * @param string           $clientName
350
     * @param array            $arguments
351
     */
352 18
    private function configureClient(ContainerBuilder $container, $clientName, array $arguments)
353
    {
354 18
        $serviceId = 'httplug.client.'.$clientName;
355
356 18
        $plugins = [];
357 18
        foreach ($arguments['plugins'] as $plugin) {
358 9
            $pluginName = key($plugin);
359 9
            $pluginConfig = current($plugin);
360 9
            if ('reference' === $pluginName) {
361 9
                $plugins[] = $pluginConfig['id'];
362 6
            } elseif ('authentication' === $pluginName) {
363 6
                $plugins = array_merge($plugins, $this->configureAuthentication($container, $pluginConfig, $serviceId.'.authentication'));
364
            } else {
365 6
                $plugins[] = $this->configurePlugin($container, $serviceId, $pluginName, $pluginConfig);
366
            }
367
        }
368
369 18
        if (empty($arguments['service'])) {
370
            $container
371 17
                ->register($serviceId.'.client', HttpClient::class)
372 17
                ->setFactory([new Reference($arguments['factory']), 'createClient'])
373 17
                ->addArgument($arguments['config'])
374 17
                ->setPublic(false);
375
        } else {
376
            $container
377 2
                ->setAlias($serviceId.'.client', new Alias($arguments['service'], false));
378
        }
379
380
        $definition = $container
381 18
            ->register($serviceId, PluginClient::class)
382 18
            ->setFactory([new Reference(PluginClientFactory::class), 'createClient'])
383 18
            ->addArgument(new Reference($serviceId.'.client'))
384 18
            ->addArgument(
385 18
                array_map(
386 18
                    function ($id) {
387 9
                        return new Reference($id);
388 18
                    },
389
                    $plugins
390
                )
391
            )
392 18
            ->addArgument([
393 18
                'client_name' => $clientName,
394
            ])
395
        ;
396
397 18
        if (is_bool($arguments['public'])) {
398 5
            $definition->setPublic($arguments['public']);
399
        }
400
401
        /*
402
         * Decorate the client with clients from client-common
403
         */
404 18 View Code Duplication
        if ($arguments['flexible_client']) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
405
            $container
406 2
                ->register($serviceId.'.flexible', FlexibleHttpClient::class)
407 2
                ->addArgument(new Reference($serviceId.'.flexible.inner'))
408 2
                ->setPublic($arguments['public'] ? true : false)
409 2
                ->setDecoratedService($serviceId)
410
            ;
411
        }
412
413 18 View Code Duplication
        if ($arguments['http_methods_client']) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
414
            $container
415 2
                ->register($serviceId.'.http_methods', HttpMethodsClient::class)
416 2
                ->setArguments([new Reference($serviceId.'.http_methods.inner'), new Reference('httplug.message_factory')])
417 2
                ->setPublic($arguments['public'] ? true : false)
418 2
                ->setDecoratedService($serviceId)
419
            ;
420
        }
421
422 18 View Code Duplication
        if ($arguments['batch_client']) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
423
            $container
424 2
                ->register($serviceId.'.batch_client', BatchClient::class)
425 2
                ->setArguments([new Reference($serviceId.'.batch_client.inner')])
426 2
                ->setPublic($arguments['public'] ? true : false)
427 2
                ->setDecoratedService($serviceId)
428
            ;
429
        }
430 18
    }
431
432
    /**
433
     * Create a URI object with the default URI factory.
434
     *
435
     * @param ContainerBuilder $container
436
     * @param string           $serviceId Name of the private service to create
437
     * @param string           $uri       String representation of the URI
438
     */
439 6
    private function createUri(ContainerBuilder $container, $serviceId, $uri)
440
    {
441
        $container
442 6
            ->register($serviceId, UriInterface::class)
443 6
            ->setPublic(false)
444 6
            ->setFactory([new Reference('httplug.uri_factory'), 'createUri'])
445 6
            ->addArgument($uri)
446
        ;
447 6
    }
448
449
    /**
450
     * Make the user can select what client is used for auto discovery. If none is provided, a service will be created
451
     * by finding a client using auto discovery.
452
     *
453
     * @param ContainerBuilder $container
454
     * @param array            $config
455
     */
456 28
    private function configureAutoDiscoveryClients(ContainerBuilder $container, array $config)
457
    {
458 28
        $httpClient = $config['discovery']['client'];
459 28 View Code Duplication
        if ('auto' !== $httpClient) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
460 2
            $container->removeDefinition('httplug.auto_discovery.auto_discovered_client');
461 2
            $container->removeDefinition('httplug.collector.auto_discovered_client');
462
463 2
            if (!empty($httpClient)) {
464 1
                $container->setAlias('httplug.auto_discovery.auto_discovered_client', $httpClient);
465 1
                $container->getAlias('httplug.auto_discovery.auto_discovered_client')->setPublic(false);
466
            }
467
        }
468
469 28
        $asyncHttpClient = $config['discovery']['async_client'];
470 28 View Code Duplication
        if ('auto' !== $asyncHttpClient) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
471 25
            $container->removeDefinition('httplug.auto_discovery.auto_discovered_async');
472 25
            $container->removeDefinition('httplug.collector.auto_discovered_async');
473
474 25
            if (!empty($asyncHttpClient)) {
475 1
                $container->setAlias('httplug.auto_discovery.auto_discovered_async', $asyncHttpClient);
476 1
                $container->getAlias('httplug.auto_discovery.auto_discovered_async')->setPublic(false);
477
            }
478
        }
479
480 28
        if (null === $httpClient && null === $asyncHttpClient) {
481 1
            $container->removeDefinition('httplug.strategy');
482
483 1
            return;
484
        }
485 27
    }
486
487
    /**
488
     * {@inheritdoc}
489
     */
490 28
    public function getConfiguration(array $config, ContainerBuilder $container)
491
    {
492 28
        return new Configuration($container->getParameter('kernel.debug'));
493
    }
494
495
    /**
496
     * Configure a plugin using the parent definition from plugins.xml.
497
     *
498
     * @param ContainerBuilder $container
499
     * @param string           $serviceId
500
     * @param string           $pluginName
501
     * @param array            $pluginConfig
502
     *
503
     * @return string configured service id
504
     */
505 6
    private function configurePlugin(ContainerBuilder $container, $serviceId, $pluginName, array $pluginConfig)
506
    {
507 6
        $pluginServiceId = $serviceId.'.plugin.'.$pluginName;
508
509 6
        $definition = class_exists(ChildDefinition::class)
510 6
            ? new ChildDefinition('httplug.plugin.'.$pluginName)
511 6
            : new DefinitionDecorator('httplug.plugin.'.$pluginName);
512
513 6
        $this->configurePluginByName($pluginName, $definition, $pluginConfig, $container, $pluginServiceId);
514 6
        $container->setDefinition($pluginServiceId, $definition);
515
516 6
        return $pluginServiceId;
517
    }
518
}
519