Completed
Push — master ( f1c022...ec01a6 )
by Tobias
05:55
created

HttplugExtension   F

Complexity

Total Complexity 60

Size/Duplication

Total Lines 429
Duplicated Lines 13.99 %

Coupling/Cohesion

Components 1
Dependencies 9

Test Coverage

Coverage 79.73%

Importance

Changes 0
Metric Value
wmc 60
lcom 1
cbo 9
dl 60
loc 429
ccs 177
cts 222
cp 0.7973
rs 3.6
c 0
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
B load() 0 48 7
A configureClients() 0 24 5
A configurePlugins() 0 16 4
D configurePluginByName() 18 92 18
B configureAuthentication() 0 46 7
B configureClient() 24 75 8
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\FlexibleHttpClient;
7
use Http\Client\Common\HttpMethodsClient;
8
use Http\Client\Common\Plugin\AuthenticationPlugin;
9
use Http\Client\Common\PluginClient;
10
use Http\Client\Common\PluginClientFactory;
11
use Http\Client\HttpClient;
12
use Http\Message\Authentication\BasicAuth;
13
use Http\Message\Authentication\Bearer;
14
use Http\Message\Authentication\QueryParam;
15
use Http\Message\Authentication\Wsse;
16
use Http\Mock\Client as MockClient;
17
use Psr\Http\Message\UriInterface;
18
use Symfony\Component\Config\FileLocator;
19
use Symfony\Component\DependencyInjection\Alias;
20
use Symfony\Component\DependencyInjection\ChildDefinition;
21
use Symfony\Component\DependencyInjection\ContainerBuilder;
22
use Symfony\Component\DependencyInjection\Definition;
23
use Symfony\Component\DependencyInjection\DefinitionDecorator;
24
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
25
use Symfony\Component\DependencyInjection\Reference;
26
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
27
28
/**
29
 * @author David Buchmann <[email protected]>
30
 * @author Tobias Nyholm <[email protected]>
31
 */
32
class HttplugExtension extends Extension
33
{
34
    /**
35
     * {@inheritdoc}
36
     */
37 14
    public function load(array $configs, ContainerBuilder $container)
38
    {
39 14
        $configuration = $this->getConfiguration($configs, $container);
40 14
        $config = $this->processConfiguration($configuration, $configs);
41
42 14
        $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
43
44 14
        $loader->load('services.xml');
45 14
        $loader->load('plugins.xml');
46 14
        if (\class_exists(MockClient::class)) {
47 14
            $loader->load('mock-client.xml');
48
        }
49
50
        // Register default services
51 14
        foreach ($config['classes'] as $service => $class) {
52 14
            if (!empty($class)) {
53 14
                $container->register(sprintf('httplug.%s.default', $service), $class);
54
            }
55
        }
56
57
        // Set main aliases
58 14
        foreach ($config['main_alias'] as $type => $id) {
59 14
            $container->setAlias(sprintf('httplug.%s', $type), new Alias($id, true));
60
        }
61
62
        // Configure toolbar
63 14
        $profilingEnabled = $this->isConfigEnabled($container, $config['profiling']);
64 14
        if ($profilingEnabled) {
65 13
            $loader->load('data-collector.xml');
66
67 13
            if (!empty($config['profiling']['formatter'])) {
68
                // Add custom formatter
69
                $container
70 1
                    ->getDefinition('httplug.collector.formatter')
71 1
                    ->replaceArgument(0, new Reference($config['profiling']['formatter']))
72
                ;
73
            }
74
75
            $container
76 13
                ->getDefinition('httplug.formatter.full_http_message')
77 13
                ->addArgument($config['profiling']['captured_body_length'])
78
            ;
79
        }
80
81 14
        $this->configureClients($container, $config);
82 14
        $this->configurePlugins($container, $config['plugins']); // must be after clients, as clients.X.plugins might use plugins as templates that will be removed
83 14
        $this->configureAutoDiscoveryClients($container, $config);
84 14
    }
85
86
    /**
87
     * Configure client services.
88
     *
89
     * @param ContainerBuilder $container
90
     * @param array            $config
91
     */
92 14
    private function configureClients(ContainerBuilder $container, array $config)
93
    {
94 14
        $first = null;
95 14
        $clients = [];
96
97 14
        foreach ($config['clients'] as $name => $arguments) {
98 7
            if (null === $first) {
99
                // Save the name of the first configured client.
100 7
                $first = $name;
101
            }
102
103 7
            $this->configureClient($container, $name, $arguments);
104 7
            $clients[] = $name;
105
        }
106
107
        // If we have clients configured
108 14
        if (null !== $first) {
109
            // If we do not have a client named 'default'
110 7
            if (!isset($config['clients']['default'])) {
111
                // Alias the first client to httplug.client.default
112 7
                $container->setAlias('httplug.client.default', 'httplug.client.'.$first);
113
            }
114
        }
115 14
    }
116
117
    /**
118
     * Configure all Httplug plugins or remove their service definition.
119
     *
120
     * @param ContainerBuilder $container
121
     * @param array            $config
122
     */
123 14
    private function configurePlugins(ContainerBuilder $container, array $config)
124
    {
125 14
        if (!empty($config['authentication'])) {
126
            $this->configureAuthentication($container, $config['authentication']);
127
        }
128 14
        unset($config['authentication']);
129
130 14
        foreach ($config as $name => $pluginConfig) {
131 14
            $pluginId = 'httplug.plugin.'.$name;
132
133 14
            if ($this->isConfigEnabled($container, $pluginConfig)) {
134 14
                $def = $container->getDefinition($pluginId);
135 14
                $this->configurePluginByName($name, $def, $pluginConfig, $container, $pluginId);
136
            }
137
        }
138 14
    }
139
140
    /**
141
     * @param string           $name
142
     * @param Definition       $definition
143
     * @param array            $config
144
     * @param ContainerBuilder $container  In case we need to add additional services for this plugin
145
     * @param string           $serviceId  Service id of the plugin, in case we need to add additional services for this plugin.
146
     */
147 14
    private function configurePluginByName($name, Definition $definition, array $config, ContainerBuilder $container, $serviceId)
148
    {
149 14
        switch ($name) {
150 14
            case 'cache':
151 2
                $options = $config['config'];
152 2
                if (!empty($options['cache_key_generator'])) {
153 1
                    $options['cache_key_generator'] = new Reference($options['cache_key_generator']);
154
                }
155
156
                $definition
157 2
                    ->replaceArgument(0, new Reference($config['cache_pool']))
158 2
                    ->replaceArgument(1, new Reference($config['stream_factory']))
159 2
                    ->replaceArgument(2, $options);
160
161 2
                break;
162 14
            case 'cookie':
163
                $definition->replaceArgument(0, new Reference($config['cookie_jar']));
164
165
                break;
166 14
            case 'decoder':
167 14
                $definition->addArgument([
168 14
                    'use_content_encoding' => $config['use_content_encoding'],
169
                ]);
170
171 14
                break;
172 14
            case 'history':
173
                $definition->replaceArgument(0, new Reference($config['journal']));
174
175
                break;
176 14
            case 'logger':
177 14
                $definition->replaceArgument(0, new Reference($config['logger']));
178 14
                if (!empty($config['formatter'])) {
179
                    $definition->replaceArgument(1, new Reference($config['formatter']));
180
                }
181
182 14
                break;
183 14
            case 'redirect':
184 14
                $definition->addArgument([
185 14
                    'preserve_header' => $config['preserve_header'],
186 14
                    'use_default_for_multiple' => $config['use_default_for_multiple'],
187
                ]);
188
189 14
                break;
190 14
            case 'retry':
191 14
                $definition->addArgument([
192 14
                    'retries' => $config['retry'],
193
                ]);
194
195 14
                break;
196 14
            case 'stopwatch':
197 14
                $definition->replaceArgument(0, new Reference($config['stopwatch']));
198
199 14
                break;
200
201
            /* client specific plugins */
202
203 5 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...
204 5
                $hostUriService = $serviceId.'.host_uri';
205 5
                $this->createUri($container, $hostUriService, $config['host']);
206 5
                $definition->replaceArgument(0, new Reference($hostUriService));
207 5
                $definition->replaceArgument(1, [
208 5
                    'replace' => $config['replace'],
209
                ]);
210
211 5
                break;
212 1
            case 'add_path':
213
                $pathUriService = $serviceId.'.path_uri';
214
                $this->createUri($container, $pathUriService, $config['path']);
215
                $definition->replaceArgument(0, new Reference($pathUriService));
216
217
                break;
218 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...
219
                $baseUriService = $serviceId.'.base_uri';
220
                $this->createUri($container, $baseUriService, $config['uri']);
221
                $definition->replaceArgument(0, new Reference($baseUriService));
222
                $definition->replaceArgument(1, [
223
                    'replace' => $config['replace'],
224
                ]);
225
226
                break;
227 1
            case 'header_append':
228 1
            case 'header_defaults':
229 1
            case 'header_set':
230 1
            case 'header_remove':
231 1
                $definition->replaceArgument(0, $config['headers']);
232
233 1
                break;
234
235
            default:
236
                throw new \InvalidArgumentException(sprintf('Internal exception: Plugin %s is not handled', $name));
237
        }
238 14
    }
239
240
    /**
241
     * @param ContainerBuilder $container
242
     * @param array            $config
243
     *
244
     * @return array List of service ids for the authentication plugins.
245
     */
246 5
    private function configureAuthentication(ContainerBuilder $container, array $config, $servicePrefix = 'httplug.plugin.authentication')
247
    {
248 5
        $pluginServices = [];
249
250 5
        foreach ($config as $name => $values) {
251 5
            $authServiceKey = sprintf($servicePrefix.'.%s.auth', $name);
252 5
            switch ($values['type']) {
253 5
                case 'bearer':
254
                    $container->register($authServiceKey, Bearer::class)
255
                        ->addArgument($values['token']);
256
257
                    break;
258 5
                case 'basic':
259 5
                    $container->register($authServiceKey, BasicAuth::class)
260 5
                        ->addArgument($values['username'])
261 5
                        ->addArgument($values['password']);
262
263 5
                    break;
264
                case 'wsse':
265
                    $container->register($authServiceKey, Wsse::class)
266
                        ->addArgument($values['username'])
267
                        ->addArgument($values['password']);
268
269
                    break;
270
                case 'query_param':
271
                    $container->register($authServiceKey, QueryParam::class)
272
                        ->addArgument($values['params']);
273
274
                    break;
275
                case 'service':
276
                    $authServiceKey = $values['service'];
277
278
                    break;
279
                default:
280
                    throw new \LogicException(sprintf('Unknown authentication type: "%s"', $values['type']));
281
            }
282
283 5
            $pluginServiceKey = $servicePrefix.'.'.$name;
284 5
            $container->register($pluginServiceKey, AuthenticationPlugin::class)
285 5
                ->addArgument(new Reference($authServiceKey))
286
            ;
287 5
            $pluginServices[] = $pluginServiceKey;
288
        }
289
290 5
        return $pluginServices;
291
    }
292
293
    /**
294
     * @param ContainerBuilder $container
295
     * @param string           $clientName
296
     * @param array            $arguments
297
     */
298 7
    private function configureClient(ContainerBuilder $container, $clientName, array $arguments)
299
    {
300 7
        $serviceId = 'httplug.client.'.$clientName;
301
302 7
        $plugins = [];
303 7
        foreach ($arguments['plugins'] as $plugin) {
304 6
            $pluginName = key($plugin);
305 6
            $pluginConfig = current($plugin);
306 6
            if ('reference' === $pluginName) {
307 6
                $plugins[] = $pluginConfig['id'];
308 5
            } elseif ('authentication' === $pluginName) {
309 5
                $plugins = array_merge($plugins, $this->configureAuthentication($container, $pluginConfig, $serviceId.'.authentication'));
310
            } else {
311 6
                $plugins[] = $this->configurePlugin($container, $serviceId, $pluginName, $pluginConfig);
312
            }
313
        }
314
315 7
        if (empty($arguments['service'])) {
316
            $container
317 6
                ->register($serviceId.'.client', HttpClient::class)
318 6
                ->setFactory([new Reference($arguments['factory']), 'createClient'])
319 6
                ->addArgument($arguments['config'])
320 6
                ->setPublic(false);
321
        } else {
322
            $container
323 1
                ->setAlias($serviceId.'.client', new Alias($arguments['service'], false));
324
        }
325
326
        $container
327 7
            ->register($serviceId, PluginClient::class)
328 7
            ->setFactory([new Reference(PluginClientFactory::class), 'createClient'])
329 7
            ->addArgument(new Reference($serviceId.'.client'))
330 7
            ->addArgument(
331 7
                array_map(
332 7
                    function ($id) {
333 6
                        return new Reference($id);
334 7
                    },
335 7
                    $plugins
336
                )
337
            )
338 7
            ->addArgument([
339 7
                'client_name' => $clientName,
340
            ])
341
        ;
342
343
        /*
344
         * Decorate the client with clients from client-common
345
         */
346 7 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...
347
            $container
348
                ->register($serviceId.'.flexible', FlexibleHttpClient::class)
349
                ->addArgument(new Reference($serviceId.'.flexible.inner'))
350
                ->setPublic(false)
351
                ->setDecoratedService($serviceId)
352
            ;
353
        }
354
355 7 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...
356
            $container
357
                ->register($serviceId.'.http_methods', HttpMethodsClient::class)
358
                ->setArguments([new Reference($serviceId.'.http_methods.inner'), new Reference('httplug.message_factory')])
359
                ->setPublic(false)
360
                ->setDecoratedService($serviceId)
361
            ;
362
        }
363
364 7 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...
365
            $container
366
                ->register($serviceId.'.batch_client', BatchClient::class)
367
                ->setArguments([new Reference($serviceId.'.batch_client.inner')])
368
                ->setPublic(false)
369
                ->setDecoratedService($serviceId)
370
            ;
371
        }
372 7
    }
373
374
    /**
375
     * Create a URI object with the default URI factory.
376
     *
377
     * @param ContainerBuilder $container
378
     * @param string           $serviceId Name of the private service to create
379
     * @param string           $uri       String representation of the URI
380
     */
381 5
    private function createUri(ContainerBuilder $container, $serviceId, $uri)
382
    {
383
        $container
384 5
            ->register($serviceId, UriInterface::class)
385 5
            ->setPublic(false)
386 5
            ->setFactory([new Reference('httplug.uri_factory'), 'createUri'])
387 5
            ->addArgument($uri)
388
        ;
389 5
    }
390
391
    /**
392
     * Make the user can select what client is used for auto discovery. If none is provided, a service will be created
393
     * by finding a client using auto discovery.
394
     *
395
     * @param ContainerBuilder $container
396
     * @param array            $config
397
     */
398 14
    private function configureAutoDiscoveryClients(ContainerBuilder $container, array $config)
399
    {
400 14
        $httpClient = $config['discovery']['client'];
401 14 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...
402 2
            $container->removeDefinition('httplug.auto_discovery.auto_discovered_client');
403 2
            $container->removeDefinition('httplug.collector.auto_discovered_client');
404
405 2
            if (!empty($httpClient)) {
406 1
                $container->setAlias('httplug.auto_discovery.auto_discovered_client', $httpClient);
407 1
                $container->getAlias('httplug.auto_discovery.auto_discovered_client')->setPublic(false);
408
            }
409
        }
410
411 14
        $asyncHttpClient = $config['discovery']['async_client'];
412 14 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...
413 12
            $container->removeDefinition('httplug.auto_discovery.auto_discovered_async');
414 12
            $container->removeDefinition('httplug.collector.auto_discovered_async');
415
416 12
            if (!empty($asyncHttpClient)) {
417 1
                $container->setAlias('httplug.auto_discovery.auto_discovered_async', $asyncHttpClient);
418 1
                $container->getAlias('httplug.auto_discovery.auto_discovered_async')->setPublic(false);
419
            }
420
        }
421
422 14
        if (null === $httpClient && null === $asyncHttpClient) {
423 1
            $container->removeDefinition('httplug.strategy');
424
425 1
            return;
426
        }
427 13
    }
428
429
    /**
430
     * {@inheritdoc}
431
     */
432 14
    public function getConfiguration(array $config, ContainerBuilder $container)
433
    {
434 14
        return new Configuration($container->getParameter('kernel.debug'));
435
    }
436
437
    /**
438
     * Configure a plugin using the parent definition from plugins.xml.
439
     *
440
     * @param ContainerBuilder $container
441
     * @param string           $serviceId
442
     * @param string           $pluginName
443
     * @param array            $pluginConfig
444
     *
445
     * @return string configured service id
446
     */
447 5
    private function configurePlugin(ContainerBuilder $container, $serviceId, $pluginName, array $pluginConfig)
448
    {
449 5
        $pluginServiceId = $serviceId.'.plugin.'.$pluginName;
450
451 5
        $definition = class_exists(ChildDefinition::class)
452 5
            ? new ChildDefinition('httplug.plugin.'.$pluginName)
453 5
            : new DefinitionDecorator('httplug.plugin.'.$pluginName);
454
455 5
        $this->configurePluginByName($pluginName, $definition, $pluginConfig, $container, $pluginServiceId);
456 5
        $container->setDefinition($pluginServiceId, $definition);
457
458 5
        return $pluginServiceId;
459
    }
460
}
461