Completed
Push — master ( 756441...8c9149 )
by David
06:21
created

HttplugExtension   C

Complexity

Total Complexity 56

Size/Duplication

Total Lines 405
Duplicated Lines 9.38 %

Coupling/Cohesion

Components 1
Dependencies 9

Test Coverage

Coverage 77.45%

Importance

Changes 0
Metric Value
wmc 56
lcom 1
cbo 9
dl 38
loc 405
ccs 158
cts 204
cp 0.7745
rs 6.5957
c 0
b 0
f 0

10 Methods

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