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

HttplugExtension   F

Complexity

Total Complexity 74

Size/Duplication

Total Lines 483
Duplicated Lines 12.42 %

Coupling/Cohesion

Components 1
Dependencies 9

Test Coverage

Coverage 85.37%

Importance

Changes 0
Metric Value
wmc 74
lcom 1
cbo 9
dl 60
loc 483
ccs 210
cts 246
cp 0.8537
rs 2.48
c 0
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
C load() 0 57 10
B configureClients() 0 46 10
A configurePlugins() 0 16 4
F configurePluginByName() 18 111 20
B configureAuthentication() 0 46 7
C configureClient() 24 79 12
A createUri() 0 9 1
B configureAutoDiscoveryClients() 18 30 7
A getConfiguration() 0 4 1
A configurePlugin() 0 13 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like HttplugExtension often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use HttplugExtension, and based on these observations, apply Extract Interface, too.

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