Completed
Push — master ( 8b7cb0...fb1018 )
by Tobias
12:08
created

HttplugExtension::configureClients()   B

Complexity

Conditions 10
Paths 111

Size

Total Lines 45

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 10.0082

Importance

Changes 0
Metric Value
dl 0
loc 45
ccs 22
cts 23
cp 0.9565
rs 7.26
c 0
b 0
f 0
cc 10
nc 111
nop 2
crap 10.0082

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 26
    public function load(array $configs, ContainerBuilder $container)
40
    {
41 26
        $configuration = $this->getConfiguration($configs, $container);
42 26
        $config = $this->processConfiguration($configuration, $configs);
43
44 26
        $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
45
46 26
        $loader->load('services.xml');
47 26
        $loader->load('plugins.xml');
48 26
        if (\class_exists(MockClient::class)) {
49 26
            $loader->load('mock-client.xml');
50
        }
51
52
        // Register default services
53 26
        foreach ($config['classes'] as $service => $class) {
54 26
            if (!empty($class)) {
55 1
                $container->register(sprintf('httplug.%s.default', $service), $class);
56
            }
57
        }
58
59
        // Set main aliases
60 26
        foreach ($config['main_alias'] as $type => $id) {
61 26
            $container->setAlias(sprintf('httplug.%s', $type), new Alias($id, true));
62
        }
63
64
        // Configure toolbar
65 26
        $profilingEnabled = $this->isConfigEnabled($container, $config['profiling']);
66 26
        if ($profilingEnabled) {
67 23
            $loader->load('data-collector.xml');
68
69 23
            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 23
                ->getDefinition('httplug.formatter.full_http_message')
79 23
                ->addArgument($config['profiling']['captured_body_length'])
80
            ;
81
82 23
            if (!class_exists(\Twig_Environment::class)) {
83
                $container->removeDefinition('httplug.collector.twig.http_message');
84
            }
85
        }
86
87 26
        $this->configureClients($container, $config);
88 26
        $this->configurePlugins($container, $config['plugins']); // must be after clients, as clients.X.plugins might use plugins as templates that will be removed
89 26
        $this->configureAutoDiscoveryClients($container, $config);
90 26
    }
91
92
    /**
93
     * Configure client services.
94
     *
95
     * @param ContainerBuilder $container
96
     * @param array            $config
97
     */
98 26
    private function configureClients(ContainerBuilder $container, array $config)
99
    {
100 26
        $first = null;
101 26
        $clients = [];
102
103 26
        foreach ($config['clients'] as $name => $arguments) {
104 18
            if (null === $first) {
105
                // Save the name of the first configured client.
106 18
                $first = $name;
107
            }
108
109 18
            $this->configureClient($container, $name, $arguments);
110 18
            $clients[] = $name;
111
        }
112
113
        // If we have clients configured
114 26
        if (null !== $first) {
115
            // If we do not have a client named 'default'
116 18
            if (!array_key_exists('default', $config['clients'])) {
117 18
                $serviceId = 'httplug.client.'.$first;
118
                // Alias the first client to httplug.client.default
119 18
                $container->setAlias('httplug.client.default', $serviceId);
120 18
                $default = $first;
121
            } else {
122
                $default = 'default';
123
            }
124
125
            // Autowiring alias for special clients, if they are enabled on the default client
126 18
            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 18
            if ($config['clients'][$default]['http_methods_client']) {
130 2
                if (\interface_exists(HttpMethodsClientInterface::class)) {
131
                    // support for client-common 1.9
132 2
                    $container->setAlias(HttpMethodsClientInterface::class, $serviceId.'.http_methods');
133
                }
134
            }
135 18
            if ($config['clients'][$default]['batch_client']) {
136 2
                if (\interface_exists(BatchClientInterface::class)) {
137
                    // support for client-common 1.9
138 2
                    $container->setAlias(BatchClientInterface::class, $serviceId.'.batch_client');
139
                }
140
            }
141
        }
142 26
    }
143
144
    /**
145
     * Configure all Httplug plugins or remove their service definition.
146
     *
147
     * @param ContainerBuilder $container
148
     * @param array            $config
149
     */
150 26
    private function configurePlugins(ContainerBuilder $container, array $config)
151
    {
152 26
        if (!empty($config['authentication'])) {
153
            $this->configureAuthentication($container, $config['authentication']);
154
        }
155 26
        unset($config['authentication']);
156
157 26
        foreach ($config as $name => $pluginConfig) {
158 26
            $pluginId = 'httplug.plugin.'.$name;
159
160 26
            if ($this->isConfigEnabled($container, $pluginConfig)) {
161 26
                $def = $container->getDefinition($pluginId);
162 26
                $this->configurePluginByName($name, $def, $pluginConfig, $container, $pluginId);
163
            }
164
        }
165 26
    }
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 26
    private function configurePluginByName($name, Definition $definition, array $config, ContainerBuilder $container, $serviceId)
175
    {
176 26
        switch ($name) {
177 26
            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 26
            case 'cookie':
190
                $definition->replaceArgument(0, new Reference($config['cookie_jar']));
191
192
                break;
193 26
            case 'decoder':
194 26
                $definition->addArgument([
195 26
                    'use_content_encoding' => $config['use_content_encoding'],
196
                ]);
197
198 26
                break;
199 26
            case 'history':
200
                $definition->replaceArgument(0, new Reference($config['journal']));
201
202
                break;
203 26
            case 'logger':
204 26
                $definition->replaceArgument(0, new Reference($config['logger']));
205 26
                if (!empty($config['formatter'])) {
206
                    $definition->replaceArgument(1, new Reference($config['formatter']));
207
                }
208
209 26
                break;
210 26
            case 'redirect':
211 26
                $definition->addArgument([
212 26
                    'preserve_header' => $config['preserve_header'],
213 26
                    'use_default_for_multiple' => $config['use_default_for_multiple'],
214
                ]);
215
216 26
                break;
217 26
            case 'retry':
218 26
                $definition->addArgument([
219 26
                    'retries' => $config['retry'],
220
                ]);
221
222 26
                break;
223 26
            case 'stopwatch':
224 26
                $definition->replaceArgument(0, new Reference($config['stopwatch']));
225
226 26
                break;
227
228
            /* client specific plugins */
229
230 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...
231 6
                $hostUriService = $serviceId.'.host_uri';
232 6
                $this->createUri($container, $hostUriService, $config['host']);
233 6
                $definition->replaceArgument(0, new Reference($hostUriService));
234 6
                $definition->replaceArgument(1, [
235 6
                    'replace' => $config['replace'],
236
                ]);
237
238 6
                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 26
    }
271
272
    /**
273
     * @param ContainerBuilder $container
274
     * @param array            $config
275
     *
276
     * @return array list of service ids for the authentication plugins
277
     */
278 6
    private function configureAuthentication(ContainerBuilder $container, array $config, $servicePrefix = 'httplug.plugin.authentication')
279
    {
280 6
        $pluginServices = [];
281
282 6
        foreach ($config as $name => $values) {
283 6
            $authServiceKey = sprintf($servicePrefix.'.%s.auth', $name);
284 6
            switch ($values['type']) {
285 6
                case 'bearer':
286
                    $container->register($authServiceKey, Bearer::class)
287
                        ->addArgument($values['token']);
288
289
                    break;
290 6
                case 'basic':
291 6
                    $container->register($authServiceKey, BasicAuth::class)
292 6
                        ->addArgument($values['username'])
293 6
                        ->addArgument($values['password']);
294
295 6
                    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 6
            $pluginServiceKey = $servicePrefix.'.'.$name;
316 6
            $container->register($pluginServiceKey, AuthenticationPlugin::class)
317 6
                ->addArgument(new Reference($authServiceKey))
318
            ;
319 6
            $pluginServices[] = $pluginServiceKey;
320
        }
321
322 6
        return $pluginServices;
323
    }
324
325
    /**
326
     * @param ContainerBuilder $container
327
     * @param string           $clientName
328
     * @param array            $arguments
329
     */
330 18
    private function configureClient(ContainerBuilder $container, $clientName, array $arguments)
331
    {
332 18
        $serviceId = 'httplug.client.'.$clientName;
333
334 18
        $plugins = [];
335 18
        foreach ($arguments['plugins'] as $plugin) {
336 9
            $pluginName = key($plugin);
337 9
            $pluginConfig = current($plugin);
338 9
            if ('reference' === $pluginName) {
339 9
                $plugins[] = $pluginConfig['id'];
340 6
            } elseif ('authentication' === $pluginName) {
341 6
                $plugins = array_merge($plugins, $this->configureAuthentication($container, $pluginConfig, $serviceId.'.authentication'));
342
            } else {
343 6
                $plugins[] = $this->configurePlugin($container, $serviceId, $pluginName, $pluginConfig);
344
            }
345
        }
346
347 18
        if (empty($arguments['service'])) {
348
            $container
349 17
                ->register($serviceId.'.client', HttpClient::class)
350 17
                ->setFactory([new Reference($arguments['factory']), 'createClient'])
351 17
                ->addArgument($arguments['config'])
352 17
                ->setPublic(false);
353
        } else {
354
            $container
355 2
                ->setAlias($serviceId.'.client', new Alias($arguments['service'], false));
356
        }
357
358
        $definition = $container
359 18
            ->register($serviceId, PluginClient::class)
360 18
            ->setFactory([new Reference(PluginClientFactory::class), 'createClient'])
361 18
            ->addArgument(new Reference($serviceId.'.client'))
362 18
            ->addArgument(
363 18
                array_map(
364 18
                    function ($id) {
365 9
                        return new Reference($id);
366 18
                    },
367
                    $plugins
368
                )
369
            )
370 18
            ->addArgument([
371 18
                'client_name' => $clientName,
372
            ])
373
        ;
374
375 18
        if (is_bool($arguments['public'])) {
376 5
            $definition->setPublic($arguments['public']);
377
        }
378
379
        /*
380
         * Decorate the client with clients from client-common
381
         */
382 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...
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 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...
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 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...
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 18
    }
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 6
    private function createUri(ContainerBuilder $container, $serviceId, $uri)
418
    {
419
        $container
420 6
            ->register($serviceId, UriInterface::class)
421 6
            ->setPublic(false)
422 6
            ->setFactory([new Reference('httplug.uri_factory'), 'createUri'])
423 6
            ->addArgument($uri)
424
        ;
425 6
    }
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 26
    private function configureAutoDiscoveryClients(ContainerBuilder $container, array $config)
435
    {
436 26
        $httpClient = $config['discovery']['client'];
437 26 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 26
        $asyncHttpClient = $config['discovery']['async_client'];
448 26 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 26
        if (null === $httpClient && null === $asyncHttpClient) {
459 1
            $container->removeDefinition('httplug.strategy');
460
461 1
            return;
462
        }
463 25
    }
464
465
    /**
466
     * {@inheritdoc}
467
     */
468 26
    public function getConfiguration(array $config, ContainerBuilder $container)
469
    {
470 26
        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 6
    private function configurePlugin(ContainerBuilder $container, $serviceId, $pluginName, array $pluginConfig)
484
    {
485 6
        $pluginServiceId = $serviceId.'.plugin.'.$pluginName;
486
487 6
        $definition = class_exists(ChildDefinition::class)
488 6
            ? new ChildDefinition('httplug.plugin.'.$pluginName)
489 6
            : new DefinitionDecorator('httplug.plugin.'.$pluginName);
490
491 6
        $this->configurePluginByName($pluginName, $definition, $pluginConfig, $container, $pluginServiceId);
492 6
        $container->setDefinition($pluginServiceId, $definition);
493
494 6
        return $pluginServiceId;
495
    }
496
}
497