Completed
Pull Request — master (#97)
by Tobias
09:36
created

HttplugExtension   B

Complexity

Total Complexity 41

Size/Duplication

Total Lines 287
Duplicated Lines 12.2 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 61.9%

Importance

Changes 19
Bugs 4 Features 7
Metric Value
wmc 41
c 19
b 4
f 7
lcom 1
cbo 7
dl 35
loc 287
ccs 26
cts 42
cp 0.619
rs 8.2769

8 Methods

Rating   Name   Duplication   Size   Complexity  
B configureClient() 12 41 3
B configureAutoDiscoveryClients() 23 37 5
A registerAutoDiscoverableClient() 0 10 1
C load() 0 30 7
B configureClients() 0 25 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\FlexibleHttpClient;
6
use Http\Client\Common\HttpMethodsClient;
7
use Http\Client\Common\Plugin\AuthenticationPlugin;
8
use Http\Discovery\HttpAsyncClientDiscovery;
9
use Http\Discovery\HttpClientDiscovery;
10
use Http\HttplugBundle\ClientFactory\DummyClient;
11
use Http\HttplugBundle\ClientFactory\PluginClientFactory;
12
use Http\Message\Authentication\BasicAuth;
13
use Http\Message\Authentication\Bearer;
14
use Http\Message\Authentication\Wsse;
15
use Symfony\Component\Config\FileLocator;
16
use Symfony\Component\DependencyInjection\ContainerBuilder;
17
use Symfony\Component\DependencyInjection\Definition;
18
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
19
use Symfony\Component\DependencyInjection\Reference;
20
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
21
22
/**
23
 * @author David Buchmann <[email protected]>
24
 * @author Tobias Nyholm <[email protected]>
25
 */
26
class HttplugExtension extends Extension
27
{
28
    /**
29
     * {@inheritdoc}
30
     */
31
    public function load(array $configs, ContainerBuilder $container)
32 3
    {
33
        $configuration = $this->getConfiguration($configs, $container);
34 3
        $config = $this->processConfiguration($configuration, $configs);
0 ignored issues
show
Documentation introduced by
$configuration is of type object|null, but the function expects a object<Symfony\Component...ConfigurationInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
35 3
36
        $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
37 3
38
        $loader->load('services.xml');
39 3
        $loader->load('plugins.xml');
40 3
41
        foreach ($config['classes'] as $service => $class) {
42 3
            if (!empty($class)) {
43
                $container->register(sprintf('httplug.%s.default', $service), $class);
44 3
            }
45
        }
46
47
        // Set main aliases
48
        foreach ($config['main_alias'] as $type => $id) {
49
            $container->setAlias(sprintf('httplug.%s', $type), $id);
50
        }
51
52
        $this->configurePlugins($container, $config['plugins']);
53
        $serviceIds = $this->configureClients($container, $config);
54
        $autoServiceIds = $this->configureAutoDiscoveryClients($container, $config);
55
56
        $toolbar = is_bool($config['toolbar']['enabled']) ? $config['toolbar']['enabled'] : $container->hasParameter('kernel.debug') && $container->getParameter('kernel.debug');
57 3
        if ($toolbar) {
58 3
            (new ProfilerExtension())->load($config, $container, array_unique(array_merge($serviceIds, $autoServiceIds)));
59 1
        }
60 1
    }
61 3
62
    /**
63
     * Configure client services.
64 3
     *
65 3
     * @param ContainerBuilder $container
66 3
     * @param array            $config
67
     *
68 3
     * @return array with client service names
69 3
     */
70 3
    private function configureClients(ContainerBuilder $container, array $config)
71 3
    {
72
        $serviceIds = [];
73
        $first = null;
74
75
        foreach ($config['clients'] as $name => $arguments) {
76
            if ($first === null) {
77
                // Save the name of the first configurated client.
78
                $first = $name;
79
            }
80 3
81
            $serviceIds[] = $this->configureClient($container, $name, $arguments);
82
        }
83 3
84
        // If we have clients configured
85 3
        if ($first !== null) {
86
            // If we do not have a client named 'default'
87
            if (!isset($config['clients']['default'])) {
88
                // Alias the first client to httplug.client.default
89
                $container->setAlias('httplug.client.default', 'httplug.client.'.$first);
90
            }
91
        }
92 3
93
        return $serviceIds;
94
    }
95 3
96
    /**
97
     * @param ContainerBuilder $container
98
     * @param array            $config
99
     */
100 3
    private function configurePlugins(ContainerBuilder $container, array $config)
101
    {
102
        if (!empty($config['authentication'])) {
103
            $this->configureAuthentication($container, $config['authentication']);
104
        }
105
        unset($config['authentication']);
106
107
        foreach ($config as $name => $pluginConfig) {
108
            $pluginId = 'httplug.plugin.'.$name;
109
110
            if ($pluginConfig['enabled']) {
111
                $def = $container->getDefinition($pluginId);
112
                $this->configurePluginByName($name, $def, $pluginConfig);
113
            } else {
114
                $container->removeDefinition($pluginId);
115
            }
116
        }
117
    }
118
119
    /**
120
     * @param string     $name
121
     * @param Definition $definition
122
     * @param array      $config
123
     */
124
    private function configurePluginByName($name, Definition $definition, array $config)
125
    {
126
        switch ($name) {
127
            case 'cache':
128
                $definition
129
                    ->replaceArgument(0, new Reference($config['cache_pool']))
130
                    ->replaceArgument(1, new Reference($config['stream_factory']))
131
                    ->replaceArgument(2, $config['config']);
132
                break;
133
            case 'cookie':
134
                $definition->replaceArgument(0, new Reference($config['cookie_jar']));
135
                break;
136
            case 'decoder':
137
                $definition->addArgument($config['use_content_encoding']);
138
                break;
139
            case 'history':
140
                $definition->replaceArgument(0, new Reference($config['journal']));
141
                break;
142
            case 'logger':
143
                $definition->replaceArgument(0, new Reference($config['logger']));
144
                if (!empty($config['formatter'])) {
145
                    $definition->replaceArgument(1, new Reference($config['formatter']));
146
                }
147
                break;
148
            case 'redirect':
149
                $definition
150
                    ->addArgument($config['preserve_header'])
151
                    ->addArgument($config['use_default_for_multiple']);
152
                break;
153
            case 'retry':
154
                $definition->addArgument($config['retry']);
155
                break;
156
            case 'stopwatch':
157
                $definition->replaceArgument(0, new Reference($config['stopwatch']));
158
                break;
159
        }
160
    }
161
162
    /**
163
     * @param ContainerBuilder $container
164
     * @param array            $config
165
     */
166
    private function configureAuthentication(ContainerBuilder $container, array $config)
167
    {
168
        foreach ($config as $name => $values) {
169
            $authServiceKey = sprintf('httplug.plugin.authentication.%s.auth', $name);
170
            switch ($values['type']) {
171
                case 'bearer':
172
                    $container->register($authServiceKey, Bearer::class)
173
                        ->addArgument($values['token']);
174
                    break;
175
                case 'basic':
176
                    $container->register($authServiceKey, BasicAuth::class)
177
                        ->addArgument($values['username'])
178
                        ->addArgument($values['password']);
179
                    break;
180
                case 'wsse':
181
                    $container->register($authServiceKey, Wsse::class)
182
                        ->addArgument($values['username'])
183
                        ->addArgument($values['password']);
184
                    break;
185
                case 'service':
186
                    $authServiceKey = $values['service'];
187
                    break;
188
                default:
189
                    throw new \LogicException(sprintf('Unknown authentication type: "%s"', $values['type']));
190
            }
191
192
            $container->register('httplug.plugin.authentication.'.$name, AuthenticationPlugin::class)
193
                ->addArgument(new Reference($authServiceKey));
194
        }
195
    }
196
197
    /**
198
     * @param ContainerBuilder $container
199
     * @param string           $name
200
     * @param array            $arguments
201
     *
202
     * @return string The service id of the client.
203
     */
204
    private function configureClient(ContainerBuilder $container, $name, array $arguments)
205
    {
206
        $serviceId = 'httplug.client.'.$name;
207
        $definition = $container->register($serviceId, DummyClient::class);
208
        $definition->setFactory([PluginClientFactory::class, 'createPluginClient'])
209
            ->addArgument(
210
                array_map(
211
                    function ($id) {
212
                        return new Reference($id);
213
                    },
214
                    $arguments['plugins']
215
                )
216
            )
217
            ->addArgument(new Reference($arguments['factory']))
218
            ->addArgument($arguments['config'])
219
            ->addArgument([])
220
        ;
221
222
        // Tell the plugin journal what plugins we used
223
        $container->getDefinition('httplug.collector.plugin_journal')
224
            ->addMethodCall('setPlugins', [$name, $arguments['plugins']]);
225
226
        /*
227
         * Decorate the client with clients from client-common
228
         */
229 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...
230
            $container->register($serviceId.'.flexible', FlexibleHttpClient::class)
231
                ->addArgument(new Reference($serviceId.'.flexible.inner'))
232
                ->setPublic(false)
233
                ->setDecoratedService($serviceId);
234
        }
235
236 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...
237
            $container->register($serviceId.'.http_methods', HttpMethodsClient::class)
238
                ->setArguments([new Reference($serviceId.'.http_methods.inner'), new Reference('httplug.message_factory')])
239
                ->setPublic(false)
240
                ->setDecoratedService($serviceId);
241
        }
242
243
        return $serviceId;
244
    }
245
246
    /**
247
     * Make the user can select what client is used for auto discovery. If none is provided, a service will be created
248
     * by finding a client using auto discovery.
249
     *
250
     * @param ContainerBuilder $container
251
     * @param array            $config
252
     *
253
     * @return array of service ids.
254
     */
255
    private function configureAutoDiscoveryClients(ContainerBuilder $container, array $config)
256
    {
257
        $serviceIds = [];
258
259
        $httpClient = $config['discovery']['client'];
260 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...
261
            if ($httpClient === 'auto') {
262
                $httpClient = $this->registerAutoDiscoverableClient(
263
                    $container,
264
                    'auto_discovered_client',
265
                    [HttpClientDiscovery::class, 'find']
266
                );
267
            }
268
269
            $serviceIds[] = $httpClient;
270
            $httpClient = new Reference($httpClient);
271
        }
272
273
        $asyncHttpClient = $config['discovery']['async_client'];
274 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...
275
            if ($asyncHttpClient === 'auto') {
276
                $asyncHttpClient = $this->registerAutoDiscoverableClient(
277
                    $container,
278
                    'auto_discovered_async',
279
                    [HttpAsyncClientDiscovery::class, 'find']
280
                );
281
            }
282
            $serviceIds[] = $asyncHttpClient;
283
            $asyncHttpClient = new Reference($httpClient);
284
        }
285
286
        $container->getDefinition('httplug.strategy')
287
            ->addArgument($httpClient)
288
            ->addArgument($asyncHttpClient);
289
290
        return $serviceIds;
291
    }
292
293
    /**
294
     * Find a client with auto discovery and return a service Reference to it.
295
     *
296
     * @param ContainerBuilder $container
297
     * @param string           $name
298
     * @param callable         $factory
299
     *
300
     * @return string service id
301
     */
302
    private function registerAutoDiscoverableClient(ContainerBuilder $container, $name, $factory)
303
    {
304
        $serviceId = 'httplug.auto_discovery.'.$name;
305
        $definition = $container->register($serviceId, DummyClient::class);
306
        $definition
307
            ->setFactory([PluginClientFactory::class, 'createPluginClient'])
308
            ->setArguments([[], $factory, [], []]);
309
310
        return $serviceId;
311
    }
312
}
313