Completed
Pull Request — master (#112)
by David
07:41 queued 04:44
created

HttplugExtension::configurePluginByName()   C

Complexity

Conditions 15
Paths 15

Size

Total Lines 62
Code Lines 50

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 42
CRAP Score 17.9725

Importance

Changes 5
Bugs 1 Features 1
Metric Value
c 5
b 1
f 1
dl 0
loc 62
ccs 42
cts 55
cp 0.7635
rs 6.1517
cc 15
eloc 50
nc 15
nop 5
crap 17.9725

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