Completed
Push — master ( baadcd...d3edb4 )
by Tobias
09:03
created

HttplugExtension   C

Complexity

Total Complexity 55

Size/Duplication

Total Lines 404
Duplicated Lines 9.41 %

Coupling/Cohesion

Components 1
Dependencies 10

Test Coverage

Coverage 85.59%

Importance

Changes 0
Metric Value
wmc 55
lcom 1
cbo 10
dl 38
loc 404
ccs 101
cts 118
cp 0.8559
rs 6.8
c 0
b 0
f 0

10 Methods

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