Completed
Push — master ( 217056...f710b9 )
by Tobias
12:16
created

HttplugExtension   B

Complexity

Total Complexity 54

Size/Duplication

Total Lines 399
Duplicated Lines 9.52 %

Coupling/Cohesion

Components 1
Dependencies 10

Test Coverage

Coverage 81.47%

Importance

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