Completed
Push — master ( be11c5...35d281 )
by David
06:05 queued 11s
created

HttplugExtension::configureAutoDiscoveryClients()   B

Complexity

Conditions 7
Paths 18

Size

Total Lines 30

Duplication

Lines 18
Ratio 60 %

Code Coverage

Tests 19
CRAP Score 7

Importance

Changes 0
Metric Value
dl 18
loc 30
ccs 19
cts 19
cp 1
rs 8.5066
c 0
b 0
f 0
cc 7
nc 18
nop 2
crap 7
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 29
    public function load(array $configs, ContainerBuilder $container)
42
    {
43 29
        $configuration = $this->getConfiguration($configs, $container);
44 29
        $config = $this->processConfiguration($configuration, $configs);
45
46 29
        $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
47
48 29
        $loader->load('services.xml');
49 29
        $loader->load('plugins.xml');
50 29
        if (\class_exists(MockClient::class)) {
51 29
            $loader->load('mock-client.xml');
52
        }
53
54
        // Register default services
55 29
        foreach ($config['classes'] as $service => $class) {
56 29
            if (!empty($class)) {
57 1
                $container->register(sprintf('httplug.%s.default', $service), $class);
58
            }
59
        }
60
61
        // Set main aliases
62 29
        foreach ($config['main_alias'] as $type => $id) {
63 29
            $container->setAlias(sprintf('httplug.%s', $type), new Alias($id, true));
64
        }
65
66
        // Configure toolbar
67 29
        $profilingEnabled = $this->isConfigEnabled($container, $config['profiling']);
68 29
        if ($profilingEnabled) {
69 26
            $loader->load('data-collector.xml');
70
71 26
            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 26
                ->getDefinition('httplug.formatter.full_http_message')
81 26
                ->addArgument($config['profiling']['captured_body_length'])
82
            ;
83
84 26
            if (!class_exists(TwigEnvironment::class) && !class_exists(\Twig_Environment::class)) {
85
                $container->removeDefinition('httplug.collector.twig.http_message');
86
            }
87
        }
88
89 29
        $this->configureClients($container, $config);
90 29
        $this->configurePlugins($container, $config['plugins']); // must be after clients, as clients.X.plugins might use plugins as templates that will be removed
91 29
        $this->configureAutoDiscoveryClients($container, $config);
92
93 29
        if (!$config['default_client_autowiring']) {
94 1
            $container->removeAlias(HttpAsyncClient::class);
95 1
            $container->removeAlias(HttpClient::class);
96
        }
97 29
    }
98
99
    /**
100
     * Configure client services.
101
     *
102
     * @param ContainerBuilder $container
103
     * @param array            $config
104
     */
105 29
    private function configureClients(ContainerBuilder $container, array $config)
106
    {
107 29
        $first = null;
108 29
        $clients = [];
109
110 29
        foreach ($config['clients'] as $name => $arguments) {
111 19
            if (null === $first) {
112
                // Save the name of the first configured client.
113 19
                $first = $name;
114
            }
115
116 19
            $this->configureClient($container, $name, $arguments);
117 19
            $clients[] = $name;
118
        }
119
120
        // If we have clients configured
121 29
        if (null !== $first) {
122
            // If we do not have a client named 'default'
123 19
            if (!array_key_exists('default', $config['clients'])) {
124 19
                $serviceId = 'httplug.client.'.$first;
125
                // Alias the first client to httplug.client.default
126 19
                $container->setAlias('httplug.client.default', $serviceId);
127 19
                $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 19
            if ($config['clients'][$default]['flexible_client']) {
135 2
                $container->setAlias(FlexibleHttpClient::class, $serviceId.'.flexible');
136
            }
137 19
            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 19
            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 29
    }
151
152
    /**
153
     * Configure all Httplug plugins or remove their service definition.
154
     *
155
     * @param ContainerBuilder $container
156
     * @param array            $config
157
     */
158 29
    private function configurePlugins(ContainerBuilder $container, array $config)
159
    {
160 29
        if (!empty($config['authentication'])) {
161
            $this->configureAuthentication($container, $config['authentication']);
162
        }
163 29
        unset($config['authentication']);
164
165 29
        foreach ($config as $name => $pluginConfig) {
166 29
            $pluginId = 'httplug.plugin.'.$name;
167
168 29
            if ($this->isConfigEnabled($container, $pluginConfig)) {
169 29
                $def = $container->getDefinition($pluginId);
170 29
                $this->configurePluginByName($name, $def, $pluginConfig, $container, $pluginId);
171
            }
172
        }
173 29
    }
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 29
    private function configurePluginByName($name, Definition $definition, array $config, ContainerBuilder $container, $serviceId)
183
    {
184 29
        switch ($name) {
185 29
            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 29
            case 'cookie':
199
                $definition->replaceArgument(0, new Reference($config['cookie_jar']));
200
201
                break;
202
203 29
            case 'decoder':
204 29
                $definition->addArgument([
205 29
                    'use_content_encoding' => $config['use_content_encoding'],
206
                ]);
207
208 29
                break;
209
210 29
            case 'history':
211
                $definition->replaceArgument(0, new Reference($config['journal']));
212
213
                break;
214
215 29
            case 'logger':
216 29
                $definition->replaceArgument(0, new Reference($config['logger']));
217 29
                if (!empty($config['formatter'])) {
218
                    $definition->replaceArgument(1, new Reference($config['formatter']));
219
                }
220
221 29
                break;
222
223 29
            case 'redirect':
224 29
                $definition->addArgument([
225 29
                    'preserve_header' => $config['preserve_header'],
226 29
                    'use_default_for_multiple' => $config['use_default_for_multiple'],
227
                ]);
228
229 29
                break;
230
231 29
            case 'retry':
232 29
                $definition->addArgument([
233 29
                    'retries' => $config['retry'],
234
                ]);
235
236 29
                break;
237
238 29
            case 'stopwatch':
239 29
                $definition->replaceArgument(0, new Reference($config['stopwatch']));
240
241 29
                break;
242
243
            /* client specific plugins */
244
245 7 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 2
            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 2 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 2
            case 'content_type':
273 2
                unset($config['enabled']);
274 2
                $definition->replaceArgument(0, $config);
275
276 2
                break;
277
278 1
            case 'header_append':
279 1
            case 'header_defaults':
280 1
            case 'header_set':
281 1
            case 'header_remove':
282 1
                $definition->replaceArgument(0, $config['headers']);
283
284 1
                break;
285
286 1
            case 'query_defaults':
287 1
                $definition->replaceArgument(0, $config['parameters']);
288
289 1
                break;
290
291
            default:
292
                throw new \InvalidArgumentException(sprintf('Internal exception: Plugin %s is not handled', $name));
293
        }
294 29
    }
295
296
    /**
297
     * @param ContainerBuilder $container
298
     * @param array            $config
299
     *
300
     * @return array list of service ids for the authentication plugins
301
     */
302 6
    private function configureAuthentication(ContainerBuilder $container, array $config, $servicePrefix = 'httplug.plugin.authentication')
303
    {
304 6
        $pluginServices = [];
305
306 6
        foreach ($config as $name => $values) {
307 6
            $authServiceKey = sprintf($servicePrefix.'.%s.auth', $name);
308 6
            switch ($values['type']) {
309 6
                case 'bearer':
310
                    $container->register($authServiceKey, Bearer::class)
311
                        ->addArgument($values['token']);
312
313
                    break;
314 6
                case 'basic':
315 6
                    $container->register($authServiceKey, BasicAuth::class)
316 6
                        ->addArgument($values['username'])
317 6
                        ->addArgument($values['password']);
318
319 6
                    break;
320
                case 'wsse':
321
                    $container->register($authServiceKey, Wsse::class)
322
                        ->addArgument($values['username'])
323
                        ->addArgument($values['password']);
324
325
                    break;
326
                case 'query_param':
327
                    $container->register($authServiceKey, QueryParam::class)
328
                        ->addArgument($values['params']);
329
330
                    break;
331
                case 'service':
332
                    $authServiceKey = $values['service'];
333
334
                    break;
335
                default:
336
                    throw new \LogicException(sprintf('Unknown authentication type: "%s"', $values['type']));
337
            }
338
339 6
            $pluginServiceKey = $servicePrefix.'.'.$name;
340 6
            $container->register($pluginServiceKey, AuthenticationPlugin::class)
341 6
                ->addArgument(new Reference($authServiceKey))
342
            ;
343 6
            $pluginServices[] = $pluginServiceKey;
344
        }
345
346 6
        return $pluginServices;
347
    }
348
349
    /**
350
     * @param ContainerBuilder $container
351
     * @param string           $clientName
352
     * @param array            $arguments
353
     */
354 19
    private function configureClient(ContainerBuilder $container, $clientName, array $arguments)
355
    {
356 19
        $serviceId = 'httplug.client.'.$clientName;
357
358 19
        $plugins = [];
359 19
        foreach ($arguments['plugins'] as $plugin) {
360 10
            $pluginName = key($plugin);
361 10
            $pluginConfig = current($plugin);
362 10
            if ('reference' === $pluginName) {
363 9
                $plugins[] = $pluginConfig['id'];
364 7
            } elseif ('authentication' === $pluginName) {
365 6
                $plugins = array_merge($plugins, $this->configureAuthentication($container, $pluginConfig, $serviceId.'.authentication'));
366
            } else {
367 7
                $plugins[] = $this->configurePlugin($container, $serviceId, $pluginName, $pluginConfig);
368
            }
369
        }
370
371 19
        if (empty($arguments['service'])) {
372
            $container
373 18
                ->register($serviceId.'.client', HttpClient::class)
374 18
                ->setFactory([new Reference($arguments['factory']), 'createClient'])
375 18
                ->addArgument($arguments['config'])
376 18
                ->setPublic(false);
377
        } else {
378
            $container
379 2
                ->setAlias($serviceId.'.client', new Alias($arguments['service'], false));
380
        }
381
382
        $definition = $container
383 19
            ->register($serviceId, PluginClient::class)
384 19
            ->setFactory([new Reference(PluginClientFactory::class), 'createClient'])
385 19
            ->addArgument(new Reference($serviceId.'.client'))
386 19
            ->addArgument(
387 19
                array_map(
388 19
                    function ($id) {
389 10
                        return new Reference($id);
390 19
                    },
391
                    $plugins
392
                )
393
            )
394 19
            ->addArgument([
395 19
                'client_name' => $clientName,
396
            ])
397
        ;
398
399 19
        if (is_bool($arguments['public'])) {
400 5
            $definition->setPublic($arguments['public']);
401
        }
402
403
        /*
404
         * Decorate the client with clients from client-common
405
         */
406 19 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...
407
            $container
408 2
                ->register($serviceId.'.flexible', FlexibleHttpClient::class)
409 2
                ->addArgument(new Reference($serviceId.'.flexible.inner'))
410 2
                ->setPublic($arguments['public'] ? true : false)
411 2
                ->setDecoratedService($serviceId)
412
            ;
413
        }
414
415 19 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...
416
            $container
417 2
                ->register($serviceId.'.http_methods', HttpMethodsClient::class)
418 2
                ->setArguments([new Reference($serviceId.'.http_methods.inner'), new Reference('httplug.message_factory')])
419 2
                ->setPublic($arguments['public'] ? true : false)
420 2
                ->setDecoratedService($serviceId)
421
            ;
422
        }
423
424 19 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...
425
            $container
426 2
                ->register($serviceId.'.batch_client', BatchClient::class)
427 2
                ->setArguments([new Reference($serviceId.'.batch_client.inner')])
428 2
                ->setPublic($arguments['public'] ? true : false)
429 2
                ->setDecoratedService($serviceId)
430
            ;
431
        }
432 19
    }
433
434
    /**
435
     * Create a URI object with the default URI factory.
436
     *
437
     * @param ContainerBuilder $container
438
     * @param string           $serviceId Name of the private service to create
439
     * @param string           $uri       String representation of the URI
440
     */
441 6
    private function createUri(ContainerBuilder $container, $serviceId, $uri)
442
    {
443
        $container
444 6
            ->register($serviceId, UriInterface::class)
445 6
            ->setPublic(false)
446 6
            ->setFactory([new Reference('httplug.uri_factory'), 'createUri'])
447 6
            ->addArgument($uri)
448
        ;
449 6
    }
450
451
    /**
452
     * Make the user can select what client is used for auto discovery. If none is provided, a service will be created
453
     * by finding a client using auto discovery.
454
     *
455
     * @param ContainerBuilder $container
456
     * @param array            $config
457
     */
458 29
    private function configureAutoDiscoveryClients(ContainerBuilder $container, array $config)
459
    {
460 29
        $httpClient = $config['discovery']['client'];
461 29 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...
462 2
            $container->removeDefinition('httplug.auto_discovery.auto_discovered_client');
463 2
            $container->removeDefinition('httplug.collector.auto_discovered_client');
464
465 2
            if (!empty($httpClient)) {
466 1
                $container->setAlias('httplug.auto_discovery.auto_discovered_client', $httpClient);
467 1
                $container->getAlias('httplug.auto_discovery.auto_discovered_client')->setPublic(false);
468
            }
469
        }
470
471 29
        $asyncHttpClient = $config['discovery']['async_client'];
472 29 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...
473 26
            $container->removeDefinition('httplug.auto_discovery.auto_discovered_async');
474 26
            $container->removeDefinition('httplug.collector.auto_discovered_async');
475
476 26
            if (!empty($asyncHttpClient)) {
477 1
                $container->setAlias('httplug.auto_discovery.auto_discovered_async', $asyncHttpClient);
478 1
                $container->getAlias('httplug.auto_discovery.auto_discovered_async')->setPublic(false);
479
            }
480
        }
481
482 29
        if (null === $httpClient && null === $asyncHttpClient) {
483 1
            $container->removeDefinition('httplug.strategy');
484
485 1
            return;
486
        }
487 28
    }
488
489
    /**
490
     * {@inheritdoc}
491
     */
492 29
    public function getConfiguration(array $config, ContainerBuilder $container)
493
    {
494 29
        return new Configuration($container->getParameter('kernel.debug'));
495
    }
496
497
    /**
498
     * Configure a plugin using the parent definition from plugins.xml.
499
     *
500
     * @param ContainerBuilder $container
501
     * @param string           $serviceId
502
     * @param string           $pluginName
503
     * @param array            $pluginConfig
504
     *
505
     * @return string configured service id
506
     */
507 7
    private function configurePlugin(ContainerBuilder $container, $serviceId, $pluginName, array $pluginConfig)
508
    {
509 7
        $pluginServiceId = $serviceId.'.plugin.'.$pluginName;
510
511 7
        $definition = class_exists(ChildDefinition::class)
512 7
            ? new ChildDefinition('httplug.plugin.'.$pluginName)
513 7
            : new DefinitionDecorator('httplug.plugin.'.$pluginName);
514
515 7
        $this->configurePluginByName($pluginName, $definition, $pluginConfig, $container, $pluginServiceId);
516 7
        $container->setDefinition($pluginServiceId, $definition);
517
518 7
        return $pluginServiceId;
519
    }
520
}
521