Completed
Push — master ( 1b0026...e1710b )
by Fabien
34:02
created

HttplugExtension   C

Complexity

Total Complexity 62

Size/Duplication

Total Lines 441
Duplicated Lines 9.07 %

Coupling/Cohesion

Components 1
Dependencies 10

Test Coverage

Coverage 80.88%

Importance

Changes 0
Metric Value
wmc 62
lcom 1
cbo 10
dl 40
loc 441
ccs 110
cts 136
cp 0.8088
rs 5.9493
c 0
b 0
f 0

12 Methods

Rating   Name   Duplication   Size   Complexity  
B load() 0 45 6
B configureClients() 0 24 5
A configurePlugins() 0 18 4
C configurePluginByName() 0 62 15
B configureAuthentication() 0 37 6
C configureClient() 20 74 9
A createUri() 0 9 1
D configureAutoDiscoveryClients() 20 40 10
A getConfiguration() 0 4 1
A configurePlugin() 0 13 2
A decoratePluginWithProfilePlugin() 0 11 1
A configureStackPlugin() 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\HttplugBundle\ClientFactory\DummyClient;
10
use Http\HttplugBundle\ClientFactory\PluginClientFactory;
11
use Http\HttplugBundle\Collector\ProfilePlugin;
12
use Http\Message\Authentication\BasicAuth;
13
use Http\Message\Authentication\Bearer;
14
use Http\Message\Authentication\Wsse;
15
use Psr\Http\Message\UriInterface;
16
use Symfony\Component\Config\FileLocator;
17
use Symfony\Component\DependencyInjection\ChildDefinition;
18
use Symfony\Component\DependencyInjection\ContainerBuilder;
19
use Symfony\Component\DependencyInjection\Definition;
20
use Symfony\Component\DependencyInjection\DefinitionDecorator;
21
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
22
use Symfony\Component\DependencyInjection\Reference;
23
use Symfony\Component\DependencyInjection\ServiceLocator;
24
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
25
26
/**
27
 * @author David Buchmann <[email protected]>
28
 * @author Tobias Nyholm <[email protected]>
29
 */
30
class HttplugExtension extends Extension
31
{
32
    /**
33
     * {@inheritdoc}
34
     */
35 15
    public function load(array $configs, ContainerBuilder $container)
36
    {
37 15
        $configuration = $this->getConfiguration($configs, $container);
38 15
        $config = $this->processConfiguration($configuration, $configs);
39
40 15
        $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
41
42 15
        $loader->load('services.xml');
43 15
        $loader->load('plugins.xml');
44
45
        // Register default services
46 15
        foreach ($config['classes'] as $service => $class) {
47 15
            if (!empty($class)) {
48 1
                $container->register(sprintf('httplug.%s.default', $service), $class);
49 1
            }
50 15
        }
51
52
        // Set main aliases
53 15
        foreach ($config['main_alias'] as $type => $id) {
54 15
            $container->setAlias(sprintf('httplug.%s', $type), $id);
55 15
        }
56
57
        // Configure toolbar
58 15
        $profilingEnabled = $this->isConfigEnabled($container, $config['profiling']);
59 15
        if ($profilingEnabled) {
60 12
            $loader->load('data-collector.xml');
61
62 12
            if (!empty($config['profiling']['formatter'])) {
63
                // Add custom formatter
64
                $container
65 1
                    ->getDefinition('httplug.collector.formatter')
66 1
                    ->replaceArgument(0, new Reference($config['profiling']['formatter']))
67
                ;
68 1
            }
69
70
            $container
71 12
                ->getDefinition('httplug.formatter.full_http_message')
72 12
                ->addArgument($config['profiling']['captured_body_length'])
73
            ;
74 12
        }
75
76 15
        $this->configureClients($container, $config, $profilingEnabled);
77 15
        $this->configurePlugins($container, $config['plugins']); // must be after clients, as clients.X.plugins might use plugins as templates that will be removed
78 15
        $this->configureAutoDiscoveryClients($container, $config);
79 15
    }
80
81
    /**
82
     * Configure client services.
83
     *
84
     * @param ContainerBuilder $container
85
     * @param array            $config
86
     * @param bool             $profiling
87
     */
88 15
    private function configureClients(ContainerBuilder $container, array $config, $profiling)
0 ignored issues
show
Unused Code introduced by
The parameter $profiling is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
89
    {
90 15
        $first = null;
91 15
        $clients = [];
92
93 15
        foreach ($config['clients'] as $name => $arguments) {
94 8
            if ($first === null) {
95
                // Save the name of the first configured client.
96 8
                $first = $name;
97 8
            }
98
99 8
            $this->configureClient($container, $name, $arguments, $this->isConfigEnabled($container, $config['profiling']));
100 8
            $clients[] = $name;
101 15
        }
102
103
        // If we have clients configured
104 15
        if ($first !== null) {
105
            // If we do not have a client named 'default'
106 8
            if (!isset($config['clients']['default'])) {
107
                // Alias the first client to httplug.client.default
108 8
                $container->setAlias('httplug.client.default', 'httplug.client.'.$first);
109 8
            }
110 8
        }
111 15
    }
112
113
    /**
114
     * Configure all Httplug plugins or remove their service definition.
115
     *
116
     * @param ContainerBuilder $container
117
     * @param array            $config
118
     */
119 15
    private function configurePlugins(ContainerBuilder $container, array $config)
120
    {
121 15
        if (!empty($config['authentication'])) {
122
            $this->configureAuthentication($container, $config['authentication']);
123
        }
124 15
        unset($config['authentication']);
125
126 15
        foreach ($config as $name => $pluginConfig) {
127 15
            $pluginId = 'httplug.plugin.'.$name;
128
129 15
            if ($this->isConfigEnabled($container, $pluginConfig)) {
130 15
                $def = $container->getDefinition($pluginId);
131 15
                $this->configurePluginByName($name, $def, $pluginConfig, $container, $pluginId);
132 15
            } else {
133 15
                $container->removeDefinition($pluginId);
134
            }
135 15
        }
136 15
    }
137
138
    /**
139
     * @param string           $name
140
     * @param Definition       $definition
141
     * @param array            $config
142
     * @param ContainerBuilder $container  In case we need to add additional services for this plugin
143
     * @param string           $serviceId  Service id of the plugin, in case we need to add additional services for this plugin.
144
     */
145 15
    private function configurePluginByName($name, Definition $definition, array $config, ContainerBuilder $container, $serviceId)
146
    {
147
        switch ($name) {
148 15
            case 'cache':
149
                $definition
150
                    ->replaceArgument(0, new Reference($config['cache_pool']))
151
                    ->replaceArgument(1, new Reference($config['stream_factory']))
152
                    ->replaceArgument(2, $config['config']);
153
                break;
154 15
            case 'cookie':
155
                $definition->replaceArgument(0, new Reference($config['cookie_jar']));
156
                break;
157 15
            case 'decoder':
158 15
                $definition->addArgument([
159 15
                    'use_content_encoding' => $config['use_content_encoding'],
160 15
                ]);
161 15
                break;
162 15
            case 'history':
163
                $definition->replaceArgument(0, new Reference($config['journal']));
164
                break;
165 15
            case 'logger':
166 15
                $definition->replaceArgument(0, new Reference($config['logger']));
167 15
                if (!empty($config['formatter'])) {
168
                    $definition->replaceArgument(1, new Reference($config['formatter']));
169
                }
170 15
                break;
171 15
            case 'redirect':
172 15
                $definition->addArgument([
173 15
                    'preserve_header' => $config['preserve_header'],
174 15
                    'use_default_for_multiple' => $config['use_default_for_multiple'],
175 15
                ]);
176 15
                break;
177 15
            case 'retry':
178 15
                $definition->addArgument([
179 15
                    'retries' => $config['retry'],
180 15
                ]);
181 15
                break;
182 15
            case 'stopwatch':
183 15
                $definition->replaceArgument(0, new Reference($config['stopwatch']));
184 15
                break;
185
186
            /* client specific plugins */
187
188 5
            case 'add_host':
189 5
                $uriService = $serviceId.'.host_uri';
190 5
                $this->createUri($container, $uriService, $config['host']);
191 5
                $definition->replaceArgument(0, new Reference($uriService));
192 5
                $definition->replaceArgument(1, [
193 5
                    'replace' => $config['replace'],
194 5
                ]);
195 5
                break;
196 1
            case 'header_append':
197 1
            case 'header_defaults':
198 1
            case 'header_set':
199 1
            case 'header_remove':
200 1
                $definition->replaceArgument(0, $config['headers']);
201 1
                break;
202
203
            default:
204
                throw new \InvalidArgumentException(sprintf('Internal exception: Plugin %s is not handled', $name));
205
        }
206 15
    }
207
208
    /**
209
     * @param ContainerBuilder $container
210
     * @param array            $config
211
     *
212
     * @return array List of service ids for the authentication plugins.
213
     */
214 5
    private function configureAuthentication(ContainerBuilder $container, array $config, $servicePrefix = 'httplug.plugin.authentication')
215
    {
216 5
        $pluginServices = [];
217
218 5
        foreach ($config as $name => $values) {
219 5
            $authServiceKey = sprintf($servicePrefix.'.%s.auth', $name);
220 5
            switch ($values['type']) {
221 5
                case 'bearer':
222
                    $container->register($authServiceKey, Bearer::class)
223
                        ->addArgument($values['token']);
224
                    break;
225 5
                case 'basic':
226
                    $container->register($authServiceKey, BasicAuth::class)
227 5
                        ->addArgument($values['username'])
228 5
                        ->addArgument($values['password']);
229 5
                    break;
230
                case 'wsse':
231
                    $container->register($authServiceKey, Wsse::class)
232
                        ->addArgument($values['username'])
233
                        ->addArgument($values['password']);
234
                    break;
235
                case 'service':
236
                    $authServiceKey = $values['service'];
237
                    break;
238
                default:
239
                    throw new \LogicException(sprintf('Unknown authentication type: "%s"', $values['type']));
240 5
            }
241
242 5
            $pluginServiceKey = $servicePrefix.'.'.$name;
243
            $container->register($pluginServiceKey, AuthenticationPlugin::class)
244
                ->addArgument(new Reference($authServiceKey))
245
            ;
246
            $pluginServices[] = $pluginServiceKey;
247
        }
248
249
        return $pluginServices;
250
    }
251
252
    /**
253
     * @param ContainerBuilder $container
254
     * @param string           $clientName
255
     * @param array            $arguments
256
     * @param bool             $profiling
257
     */
258
    private function configureClient(ContainerBuilder $container, $clientName, array $arguments, $profiling)
259
    {
260
        $serviceId = 'httplug.client.'.$clientName;
261
262
        $plugins = [];
263
        foreach ($arguments['plugins'] as $plugin) {
264
            list($pluginName, $pluginConfig) = each($plugin);
265
            if ('reference' === $pluginName) {
266
                $plugins[] = $pluginConfig['id'];
267
            } elseif ('authentication' === $pluginName) {
268
                $plugins = array_merge($plugins, $this->configureAuthentication($container, $pluginConfig, $serviceId.'.authentication'));
269
            } else {
270
                $plugins[] = $this->configurePlugin($container, $serviceId, $pluginName, $pluginConfig);
271
            }
272
        }
273
274
        $pluginClientOptions = [];
275
        if ($profiling) {
276
            //Decorate each plugin with a ProfilePlugin instance.
277
            foreach ($plugins as $pluginServiceId) {
278
                $this->decoratePluginWithProfilePlugin($container, $pluginServiceId);
279
            }
280
281
            // To profile the requests, add a StackPlugin as first plugin in the chain.
282
            $stackPluginId = $this->configureStackPlugin($container, $clientName, $serviceId);
283
            array_unshift($plugins, $stackPluginId);
284
        }
285
286
        $container
287
            ->register($serviceId, DummyClient::class)
288
            ->setFactory([PluginClientFactory::class, 'createPluginClient'])
289
            ->addArgument(
290
                array_map(
291
                    function ($id) {
292
                        return new Reference($id);
293
                    },
294
                    $plugins
295
                )
296
            )
297
            ->addArgument(new Reference($arguments['factory']))
298
            ->addArgument($arguments['config'])
299
            ->addArgument($pluginClientOptions)
300
        ;
301
302
        /*
303
         * Decorate the client with clients from client-common
304
         */
305 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...
306
            $container
307
                ->register($serviceId.'.flexible', FlexibleHttpClient::class)
308
                ->addArgument(new Reference($serviceId.'.flexible.inner'))
309
                ->setPublic(false)
310
                ->setDecoratedService($serviceId)
311
            ;
312
        }
313
314 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...
315
            $container
316
                ->register($serviceId.'.http_methods', HttpMethodsClient::class)
317
                ->setArguments([new Reference($serviceId.'.http_methods.inner'), new Reference('httplug.message_factory')])
318
                ->setPublic(false)
319
                ->setDecoratedService($serviceId)
320
            ;
321
        }
322
323 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...
324
            $container
325
                ->register($serviceId.'.batch_client', BatchClient::class)
326
                ->setArguments([new Reference($serviceId.'.batch_client.inner')])
327
                ->setPublic(false)
328
                ->setDecoratedService($serviceId)
329
            ;
330
        }
331
    }
332
333
    /**
334
     * Create a URI object with the default URI factory.
335
     *
336
     * @param ContainerBuilder $container
337
     * @param string           $serviceId Name of the private service to create
338
     * @param string           $uri       String representation of the URI
339
     */
340
    private function createUri(ContainerBuilder $container, $serviceId, $uri)
341
    {
342
        $container
343
            ->register($serviceId, UriInterface::class)
344
            ->setPublic(false)
345
            ->setFactory([new Reference('httplug.uri_factory'), 'createUri'])
346
            ->addArgument($uri)
347
        ;
348
    }
349
350
    /**
351
     * Make the user can select what client is used for auto discovery. If none is provided, a service will be created
352
     * by finding a client using auto discovery.
353
     *
354
     * @param ContainerBuilder $container
355
     * @param array            $config
356
     */
357
    private function configureAutoDiscoveryClients(ContainerBuilder $container, array $config)
358
    {
359
        $httpClient = $config['discovery']['client'];
360 View Code Duplication
        if ($httpClient !== 'auto') {
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...
361
            $container->removeDefinition('httplug.auto_discovery.auto_discovered_client');
362
            $container->removeDefinition('httplug.collector.auto_discovered_client');
363
            $container->removeDefinition('httplug.auto_discovery.auto_discovered_client.plugin');
364
365
            if (!empty($httpClient)) {
366
                $container->setAlias('httplug.auto_discovery.auto_discovered_client', $httpClient);
367
                $container->getAlias('httplug.auto_discovery.auto_discovered_client')->setPublic(false);
368
            }
369
        }
370
371
        $asyncHttpClient = $config['discovery']['async_client'];
372 View Code Duplication
        if ($asyncHttpClient !== 'auto') {
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...
373
            $container->removeDefinition('httplug.auto_discovery.auto_discovered_async');
374
            $container->removeDefinition('httplug.collector.auto_discovered_async');
375
            $container->removeDefinition('httplug.auto_discovery.auto_discovered_async.plugin');
376
377
            if (!empty($asyncHttpClient)) {
378
                $container->setAlias('httplug.auto_discovery.auto_discovered_async', $asyncHttpClient);
379
                $container->getAlias('httplug.auto_discovery.auto_discovered_async')->setPublic(false);
380
            }
381
        }
382
383
        if (null === $httpClient && null === $asyncHttpClient) {
384
            $container->removeDefinition('httplug.strategy');
385
386
            return;
387
        }
388
389
        if (!class_exists(ServiceLocator::class)) {
390
            $container->getDefinition('httplug.strategy')->setArguments([
391
                new Reference('service_container'),
392
                in_array($httpClient, ['auto', null], true) ? 'httplug.auto_discovery.auto_discovered_client' : $httpClient,
393
                in_array($asyncHttpClient, ['auto', null], true) ? 'httplug.auto_discovery.auto_discovered_async' : $asyncHttpClient,
394
            ]);
395
        }
396
    }
397
398
    /**
399
     * {@inheritdoc}
400
     */
401
    public function getConfiguration(array $config, ContainerBuilder $container)
402
    {
403
        return new Configuration($container->getParameter('kernel.debug'));
404
    }
405
406
    /**
407
     * Configure a plugin using the parent definition from plugins.xml.
408
     *
409
     * @param ContainerBuilder $container
410
     * @param string           $serviceId
411
     * @param string           $pluginName
412
     * @param array            $pluginConfig
413
     *
414
     * @return string configured service id
415
     */
416
    private function configurePlugin(ContainerBuilder $container, $serviceId, $pluginName, array $pluginConfig)
417
    {
418
        $pluginServiceId = $serviceId.'.plugin.'.$pluginName;
419
420
        $definition = class_exists(ChildDefinition::class)
421
            ? new ChildDefinition('httplug.plugin.'.$pluginName)
422
            : new DefinitionDecorator('httplug.plugin.'.$pluginName);
0 ignored issues
show
Deprecated Code introduced by
The class Symfony\Component\Depend...ion\DefinitionDecorator has been deprecated with message: The DefinitionDecorator class is deprecated since version 3.3 and will be removed in 4.0. Use the Symfony\Component\DependencyInjection\ChildDefinition class instead.

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
423
424
        $this->configurePluginByName($pluginName, $definition, $pluginConfig, $container, $pluginServiceId);
425
        $container->setDefinition($pluginServiceId, $definition);
426
427
        return $pluginServiceId;
428
    }
429
430
    /**
431
     * Decorate the plugin service with a ProfilePlugin service.
432
     *
433
     * @param ContainerBuilder $container
434
     * @param string           $pluginServiceId
435
     */
436
    private function decoratePluginWithProfilePlugin(ContainerBuilder $container, $pluginServiceId)
437
    {
438
        $container->register($pluginServiceId.'.debug', ProfilePlugin::class)
439
            ->setDecoratedService($pluginServiceId)
440
            ->setArguments([
441
                new Reference($pluginServiceId.'.debug.inner'),
442
                new Reference('httplug.collector.collector'),
443
                new Reference('httplug.collector.formatter'),
444
            ])
445
            ->setPublic(false);
446
    }
447
448
    /**
449
     * Configure a StackPlugin for a client.
450
     *
451
     * @param ContainerBuilder $container
452
     * @param string           $clientName Client name to display in the profiler.
453
     * @param string           $serviceId  Client service id. Used as base for the StackPlugin service id.
454
     *
455
     * @return string configured StackPlugin service id
456
     */
457
    private function configureStackPlugin(ContainerBuilder $container, $clientName, $serviceId)
458
    {
459
        $pluginServiceId = $serviceId.'.plugin.stack';
460
461
        $definition = class_exists(ChildDefinition::class)
462
            ? new ChildDefinition('httplug.plugin.stack')
463
            : new DefinitionDecorator('httplug.plugin.stack');
0 ignored issues
show
Deprecated Code introduced by
The class Symfony\Component\Depend...ion\DefinitionDecorator has been deprecated with message: The DefinitionDecorator class is deprecated since version 3.3 and will be removed in 4.0. Use the Symfony\Component\DependencyInjection\ChildDefinition class instead.

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
464
465
        $definition->addArgument($clientName);
466
        $container->setDefinition($pluginServiceId, $definition);
467
468
        return $pluginServiceId;
469
    }
470
}
471