Completed
Push — master ( f718e5...8b7cb0 )
by Tobias
05:26
created

HttplugExtension   F

Complexity

Total Complexity 71

Size/Duplication

Total Lines 463
Duplicated Lines 12.96 %

Coupling/Cohesion

Components 1
Dependencies 9

Test Coverage

Coverage 84.52%

Importance

Changes 0
Metric Value
wmc 71
lcom 1
cbo 9
dl 60
loc 463
ccs 202
cts 239
cp 0.8452
rs 2.7199
c 0
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
B load() 0 52 8
B configureClients() 0 45 10
A configurePlugins() 0 16 4
F configurePluginByName() 18 97 19
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\HttpClient;
14
use Http\Message\Authentication\BasicAuth;
15
use Http\Message\Authentication\Bearer;
16
use Http\Message\Authentication\QueryParam;
17
use Http\Message\Authentication\Wsse;
18
use Http\Mock\Client as MockClient;
19
use Psr\Http\Message\UriInterface;
20
use Symfony\Component\Config\FileLocator;
21
use Symfony\Component\DependencyInjection\Alias;
22
use Symfony\Component\DependencyInjection\ChildDefinition;
23
use Symfony\Component\DependencyInjection\ContainerBuilder;
24
use Symfony\Component\DependencyInjection\Definition;
25
use Symfony\Component\DependencyInjection\DefinitionDecorator;
26
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
27
use Symfony\Component\DependencyInjection\Reference;
28
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
29
30
/**
31
 * @author David Buchmann <[email protected]>
32
 * @author Tobias Nyholm <[email protected]>
33
 */
34
class HttplugExtension extends Extension
35
{
36
    /**
37
     * {@inheritdoc}
38
     */
39 25
    public function load(array $configs, ContainerBuilder $container)
40
    {
41 25
        $configuration = $this->getConfiguration($configs, $container);
42 25
        $config = $this->processConfiguration($configuration, $configs);
43
44 25
        $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
45
46 25
        $loader->load('services.xml');
47 25
        $loader->load('plugins.xml');
48 25
        if (\class_exists(MockClient::class)) {
49 25
            $loader->load('mock-client.xml');
50
        }
51
52
        // Register default services
53 25
        foreach ($config['classes'] as $service => $class) {
54 25
            if (!empty($class)) {
55 25
                $container->register(sprintf('httplug.%s.default', $service), $class);
56
            }
57
        }
58
59
        // Set main aliases
60 25
        foreach ($config['main_alias'] as $type => $id) {
61 25
            $container->setAlias(sprintf('httplug.%s', $type), new Alias($id, true));
62
        }
63
64
        // Configure toolbar
65 25
        $profilingEnabled = $this->isConfigEnabled($container, $config['profiling']);
66 25
        if ($profilingEnabled) {
67 22
            $loader->load('data-collector.xml');
68
69 22
            if (!empty($config['profiling']['formatter'])) {
70
                // Add custom formatter
71
                $container
72 1
                    ->getDefinition('httplug.collector.formatter')
73 1
                    ->replaceArgument(0, new Reference($config['profiling']['formatter']))
74
                ;
75
            }
76
77
            $container
78 22
                ->getDefinition('httplug.formatter.full_http_message')
79 22
                ->addArgument($config['profiling']['captured_body_length'])
80
            ;
81
82 22
            if (!class_exists(\Twig_Environment::class)) {
83
                $container->removeDefinition('httplug.collector.twig.http_message');
84
            }
85
        }
86
87 25
        $this->configureClients($container, $config);
88 25
        $this->configurePlugins($container, $config['plugins']); // must be after clients, as clients.X.plugins might use plugins as templates that will be removed
89 25
        $this->configureAutoDiscoveryClients($container, $config);
90 25
    }
91
92
    /**
93
     * Configure client services.
94
     *
95
     * @param ContainerBuilder $container
96
     * @param array            $config
97
     */
98 25
    private function configureClients(ContainerBuilder $container, array $config)
99
    {
100 25
        $first = null;
101 25
        $clients = [];
102
103 25
        foreach ($config['clients'] as $name => $arguments) {
104 17
            if (null === $first) {
105
                // Save the name of the first configured client.
106 17
                $first = $name;
107
            }
108
109 17
            $this->configureClient($container, $name, $arguments);
110 17
            $clients[] = $name;
111
        }
112
113
        // If we have clients configured
114 25
        if (null !== $first) {
115
            // If we do not have a client named 'default'
116 17
            if (!array_key_exists('default', $config['clients'])) {
117 17
                $serviceId = 'httplug.client.'.$first;
118
                // Alias the first client to httplug.client.default
119 17
                $container->setAlias('httplug.client.default', $serviceId);
120 17
                $default = $first;
121
            } else {
122
                $default = 'default';
123
            }
124
125
            // Autowiring alias for special clients, if they are enabled on the default client
126 17
            if ($config['clients'][$default]['flexible_client']) {
127 2
                $container->setAlias(FlexibleHttpClient::class, $serviceId.'.flexible');
0 ignored issues
show
Bug introduced by
The variable $serviceId does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
128
            }
129 17
            if ($config['clients'][$default]['http_methods_client']) {
130 2
                if (\interface_exists(HttpMethodsClientInterface::class)) {
131
                    // support for client-common 1.9
132
                    $container->setAlias(HttpMethodsClientInterface::class, $serviceId.'.http_methods');
133
                }
134
            }
135 17
            if ($config['clients'][$default]['batch_client']) {
136 2
                if (\interface_exists(BatchClientInterface::class)) {
137
                    // support for client-common 1.9
138
                    $container->setAlias(BatchClientInterface::class, $serviceId.'.batch_client');
139
                }
140
            }
141
        }
142 25
    }
143
144
    /**
145
     * Configure all Httplug plugins or remove their service definition.
146
     *
147
     * @param ContainerBuilder $container
148
     * @param array            $config
149
     */
150 25
    private function configurePlugins(ContainerBuilder $container, array $config)
151
    {
152 25
        if (!empty($config['authentication'])) {
153
            $this->configureAuthentication($container, $config['authentication']);
154
        }
155 25
        unset($config['authentication']);
156
157 25
        foreach ($config as $name => $pluginConfig) {
158 25
            $pluginId = 'httplug.plugin.'.$name;
159
160 25
            if ($this->isConfigEnabled($container, $pluginConfig)) {
161 25
                $def = $container->getDefinition($pluginId);
162 25
                $this->configurePluginByName($name, $def, $pluginConfig, $container, $pluginId);
163
            }
164
        }
165 25
    }
166
167
    /**
168
     * @param string           $name
169
     * @param Definition       $definition
170
     * @param array            $config
171
     * @param ContainerBuilder $container  In case we need to add additional services for this plugin
172
     * @param string           $serviceId  service id of the plugin, in case we need to add additional services for this plugin
173
     */
174 25
    private function configurePluginByName($name, Definition $definition, array $config, ContainerBuilder $container, $serviceId)
175
    {
176 25
        switch ($name) {
177 25
            case 'cache':
178 2
                $options = $config['config'];
179 2
                if (!empty($options['cache_key_generator'])) {
180 1
                    $options['cache_key_generator'] = new Reference($options['cache_key_generator']);
181
                }
182
183
                $definition
184 2
                    ->replaceArgument(0, new Reference($config['cache_pool']))
185 2
                    ->replaceArgument(1, new Reference($config['stream_factory']))
186 2
                    ->replaceArgument(2, $options);
187
188 2
                break;
189 25
            case 'cookie':
190
                $definition->replaceArgument(0, new Reference($config['cookie_jar']));
191
192
                break;
193 25
            case 'decoder':
194 25
                $definition->addArgument([
195 25
                    'use_content_encoding' => $config['use_content_encoding'],
196
                ]);
197
198 25
                break;
199 25
            case 'history':
200
                $definition->replaceArgument(0, new Reference($config['journal']));
201
202
                break;
203 25
            case 'logger':
204 25
                $definition->replaceArgument(0, new Reference($config['logger']));
205 25
                if (!empty($config['formatter'])) {
206
                    $definition->replaceArgument(1, new Reference($config['formatter']));
207
                }
208
209 25
                break;
210 25
            case 'redirect':
211 25
                $definition->addArgument([
212 25
                    'preserve_header' => $config['preserve_header'],
213 25
                    'use_default_for_multiple' => $config['use_default_for_multiple'],
214
                ]);
215
216 25
                break;
217 25
            case 'retry':
218 25
                $definition->addArgument([
219 25
                    'retries' => $config['retry'],
220
                ]);
221
222 25
                break;
223 25
            case 'stopwatch':
224 25
                $definition->replaceArgument(0, new Reference($config['stopwatch']));
225
226 25
                break;
227
228
            /* client specific plugins */
229
230 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...
231 5
                $hostUriService = $serviceId.'.host_uri';
232 5
                $this->createUri($container, $hostUriService, $config['host']);
233 5
                $definition->replaceArgument(0, new Reference($hostUriService));
234 5
                $definition->replaceArgument(1, [
235 5
                    'replace' => $config['replace'],
236
                ]);
237
238 5
                break;
239 1
            case 'add_path':
240
                $pathUriService = $serviceId.'.path_uri';
241
                $this->createUri($container, $pathUriService, $config['path']);
242
                $definition->replaceArgument(0, new Reference($pathUriService));
243
244
                break;
245 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...
246
                $baseUriService = $serviceId.'.base_uri';
247
                $this->createUri($container, $baseUriService, $config['uri']);
248
                $definition->replaceArgument(0, new Reference($baseUriService));
249
                $definition->replaceArgument(1, [
250
                    'replace' => $config['replace'],
251
                ]);
252
253
                break;
254 1
            case 'header_append':
255 1
            case 'header_defaults':
256 1
            case 'header_set':
257 1
            case 'header_remove':
258 1
                $definition->replaceArgument(0, $config['headers']);
259
260 1
                break;
261
262 1
            case 'query_defaults':
263 1
                $definition->replaceArgument(0, $config['parameters']);
264
265 1
                break;
266
267
            default:
268
                throw new \InvalidArgumentException(sprintf('Internal exception: Plugin %s is not handled', $name));
269
        }
270 25
    }
271
272
    /**
273
     * @param ContainerBuilder $container
274
     * @param array            $config
275
     *
276
     * @return array list of service ids for the authentication plugins
277
     */
278 5
    private function configureAuthentication(ContainerBuilder $container, array $config, $servicePrefix = 'httplug.plugin.authentication')
279
    {
280 5
        $pluginServices = [];
281
282 5
        foreach ($config as $name => $values) {
283 5
            $authServiceKey = sprintf($servicePrefix.'.%s.auth', $name);
284 5
            switch ($values['type']) {
285 5
                case 'bearer':
286
                    $container->register($authServiceKey, Bearer::class)
287
                        ->addArgument($values['token']);
288
289
                    break;
290 5
                case 'basic':
291 5
                    $container->register($authServiceKey, BasicAuth::class)
292 5
                        ->addArgument($values['username'])
293 5
                        ->addArgument($values['password']);
294
295 5
                    break;
296
                case 'wsse':
297
                    $container->register($authServiceKey, Wsse::class)
298
                        ->addArgument($values['username'])
299
                        ->addArgument($values['password']);
300
301
                    break;
302
                case 'query_param':
303
                    $container->register($authServiceKey, QueryParam::class)
304
                        ->addArgument($values['params']);
305
306
                    break;
307
                case 'service':
308
                    $authServiceKey = $values['service'];
309
310
                    break;
311
                default:
312
                    throw new \LogicException(sprintf('Unknown authentication type: "%s"', $values['type']));
313
            }
314
315 5
            $pluginServiceKey = $servicePrefix.'.'.$name;
316 5
            $container->register($pluginServiceKey, AuthenticationPlugin::class)
317 5
                ->addArgument(new Reference($authServiceKey))
318
            ;
319 5
            $pluginServices[] = $pluginServiceKey;
320
        }
321
322 5
        return $pluginServices;
323
    }
324
325
    /**
326
     * @param ContainerBuilder $container
327
     * @param string           $clientName
328
     * @param array            $arguments
329
     */
330 17
    private function configureClient(ContainerBuilder $container, $clientName, array $arguments)
331
    {
332 17
        $serviceId = 'httplug.client.'.$clientName;
333
334 17
        $plugins = [];
335 17
        foreach ($arguments['plugins'] as $plugin) {
336 8
            $pluginName = key($plugin);
337 8
            $pluginConfig = current($plugin);
338 8
            if ('reference' === $pluginName) {
339 8
                $plugins[] = $pluginConfig['id'];
340 5
            } elseif ('authentication' === $pluginName) {
341 5
                $plugins = array_merge($plugins, $this->configureAuthentication($container, $pluginConfig, $serviceId.'.authentication'));
342
            } else {
343 8
                $plugins[] = $this->configurePlugin($container, $serviceId, $pluginName, $pluginConfig);
344
            }
345
        }
346
347 17
        if (empty($arguments['service'])) {
348
            $container
349 16
                ->register($serviceId.'.client', HttpClient::class)
350 16
                ->setFactory([new Reference($arguments['factory']), 'createClient'])
351 16
                ->addArgument($arguments['config'])
352 16
                ->setPublic(false);
353
        } else {
354
            $container
355 1
                ->setAlias($serviceId.'.client', new Alias($arguments['service'], false));
356
        }
357
358
        $definition = $container
359 17
            ->register($serviceId, PluginClient::class)
360 17
            ->setFactory([new Reference(PluginClientFactory::class), 'createClient'])
361 17
            ->addArgument(new Reference($serviceId.'.client'))
362 17
            ->addArgument(
363 17
                array_map(
364 17
                    function ($id) {
365 8
                        return new Reference($id);
366 17
                    },
367
                    $plugins
368
                )
369
            )
370 17
            ->addArgument([
371 17
                'client_name' => $clientName,
372
            ])
373
        ;
374
375 17
        if (is_bool($arguments['public'])) {
376 4
            $definition->setPublic($arguments['public']);
377
        }
378
379
        /*
380
         * Decorate the client with clients from client-common
381
         */
382 17 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...
383
            $container
384 2
                ->register($serviceId.'.flexible', FlexibleHttpClient::class)
385 2
                ->addArgument(new Reference($serviceId.'.flexible.inner'))
386 2
                ->setPublic($arguments['public'] ? true : false)
387 2
                ->setDecoratedService($serviceId)
388
            ;
389
        }
390
391 17 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...
392
            $container
393 2
                ->register($serviceId.'.http_methods', HttpMethodsClient::class)
394 2
                ->setArguments([new Reference($serviceId.'.http_methods.inner'), new Reference('httplug.message_factory')])
395 2
                ->setPublic($arguments['public'] ? true : false)
396 2
                ->setDecoratedService($serviceId)
397
            ;
398
        }
399
400 17 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...
401
            $container
402 2
                ->register($serviceId.'.batch_client', BatchClient::class)
403 2
                ->setArguments([new Reference($serviceId.'.batch_client.inner')])
404 2
                ->setPublic($arguments['public'] ? true : false)
405 2
                ->setDecoratedService($serviceId)
406
            ;
407
        }
408 17
    }
409
410
    /**
411
     * Create a URI object with the default URI factory.
412
     *
413
     * @param ContainerBuilder $container
414
     * @param string           $serviceId Name of the private service to create
415
     * @param string           $uri       String representation of the URI
416
     */
417 5
    private function createUri(ContainerBuilder $container, $serviceId, $uri)
418
    {
419
        $container
420 5
            ->register($serviceId, UriInterface::class)
421 5
            ->setPublic(false)
422 5
            ->setFactory([new Reference('httplug.uri_factory'), 'createUri'])
423 5
            ->addArgument($uri)
424
        ;
425 5
    }
426
427
    /**
428
     * Make the user can select what client is used for auto discovery. If none is provided, a service will be created
429
     * by finding a client using auto discovery.
430
     *
431
     * @param ContainerBuilder $container
432
     * @param array            $config
433
     */
434 25
    private function configureAutoDiscoveryClients(ContainerBuilder $container, array $config)
435
    {
436 25
        $httpClient = $config['discovery']['client'];
437 25 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...
438 2
            $container->removeDefinition('httplug.auto_discovery.auto_discovered_client');
439 2
            $container->removeDefinition('httplug.collector.auto_discovered_client');
440
441 2
            if (!empty($httpClient)) {
442 1
                $container->setAlias('httplug.auto_discovery.auto_discovered_client', $httpClient);
443 1
                $container->getAlias('httplug.auto_discovery.auto_discovered_client')->setPublic(false);
444
            }
445
        }
446
447 25
        $asyncHttpClient = $config['discovery']['async_client'];
448 25 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...
449 23
            $container->removeDefinition('httplug.auto_discovery.auto_discovered_async');
450 23
            $container->removeDefinition('httplug.collector.auto_discovered_async');
451
452 23
            if (!empty($asyncHttpClient)) {
453 1
                $container->setAlias('httplug.auto_discovery.auto_discovered_async', $asyncHttpClient);
454 1
                $container->getAlias('httplug.auto_discovery.auto_discovered_async')->setPublic(false);
455
            }
456
        }
457
458 25
        if (null === $httpClient && null === $asyncHttpClient) {
459 1
            $container->removeDefinition('httplug.strategy');
460
461 1
            return;
462
        }
463 24
    }
464
465
    /**
466
     * {@inheritdoc}
467
     */
468 25
    public function getConfiguration(array $config, ContainerBuilder $container)
469
    {
470 25
        return new Configuration($container->getParameter('kernel.debug'));
471
    }
472
473
    /**
474
     * Configure a plugin using the parent definition from plugins.xml.
475
     *
476
     * @param ContainerBuilder $container
477
     * @param string           $serviceId
478
     * @param string           $pluginName
479
     * @param array            $pluginConfig
480
     *
481
     * @return string configured service id
482
     */
483 5
    private function configurePlugin(ContainerBuilder $container, $serviceId, $pluginName, array $pluginConfig)
484
    {
485 5
        $pluginServiceId = $serviceId.'.plugin.'.$pluginName;
486
487 5
        $definition = class_exists(ChildDefinition::class)
488 5
            ? new ChildDefinition('httplug.plugin.'.$pluginName)
489 5
            : new DefinitionDecorator('httplug.plugin.'.$pluginName);
490
491 5
        $this->configurePluginByName($pluginName, $definition, $pluginConfig, $container, $pluginServiceId);
492 5
        $container->setDefinition($pluginServiceId, $definition);
493
494 5
        return $pluginServiceId;
495
    }
496
}
497