Completed
Push — master ( 30ca50...bb6ac8 )
by Márk
09:52
created

HttplugExtension   B

Complexity

Total Complexity 45

Size/Duplication

Total Lines 359
Duplicated Lines 12.26 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 66.67%

Importance

Changes 20
Bugs 4 Features 8
Metric Value
wmc 45
c 20
b 4
f 8
lcom 1
cbo 7
dl 44
loc 359
ccs 72
cts 108
cp 0.6667
rs 8.3673

10 Methods

Rating   Name   Duplication   Size   Complexity  
B configureClient() 20 65 5
B configureAutoDiscoveryClients() 24 38 5
A registerAutoDiscoverableClient() 0 20 2
A registerDebugPlugin() 0 13 1
A getConfiguration() 0 4 1
B load() 0 44 6
B configureClients() 0 22 5
A configurePlugins() 0 18 4
D configurePluginByName() 0 37 10
B configureAuthentication() 0 30 6

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 Symfony\Component\Config\FileLocator;
18
use Symfony\Component\DependencyInjection\ContainerBuilder;
19
use Symfony\Component\DependencyInjection\Definition;
20
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
21
use Symfony\Component\DependencyInjection\Reference;
22
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
23
24
/**
25
 * @author David Buchmann <[email protected]>
26
 * @author Tobias Nyholm <[email protected]>
27
 */
28
class HttplugExtension extends Extension
29
{
30
    /**
31
     * {@inheritdoc}
32 8
     */
33
    public function load(array $configs, ContainerBuilder $container)
34 8
    {
35 8
        $configuration = $this->getConfiguration($configs, $container);
36
        $config = $this->processConfiguration($configuration, $configs);
1 ignored issue
show
Bug introduced by
It seems like $configuration defined by $this->getConfiguration($configs, $container) on line 35 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...
37 8
38
        $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
39 8
40 8
        $loader->load('services.xml');
41
        $loader->load('plugins.xml');
42
43 8
        // Register default services
44 8
        foreach ($config['classes'] as $service => $class) {
45 1
            if (!empty($class)) {
46 1
                $container->register(sprintf('httplug.%s.default', $service), $class);
47 8
            }
48
        }
49
50 8
        // Set main aliases
51 8
        foreach ($config['main_alias'] as $type => $id) {
52 8
            $container->setAlias(sprintf('httplug.%s', $type), $id);
53
        }
54
55 8
        // Configure toolbar
56 5
        if ($config['toolbar']['enabled']) {
57
            $loader->load('data-collector.xml');
58 5
59
            if (!empty($config['toolbar']['formatter'])) {
60
                // Add custom formatter
61
                $container
62
                    ->getDefinition('httplug.collector.debug_collector')
63
                    ->replaceArgument(0, new Reference($config['toolbar']['formatter']))
64
                ;
65
            }
66
67 5
            $container
68 5
                ->getDefinition('httplug.formatter.full_http_message')
69
                ->addArgument($config['toolbar']['captured_body_length'])
70 5
            ;
71
        }
72 8
73 8
        $this->configurePlugins($container, $config['plugins']);
74 8
        $this->configureClients($container, $config);
75 8
        $this->configureAutoDiscoveryClients($container, $config);
76
    }
77
78
    /**
79
     * Configure client services.
80
     *
81
     * @param ContainerBuilder $container
82
     * @param array            $config
83 8
     */
84
    private function configureClients(ContainerBuilder $container, array $config)
85 8
    {
86
        $first = null;
87 8
88 3
        foreach ($config['clients'] as $name => $arguments) {
89
            if ($first === null) {
90 3
                // Save the name of the first configurated client.
91 3
                $first = $name;
92
            }
93 3
94 8
            $this->configureClient($container, $name, $arguments, $config['toolbar']['enabled']);
95
        }
96
97 8
        // If we have clients configured
98
        if ($first !== null) {
99 3
            // If we do not have a client named 'default'
100
            if (!isset($config['clients']['default'])) {
101 3
                // Alias the first client to httplug.client.default
102 3
                $container->setAlias('httplug.client.default', 'httplug.client.'.$first);
103 3
            }
104 8
        }
105
    }
106
107
    /**
108
     * @param ContainerBuilder $container
109
     * @param array            $config
110 8
     */
111
    private function configurePlugins(ContainerBuilder $container, array $config)
112 8
    {
113
        if (!empty($config['authentication'])) {
114
            $this->configureAuthentication($container, $config['authentication']);
115 8
        }
116
        unset($config['authentication']);
117 8
118 8
        foreach ($config as $name => $pluginConfig) {
119
            $pluginId = 'httplug.plugin.'.$name;
120 8
121 8
            if ($pluginConfig['enabled']) {
122 8
                $def = $container->getDefinition($pluginId);
123 8
                $this->configurePluginByName($name, $def, $pluginConfig);
124 8
            } else {
125
                $container->removeDefinition($pluginId);
126 8
            }
127 8
        }
128
    }
129
130
    /**
131
     * @param string     $name
132
     * @param Definition $definition
133
     * @param array      $config
134 8
     */
135
    private function configurePluginByName($name, Definition $definition, array $config)
136
    {
137 8
        switch ($name) {
138
            case 'cache':
139
                $definition
140
                    ->replaceArgument(0, new Reference($config['cache_pool']))
141
                    ->replaceArgument(1, new Reference($config['stream_factory']))
142
                    ->replaceArgument(2, $config['config']);
143 8
                break;
144
            case 'cookie':
145
                $definition->replaceArgument(0, new Reference($config['cookie_jar']));
146 8
                break;
147 8
            case 'decoder':
148 8
                $definition->addArgument($config['use_content_encoding']);
149 8
                break;
150
            case 'history':
151
                $definition->replaceArgument(0, new Reference($config['journal']));
152 8
                break;
153 8
            case 'logger':
154 8
                $definition->replaceArgument(0, new Reference($config['logger']));
155
                if (!empty($config['formatter'])) {
156
                    $definition->replaceArgument(1, new Reference($config['formatter']));
157 8
                }
158 8
                break;
159
            case 'redirect':
160 8
                $definition
161 8
                    ->addArgument($config['preserve_header'])
162 8
                    ->addArgument($config['use_default_for_multiple']);
163 8
                break;
164 8
            case 'retry':
165 8
                $definition->addArgument($config['retry']);
166 8
                break;
167 8
            case 'stopwatch':
168 8
                $definition->replaceArgument(0, new Reference($config['stopwatch']));
169
                break;
170 8
        }
171
    }
172
173
    /**
174
     * @param ContainerBuilder $container
175
     * @param array            $config
176
     */
177
    private function configureAuthentication(ContainerBuilder $container, array $config)
178
    {
179
        foreach ($config as $name => $values) {
180
            $authServiceKey = sprintf('httplug.plugin.authentication.%s.auth', $name);
181
            switch ($values['type']) {
182
                case 'bearer':
183
                    $container->register($authServiceKey, Bearer::class)
184
                        ->addArgument($values['token']);
185
                    break;
186
                case 'basic':
187
                    $container->register($authServiceKey, BasicAuth::class)
188
                        ->addArgument($values['username'])
189
                        ->addArgument($values['password']);
190
                    break;
191
                case 'wsse':
192
                    $container->register($authServiceKey, Wsse::class)
193
                        ->addArgument($values['username'])
194
                        ->addArgument($values['password']);
195
                    break;
196
                case 'service':
197
                    $authServiceKey = $values['service'];
198
                    break;
199
                default:
200
                    throw new \LogicException(sprintf('Unknown authentication type: "%s"', $values['type']));
201
            }
202
203
            $container->register('httplug.plugin.authentication.'.$name, AuthenticationPlugin::class)
204
                ->addArgument(new Reference($authServiceKey));
205
        }
206
    }
207
208
    /**
209
     * @param ContainerBuilder $container
210
     * @param string           $name
211
     * @param array            $arguments
212
     * @param bool             $profiling
213
     */
214
    private function configureClient(ContainerBuilder $container, $name, array $arguments, $profiling)
215
    {
216
        $serviceId = 'httplug.client.'.$name;
217
218
        $pluginClientOptions = [];
219
220
        if ($profiling) {
221
            // Tell the plugin journal what plugins we used
222
            $container
223
                ->getDefinition('httplug.collector.plugin_journal')
224
                ->addMethodCall('setPlugins', [$name, $arguments['plugins']])
225
            ;
226
227
            $debugPluginServiceId = $this->registerDebugPlugin($container, $serviceId);
228
229
            $pluginClientOptions['debug_plugins'] = [new Reference($debugPluginServiceId)];
230
        }
231
232
        $container
233
            ->register($serviceId, DummyClient::class)
234
            ->setFactory([PluginClientFactory::class, 'createPluginClient'])
235
            ->addArgument(
236
                array_map(
237
                    function ($id) {
238
                        return new Reference($id);
239
                    },
240
                    $arguments['plugins']
241
                )
242
            )
243
            ->addArgument(new Reference($arguments['factory']))
244
            ->addArgument($arguments['config'])
245
            ->addArgument($pluginClientOptions)
246
        ;
247
248
249
        /*
250
         * Decorate the client with clients from client-common
251
         */
252 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...
253
            $container
254
                ->register($serviceId.'.flexible', FlexibleHttpClient::class)
255
                ->addArgument(new Reference($serviceId.'.flexible.inner'))
256
                ->setPublic(false)
257
                ->setDecoratedService($serviceId)
258
            ;
259
        }
260
261 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...
262
            $container
263
                ->register($serviceId.'.http_methods', HttpMethodsClient::class)
264
                ->setArguments([new Reference($serviceId.'.http_methods.inner'), new Reference('httplug.message_factory')])
265
                ->setPublic(false)
266
                ->setDecoratedService($serviceId)
267
            ;
268
        }
269
270 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...
271
            $container
272
                ->register($serviceId.'.batch_client', BatchClient::class)
273
                ->setArguments([new Reference($serviceId.'.batch_client.inner')])
274
                ->setPublic(false)
275
                ->setDecoratedService($serviceId)
276
            ;
277
        }
278
    }
279
280
    /**
281
     * Make the user can select what client is used for auto discovery. If none is provided, a service will be created
282
     * by finding a client using auto discovery.
283
     *
284
     * @param ContainerBuilder $container
285
     * @param array            $config
286
     */
287
    private function configureAutoDiscoveryClients(ContainerBuilder $container, array $config)
288
    {
289
        $httpClient = $config['discovery']['client'];
290
291 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...
292
            if ($httpClient === 'auto') {
293
                $httpClient = $this->registerAutoDiscoverableClient(
294
                    $container,
295
                    'auto_discovered_client',
296
                    [HttpClientDiscovery::class, 'find'],
297
                    $config['toolbar']['enabled']
298
                );
299
            }
300
301
            $httpClient = new Reference($httpClient);
302
        }
303
304
        $asyncHttpClient = $config['discovery']['async_client'];
305
306 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...
307
            if ($asyncHttpClient === 'auto') {
308
                $asyncHttpClient = $this->registerAutoDiscoverableClient(
309
                    $container,
310
                    'auto_discovered_async',
311
                    [HttpAsyncClientDiscovery::class, 'find'],
312
                    $config['toolbar']['enabled']
313
                );
314
            }
315
316
            $asyncHttpClient = new Reference($asyncHttpClient);
317
        }
318
319
        $container
320
            ->getDefinition('httplug.strategy')
321
            ->addArgument($httpClient)
322
            ->addArgument($asyncHttpClient)
323
        ;
324
    }
325
326
    /**
327
     * Find a client with auto discovery and return a service Reference to it.
328
     *
329
     * @param ContainerBuilder $container
330
     * @param string           $name
331
     * @param callable         $factory
332
     * @param bool             $profiling
333
     *
334
     * @return string service id
335
     */
336
    private function registerAutoDiscoverableClient(ContainerBuilder $container, $name, $factory, $profiling)
337
    {
338
        $serviceId = 'httplug.auto_discovery.'.$name;
339
340
        $pluginClientOptions = [];
341
342
        if ($profiling) {
343
            $debugPluginServiceId = $this->registerDebugPlugin($container, $serviceId);
344
345
            $pluginClientOptions['debug_plugins'] = [new Reference($debugPluginServiceId)];
346
        }
347
348
        $container
349
            ->register($serviceId, DummyClient::class)
350
            ->setFactory([PluginClientFactory::class, 'createPluginClient'])
351
            ->setArguments([[], $factory, [], $pluginClientOptions])
352
        ;
353
354
        return $serviceId;
355
    }
356
357
    /**
358
     * Create a new plugin service for this client.
359
     *
360
     * @param ContainerBuilder $container
361
     * @param string           $serviceId
362
     *
363
     * @return string
364
     */
365
    private function registerDebugPlugin(ContainerBuilder $container, $serviceId)
366
    {
367
        $serviceIdDebugPlugin = $serviceId.'.debug_plugin';
368
369
        $container
370
            ->register($serviceIdDebugPlugin, DebugPlugin::class)
371
            ->addArgument(new Reference('httplug.collector.debug_collector'))
372
            ->addArgument(substr($serviceId, strrpos($serviceId, '.') + 1))
373
            ->setPublic(false)
374
        ;
375
376
        return $serviceIdDebugPlugin;
377
    }
378
379
    /**
380
     * {@inheritdoc}
381
     */
382
    public function getConfiguration(array $config, ContainerBuilder $container)
383
    {
384
        return new Configuration($container->getParameter('kernel.debug'));
385
    }
386
}
387