Completed
Pull Request — master (#112)
by David
10:10
created

HttplugExtension   B

Complexity

Total Complexity 51

Size/Duplication

Total Lines 430
Duplicated Lines 10.23 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 66.67%

Importance

Changes 25
Bugs 5 Features 9
Metric Value
wmc 51
c 25
b 5
f 9
lcom 1
cbo 7
dl 44
loc 430
ccs 72
cts 108
cp 0.6667
rs 8.3206

11 Methods

Rating   Name   Duplication   Size   Complexity  
B load() 0 44 6
B configureClients() 0 22 5
A configureSharedPlugins() 0 18 4
C configurePluginByName() 0 56 11
B configureAuthentication() 0 30 6
C configureClient() 20 92 9
A createUri() 0 9 1
B configureAutoDiscoveryClients() 24 38 5
B registerAutoDiscoverableClient() 0 26 2
A registerDebugPlugin() 0 13 1
A getConfiguration() 0 4 1

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\Discovery\HttpAsyncClientDiscovery;
10
use Http\Discovery\HttpClientDiscovery;
11
use Http\HttplugBundle\ClientFactory\DummyClient;
12
use Http\HttplugBundle\ClientFactory\PluginClientFactory;
13
use Http\HttplugBundle\Collector\DebugPlugin;
14
use Http\Message\Authentication\BasicAuth;
15
use Http\Message\Authentication\Bearer;
16
use Http\Message\Authentication\Wsse;
17
use Psr\Http\Message\UriInterface;
18
use Symfony\Component\Config\FileLocator;
19
use Symfony\Component\DependencyInjection\ContainerBuilder;
20
use Symfony\Component\DependencyInjection\ContainerInterface;
21
use Symfony\Component\DependencyInjection\Definition;
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 8
     * {@inheritdoc}
34
     */
35 8
    public function load(array $configs, ContainerBuilder $container)
36 8
    {
37
        $configuration = $this->getConfiguration($configs, $container);
38 8
        $config = $this->processConfiguration($configuration, $configs);
1 ignored issue
show
Bug introduced by
It seems like $configuration defined by $this->getConfiguration($configs, $container) on line 37 can be null; however, Symfony\Component\Depend...:processConfiguration() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
39
40 8
        $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
41 8
42
        $loader->load('services.xml');
43
        $loader->load('plugins.xml');
44 8
45 8
        // Register default services
46 1
        foreach ($config['classes'] as $service => $class) {
47 1
            if (!empty($class)) {
48 8
                $container->register(sprintf('httplug.%s.default', $service), $class);
49
            }
50
        }
51 8
52 8
        // Set main aliases
53 8
        foreach ($config['main_alias'] as $type => $id) {
54
            $container->setAlias(sprintf('httplug.%s', $type), $id);
55
        }
56 8
57 5
        // Configure toolbar
58
        if ($config['profiling']['enabled']) {
59 5
            $loader->load('data-collector.xml');
60
61
            if (!empty($config['profiling']['formatter'])) {
62
                // Add custom formatter
63
                $container
64
                    ->getDefinition('httplug.collector.debug_collector')
65
                    ->replaceArgument(0, new Reference($config['profiling']['formatter']))
66
                ;
67
            }
68 5
69 5
            $container
70
                ->getDefinition('httplug.formatter.full_http_message')
71 5
                ->addArgument($config['profiling']['captured_body_length'])
72
            ;
73 8
        }
74 8
75 8
        $this->configureClients($container, $config);
76 8
        $this->configureSharedPlugins($container, $config['plugins']); // must be after clients, as the extra_plugins in clients might use plugins as template that will be removed
77
        $this->configureAutoDiscoveryClients($container, $config);
78
    }
79
80
    /**
81
     * Configure client services.
82
     *
83
     * @param ContainerBuilder $container
84 8
     * @param array            $config
85
     */
86 8
    private function configureClients(ContainerBuilder $container, array $config)
87
    {
88 8
        $first = null;
89 3
90
        foreach ($config['clients'] as $name => $arguments) {
91 3
            if ($first === null) {
92 3
                // Save the name of the first configurated client.
93
                $first = $name;
94 3
            }
95 8
96
            $this->configureClient($container, $name, $arguments, $config['profiling']['enabled']);
97
        }
98 8
99
        // If we have clients configured
100 3
        if ($first !== null) {
101
            // If we do not have a client named 'default'
102 3
            if (!isset($config['clients']['default'])) {
103 3
                // Alias the first client to httplug.client.default
104 3
                $container->setAlias('httplug.client.default', 'httplug.client.'.$first);
105 8
            }
106
        }
107
    }
108
109
    /**
110
     * @param ContainerBuilder $container
111 8
     * @param array            $config
112
     */
113 8
    private function configureSharedPlugins(ContainerBuilder $container, array $config)
114
    {
115
        if (!empty($config['authentication'])) {
116 8
            $this->configureAuthentication($container, $config['authentication']);
117
        }
118 8
        unset($config['authentication']);
119 8
120
        foreach ($config as $name => $pluginConfig) {
121 8
            $pluginId = 'httplug.plugin.'.$name;
122 8
123 8
            if ($pluginConfig['enabled']) {
124 8
                $def = $container->getDefinition('httplug.plugin.'.$name);
125 8
                $this->configurePluginByName($name, $def, $pluginConfig, $container, $pluginId);
126
            } else {
127 8
                $container->removeDefinition($pluginId);
128 8
            }
129
        }
130
    }
131
132
    /**
133
     * @param string           $name
134
     * @param Definition       $definition
135 8
     * @param array            $config
136
     * @param ContainerBuilder $container  In case we need to add additional services for this plugin
137
     * @param string           $serviceId  Service id of the plugin, in case we need to add additional services for this plugin.
138 8
     */
139
    private function configurePluginByName($name, Definition $definition, array $config, ContainerInterface $container, $serviceId)
140
    {
141
        switch ($name) {
142
            case 'cache':
143
                $definition
144 8
                    ->replaceArgument(0, new Reference($config['cache_pool']))
145
                    ->replaceArgument(1, new Reference($config['stream_factory']))
146
                    ->replaceArgument(2, $config['config']);
147 8
                break;
148 8
            case 'cookie':
149 8
                $definition->replaceArgument(0, new Reference($config['cookie_jar']));
150 8
                break;
151
            case 'decoder':
152
                $definition->addArgument([
153 8
                    'use_content_encoding' => $config['use_content_encoding'],
154 8
                ]);
155 8
                break;
156
            case 'history':
157
                $definition->replaceArgument(0, new Reference($config['journal']));
158 8
                break;
159 8
            case 'logger':
160
                $definition->replaceArgument(0, new Reference($config['logger']));
161 8
                if (!empty($config['formatter'])) {
162 8
                    $definition->replaceArgument(1, new Reference($config['formatter']));
163 8
                }
164 8
                break;
165 8
            case 'redirect':
166 8
                $definition->addArgument([
167 8
                    'preserve_header' => $config['preserve_header'],
168 8
                    'use_default_for_multiple' => $config['use_default_for_multiple'],
169 8
                ]);
170
                break;
171 8
            case 'retry':
172
                $definition->addArgument([
173
                    'retries' => $config['retry'],
174
                ]);
175
                break;
176
            case 'stopwatch':
177
                $definition->replaceArgument(0, new Reference($config['stopwatch']));
178
                break;
179
180
            /* client specific plugins */
181
182
            case 'add_host':
183
                $uriService = $serviceId.'.host_uri';
184
                $this->createUri($container, $uriService, $config['host']);
0 ignored issues
show
Compatibility introduced by
$container of type object<Symfony\Component...ion\ContainerInterface> is not a sub-type of object<Symfony\Component...ction\ContainerBuilder>. It seems like you assume a concrete implementation of the interface Symfony\Component\Depend...tion\ContainerInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
185
                $definition->replaceArgument(0, new Reference($uriService));
186
                $definition->replaceArgument(1, [
187
                    'replace' => $config['replace'],
188
                ]);
189
                break;
190
191
            default:
192
                throw new \InvalidArgumentException(sprintf('Internal exception: Plugin %s is not handled', $name));
193
        }
194
    }
195
196
    /**
197
     * @param ContainerBuilder $container
198
     * @param array            $config
199
     */
200
    private function configureAuthentication(ContainerBuilder $container, array $config)
201
    {
202
        foreach ($config as $name => $values) {
203
            $authServiceKey = sprintf('httplug.plugin.authentication.%s.auth', $name);
204
            switch ($values['type']) {
205
                case 'bearer':
206
                    $container->register($authServiceKey, Bearer::class)
207
                        ->addArgument($values['token']);
208
                    break;
209
                case 'basic':
210
                    $container->register($authServiceKey, BasicAuth::class)
211
                        ->addArgument($values['username'])
212
                        ->addArgument($values['password']);
213
                    break;
214
                case 'wsse':
215
                    $container->register($authServiceKey, Wsse::class)
216
                        ->addArgument($values['username'])
217
                        ->addArgument($values['password']);
218
                    break;
219
                case 'service':
220
                    $authServiceKey = $values['service'];
221
                    break;
222
                default:
223
                    throw new \LogicException(sprintf('Unknown authentication type: "%s"', $values['type']));
224
            }
225
226
            $container->register('httplug.plugin.authentication.'.$name, AuthenticationPlugin::class)
227
                ->addArgument(new Reference($authServiceKey));
228
        }
229
    }
230
231
    /**
232
     * @param ContainerBuilder $container
233
     * @param string           $name
234
     * @param array            $arguments
235
     * @param bool             $profiling
236
     */
237
    private function configureClient(ContainerBuilder $container, $name, array $arguments, $profiling)
238
    {
239
        $serviceId = 'httplug.client.'.$name;
240
241
        $pluginClientOptions = [];
242
243
        if ($profiling) {
244
            if (!in_array('httplug.plugin.stopwatch', $arguments['plugins'])) {
245
                // Add the stopwatch plugin
246
                array_unshift($arguments['plugins'], [
247
                    'reference' => [
248
                        'id' => 'httplug.plugin.stopwatch',
249
                    ],
250
                ]);
251
            }
252
253
            // Tell the plugin journal what plugins we used
254
            $container
255
                ->getDefinition('httplug.collector.plugin_journal')
256
                ->addMethodCall('setPlugins', [$name, $arguments['plugins']])
257
            ;
258
259
            $debugPluginServiceId = $this->registerDebugPlugin($container, $serviceId);
260
261
            $pluginClientOptions['debug_plugins'] = [new Reference($debugPluginServiceId)];
262
        }
263
264
        $plugins = [];
265
        foreach ($arguments['plugins'] as $plugin) {
266
            list($name, $pluginConfig) = each($plugin);
267
            if ('reference' === $name) {
268
                $plugins[] = $pluginConfig['id'];
269
            } elseif ('authentication' === $name) {
0 ignored issues
show
Unused Code introduced by
This elseif statement is empty, and could be removed.

This check looks for the bodies of elseif statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These elseif bodies can be removed. If you have an empty elseif but statements in the else branch, consider inverting the condition.

Loading history...
270
                // TODO handle custom authentication
271
            } else {
272
                $pluginServiceId = $serviceId.'.plugin.'.$name;
273
                $def = clone $container->getDefinition('httplug.plugin'.'.'.$name);
274
                $def->setAbstract(false);
275
                $this->configurePluginByName($name, $def, $pluginConfig, $container, $pluginServiceId);
276
                $container->setDefinition($pluginServiceId, $def);
277
                $plugins[] = $pluginServiceId;
278
            }
279
280
        }
281
282
        $container
283
            ->register($serviceId, DummyClient::class)
284
            ->setFactory([PluginClientFactory::class, 'createPluginClient'])
285
            ->addArgument(
286
                array_map(
287
                    function ($id) {
288
                        return new Reference($id);
289
                    },
290
                    $plugins
291
                )
292
            )
293
            ->addArgument(new Reference($arguments['factory']))
294
            ->addArgument($arguments['config'])
295
            ->addArgument($pluginClientOptions)
296
        ;
297
298
299
        /*
300
         * Decorate the client with clients from client-common
301
         */
302 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...
303
            $container
304
                ->register($serviceId.'.flexible', FlexibleHttpClient::class)
305
                ->addArgument(new Reference($serviceId.'.flexible.inner'))
306
                ->setPublic(false)
307
                ->setDecoratedService($serviceId)
308
            ;
309
        }
310
311 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...
312
            $container
313
                ->register($serviceId.'.http_methods', HttpMethodsClient::class)
314
                ->setArguments([new Reference($serviceId.'.http_methods.inner'), new Reference('httplug.message_factory')])
315
                ->setPublic(false)
316
                ->setDecoratedService($serviceId)
317
            ;
318
        }
319
320 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...
321
            $container
322
                ->register($serviceId.'.batch_client', BatchClient::class)
323
                ->setArguments([new Reference($serviceId.'.batch_client.inner')])
324
                ->setPublic(false)
325
                ->setDecoratedService($serviceId)
326
            ;
327
        }
328
    }
329
330
    /**
331
     * Create a URI object with the default URI factory.
332
     *
333
     * @param ContainerBuilder $container
334
     * @param string           $serviceId Name of the private service to create
335
     * @param string           $uri       String representation of the URI
336
     */
337
    private function createUri(ContainerBuilder $container, $serviceId, $uri)
338
    {
339
        $container
340
            ->register($serviceId, UriInterface::class)
341
            ->setPublic(false)
342
            ->setFactory([new Reference('httplug.uri_factory'), 'createUri'])
343
            ->addArgument($uri)
344
        ;
345
    }
346
347
    /**
348
     * Make the user can select what client is used for auto discovery. If none is provided, a service will be created
349
     * by finding a client using auto discovery.
350
     *
351
     * @param ContainerBuilder $container
352
     * @param array            $config
353
     */
354
    private function configureAutoDiscoveryClients(ContainerBuilder $container, array $config)
355
    {
356
        $httpClient = $config['discovery']['client'];
357
358 View Code Duplication
        if (!empty($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...
359
            if ($httpClient === 'auto') {
360
                $httpClient = $this->registerAutoDiscoverableClient(
361
                    $container,
362
                    'auto_discovered_client',
363
                    [HttpClientDiscovery::class, 'find'],
364
                    $config['profiling']['enabled']
365
                );
366
            }
367
368
            $httpClient = new Reference($httpClient);
369
        }
370
371
        $asyncHttpClient = $config['discovery']['async_client'];
372
373 View Code Duplication
        if (!empty($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...
374
            if ($asyncHttpClient === 'auto') {
375
                $asyncHttpClient = $this->registerAutoDiscoverableClient(
376
                    $container,
377
                    'auto_discovered_async',
378
                    [HttpAsyncClientDiscovery::class, 'find'],
379
                    $config['profiling']['enabled']
380
                );
381
            }
382
383
            $asyncHttpClient = new Reference($asyncHttpClient);
384
        }
385
386
        $container
387
            ->getDefinition('httplug.strategy')
388
            ->addArgument($httpClient)
389
            ->addArgument($asyncHttpClient)
390
        ;
391
    }
392
393
    /**
394
     * Find a client with auto discovery and return a service Reference to it.
395
     *
396
     * @param ContainerBuilder $container
397
     * @param string           $name
398
     * @param callable         $factory
399
     * @param bool             $profiling
400
     *
401
     * @return string service id
402
     */
403
    private function registerAutoDiscoverableClient(ContainerBuilder $container, $name, $factory, $profiling)
404
    {
405
        $serviceId = 'httplug.auto_discovery.'.$name;
406
407
        $pluginClientOptions = [];
408
409
        if ($profiling) {
410
            // Tell the plugin journal what plugins we used
411
            $container
412
                ->getDefinition('httplug.collector.plugin_journal')
413
                ->addMethodCall('setPlugins', [$name, ['httplug.plugin.stopwatch']])
414
            ;
415
416
            $debugPluginServiceId = $this->registerDebugPlugin($container, $serviceId);
417
418
            $pluginClientOptions['debug_plugins'] = [new Reference($debugPluginServiceId)];
419
        }
420
421
        $container
422
            ->register($serviceId, DummyClient::class)
423
            ->setFactory([PluginClientFactory::class, 'createPluginClient'])
424
            ->setArguments([[new Reference('httplug.plugin.stopwatch')], $factory, [], $pluginClientOptions])
425
        ;
426
427
        return $serviceId;
428
    }
429
430
    /**
431
     * Create a new plugin service for this client.
432
     *
433
     * @param ContainerBuilder $container
434
     * @param string           $serviceId
435
     *
436
     * @return string
437
     */
438
    private function registerDebugPlugin(ContainerBuilder $container, $serviceId)
439
    {
440
        $serviceIdDebugPlugin = $serviceId.'.debug_plugin';
441
442
        $container
443
            ->register($serviceIdDebugPlugin, DebugPlugin::class)
444
            ->addArgument(new Reference('httplug.collector.debug_collector'))
445
            ->addArgument(substr($serviceId, strrpos($serviceId, '.') + 1))
446
            ->setPublic(false)
447
        ;
448
449
        return $serviceIdDebugPlugin;
450
    }
451
452
    /**
453
     * {@inheritdoc}
454
     */
455
    public function getConfiguration(array $config, ContainerBuilder $container)
456
    {
457
        return new Configuration($container->getParameter('kernel.debug'));
458
    }
459
}
460