Completed
Pull Request — master (#128)
by Fabien
09:03
created

HttplugExtension::configureClient()   D

Complexity

Conditions 10
Paths 96

Size

Total Lines 96
Code Lines 63

Duplication

Lines 20
Ratio 20.83 %

Code Coverage

Tests 0
CRAP Score 110

Importance

Changes 0
Metric Value
dl 20
loc 96
rs 4.9494
c 0
b 0
f 0
ccs 0
cts 0
cp 0
cc 10
eloc 63
nc 96
nop 4
crap 110

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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