Completed
Push — master ( 1b0026...e1710b )
by Fabien
34:02
created

HttplugExtension::configureAutoDiscoveryClients()   D

Complexity

Conditions 10
Paths 27

Size

Total Lines 40
Code Lines 25

Duplication

Lines 20
Ratio 50 %

Code Coverage

Tests 0
CRAP Score 110

Importance

Changes 0
Metric Value
dl 20
loc 40
ccs 0
cts 0
cp 0
rs 4.8196
c 0
b 0
f 0
cc 10
eloc 25
nc 27
nop 2
crap 110

How to fix   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\HttplugBundle\ClientFactory\DummyClient;
10
use Http\HttplugBundle\ClientFactory\PluginClientFactory;
11
use Http\HttplugBundle\Collector\ProfilePlugin;
12
use Http\Message\Authentication\BasicAuth;
13
use Http\Message\Authentication\Bearer;
14
use Http\Message\Authentication\Wsse;
15
use Psr\Http\Message\UriInterface;
16
use Symfony\Component\Config\FileLocator;
17
use Symfony\Component\DependencyInjection\ChildDefinition;
18
use Symfony\Component\DependencyInjection\ContainerBuilder;
19
use Symfony\Component\DependencyInjection\Definition;
20
use Symfony\Component\DependencyInjection\DefinitionDecorator;
21
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
22
use Symfony\Component\DependencyInjection\Reference;
23
use Symfony\Component\DependencyInjection\ServiceLocator;
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 15
    public function load(array $configs, ContainerBuilder $container)
36
    {
37 15
        $configuration = $this->getConfiguration($configs, $container);
38 15
        $config = $this->processConfiguration($configuration, $configs);
39
40 15
        $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
41
42 15
        $loader->load('services.xml');
43 15
        $loader->load('plugins.xml');
44
45
        // Register default services
46 15
        foreach ($config['classes'] as $service => $class) {
47 15
            if (!empty($class)) {
48 1
                $container->register(sprintf('httplug.%s.default', $service), $class);
49 1
            }
50 15
        }
51
52
        // Set main aliases
53 15
        foreach ($config['main_alias'] as $type => $id) {
54 15
            $container->setAlias(sprintf('httplug.%s', $type), $id);
55 15
        }
56
57
        // Configure toolbar
58 15
        $profilingEnabled = $this->isConfigEnabled($container, $config['profiling']);
59 15
        if ($profilingEnabled) {
60 12
            $loader->load('data-collector.xml');
61
62 12
            if (!empty($config['profiling']['formatter'])) {
63
                // Add custom formatter
64
                $container
65 1
                    ->getDefinition('httplug.collector.formatter')
66 1
                    ->replaceArgument(0, new Reference($config['profiling']['formatter']))
67
                ;
68 1
            }
69
70
            $container
71 12
                ->getDefinition('httplug.formatter.full_http_message')
72 12
                ->addArgument($config['profiling']['captured_body_length'])
73
            ;
74 12
        }
75
76 15
        $this->configureClients($container, $config, $profilingEnabled);
77 15
        $this->configurePlugins($container, $config['plugins']); // must be after clients, as clients.X.plugins might use plugins as templates that will be removed
78 15
        $this->configureAutoDiscoveryClients($container, $config);
79 15
    }
80
81
    /**
82
     * Configure client services.
83
     *
84
     * @param ContainerBuilder $container
85
     * @param array            $config
86
     * @param bool             $profiling
87
     */
88 15
    private function configureClients(ContainerBuilder $container, array $config, $profiling)
0 ignored issues
show
Unused Code introduced by
The parameter $profiling is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
89
    {
90 15
        $first = null;
91 15
        $clients = [];
92
93 15
        foreach ($config['clients'] as $name => $arguments) {
94 8
            if ($first === null) {
95
                // Save the name of the first configured client.
96 8
                $first = $name;
97 8
            }
98
99 8
            $this->configureClient($container, $name, $arguments, $this->isConfigEnabled($container, $config['profiling']));
100 8
            $clients[] = $name;
101 15
        }
102
103
        // If we have clients configured
104 15
        if ($first !== null) {
105
            // If we do not have a client named 'default'
106 8
            if (!isset($config['clients']['default'])) {
107
                // Alias the first client to httplug.client.default
108 8
                $container->setAlias('httplug.client.default', 'httplug.client.'.$first);
109 8
            }
110 8
        }
111 15
    }
112
113
    /**
114
     * Configure all Httplug plugins or remove their service definition.
115
     *
116
     * @param ContainerBuilder $container
117
     * @param array            $config
118
     */
119 15
    private function configurePlugins(ContainerBuilder $container, array $config)
120
    {
121 15
        if (!empty($config['authentication'])) {
122
            $this->configureAuthentication($container, $config['authentication']);
123
        }
124 15
        unset($config['authentication']);
125
126 15
        foreach ($config as $name => $pluginConfig) {
127 15
            $pluginId = 'httplug.plugin.'.$name;
128
129 15
            if ($this->isConfigEnabled($container, $pluginConfig)) {
130 15
                $def = $container->getDefinition($pluginId);
131 15
                $this->configurePluginByName($name, $def, $pluginConfig, $container, $pluginId);
132 15
            } else {
133 15
                $container->removeDefinition($pluginId);
134
            }
135 15
        }
136 15
    }
137
138
    /**
139
     * @param string           $name
140
     * @param Definition       $definition
141
     * @param array            $config
142
     * @param ContainerBuilder $container  In case we need to add additional services for this plugin
143
     * @param string           $serviceId  Service id of the plugin, in case we need to add additional services for this plugin.
144
     */
145 15
    private function configurePluginByName($name, Definition $definition, array $config, ContainerBuilder $container, $serviceId)
146
    {
147
        switch ($name) {
148 15
            case 'cache':
149
                $definition
150
                    ->replaceArgument(0, new Reference($config['cache_pool']))
151
                    ->replaceArgument(1, new Reference($config['stream_factory']))
152
                    ->replaceArgument(2, $config['config']);
153
                break;
154 15
            case 'cookie':
155
                $definition->replaceArgument(0, new Reference($config['cookie_jar']));
156
                break;
157 15
            case 'decoder':
158 15
                $definition->addArgument([
159 15
                    'use_content_encoding' => $config['use_content_encoding'],
160 15
                ]);
161 15
                break;
162 15
            case 'history':
163
                $definition->replaceArgument(0, new Reference($config['journal']));
164
                break;
165 15
            case 'logger':
166 15
                $definition->replaceArgument(0, new Reference($config['logger']));
167 15
                if (!empty($config['formatter'])) {
168
                    $definition->replaceArgument(1, new Reference($config['formatter']));
169
                }
170 15
                break;
171 15
            case 'redirect':
172 15
                $definition->addArgument([
173 15
                    'preserve_header' => $config['preserve_header'],
174 15
                    'use_default_for_multiple' => $config['use_default_for_multiple'],
175 15
                ]);
176 15
                break;
177 15
            case 'retry':
178 15
                $definition->addArgument([
179 15
                    'retries' => $config['retry'],
180 15
                ]);
181 15
                break;
182 15
            case 'stopwatch':
183 15
                $definition->replaceArgument(0, new Reference($config['stopwatch']));
184 15
                break;
185
186
            /* client specific plugins */
187
188 5
            case 'add_host':
189 5
                $uriService = $serviceId.'.host_uri';
190 5
                $this->createUri($container, $uriService, $config['host']);
191 5
                $definition->replaceArgument(0, new Reference($uriService));
192 5
                $definition->replaceArgument(1, [
193 5
                    'replace' => $config['replace'],
194 5
                ]);
195 5
                break;
196 1
            case 'header_append':
197 1
            case 'header_defaults':
198 1
            case 'header_set':
199 1
            case 'header_remove':
200 1
                $definition->replaceArgument(0, $config['headers']);
201 1
                break;
202
203
            default:
204
                throw new \InvalidArgumentException(sprintf('Internal exception: Plugin %s is not handled', $name));
205
        }
206 15
    }
207
208
    /**
209
     * @param ContainerBuilder $container
210
     * @param array            $config
211
     *
212
     * @return array List of service ids for the authentication plugins.
213
     */
214 5
    private function configureAuthentication(ContainerBuilder $container, array $config, $servicePrefix = 'httplug.plugin.authentication')
215
    {
216 5
        $pluginServices = [];
217
218 5
        foreach ($config as $name => $values) {
219 5
            $authServiceKey = sprintf($servicePrefix.'.%s.auth', $name);
220 5
            switch ($values['type']) {
221 5
                case 'bearer':
222
                    $container->register($authServiceKey, Bearer::class)
223
                        ->addArgument($values['token']);
224
                    break;
225 5
                case 'basic':
226
                    $container->register($authServiceKey, BasicAuth::class)
227 5
                        ->addArgument($values['username'])
228 5
                        ->addArgument($values['password']);
229 5
                    break;
230
                case 'wsse':
231
                    $container->register($authServiceKey, Wsse::class)
232
                        ->addArgument($values['username'])
233
                        ->addArgument($values['password']);
234
                    break;
235
                case 'service':
236
                    $authServiceKey = $values['service'];
237
                    break;
238
                default:
239
                    throw new \LogicException(sprintf('Unknown authentication type: "%s"', $values['type']));
240 5
            }
241
242 5
            $pluginServiceKey = $servicePrefix.'.'.$name;
243
            $container->register($pluginServiceKey, AuthenticationPlugin::class)
244
                ->addArgument(new Reference($authServiceKey))
245
            ;
246
            $pluginServices[] = $pluginServiceKey;
247
        }
248
249
        return $pluginServices;
250
    }
251
252
    /**
253
     * @param ContainerBuilder $container
254
     * @param string           $clientName
255
     * @param array            $arguments
256
     * @param bool             $profiling
257
     */
258
    private function configureClient(ContainerBuilder $container, $clientName, array $arguments, $profiling)
259
    {
260
        $serviceId = 'httplug.client.'.$clientName;
261
262
        $plugins = [];
263
        foreach ($arguments['plugins'] as $plugin) {
264
            list($pluginName, $pluginConfig) = each($plugin);
265
            if ('reference' === $pluginName) {
266
                $plugins[] = $pluginConfig['id'];
267
            } elseif ('authentication' === $pluginName) {
268
                $plugins = array_merge($plugins, $this->configureAuthentication($container, $pluginConfig, $serviceId.'.authentication'));
269
            } else {
270
                $plugins[] = $this->configurePlugin($container, $serviceId, $pluginName, $pluginConfig);
271
            }
272
        }
273
274
        $pluginClientOptions = [];
275
        if ($profiling) {
276
            //Decorate each plugin with a ProfilePlugin instance.
277
            foreach ($plugins as $pluginServiceId) {
278
                $this->decoratePluginWithProfilePlugin($container, $pluginServiceId);
279
            }
280
281
            // To profile the requests, add a StackPlugin as first plugin in the chain.
282
            $stackPluginId = $this->configureStackPlugin($container, $clientName, $serviceId);
283
            array_unshift($plugins, $stackPluginId);
284
        }
285
286
        $container
287
            ->register($serviceId, DummyClient::class)
288
            ->setFactory([PluginClientFactory::class, 'createPluginClient'])
289
            ->addArgument(
290
                array_map(
291
                    function ($id) {
292
                        return new Reference($id);
293
                    },
294
                    $plugins
295
                )
296
            )
297
            ->addArgument(new Reference($arguments['factory']))
298
            ->addArgument($arguments['config'])
299
            ->addArgument($pluginClientOptions)
300
        ;
301
302
        /*
303
         * Decorate the client with clients from client-common
304
         */
305 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...
306
            $container
307
                ->register($serviceId.'.flexible', FlexibleHttpClient::class)
308
                ->addArgument(new Reference($serviceId.'.flexible.inner'))
309
                ->setPublic(false)
310
                ->setDecoratedService($serviceId)
311
            ;
312
        }
313
314 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...
315
            $container
316
                ->register($serviceId.'.http_methods', HttpMethodsClient::class)
317
                ->setArguments([new Reference($serviceId.'.http_methods.inner'), new Reference('httplug.message_factory')])
318
                ->setPublic(false)
319
                ->setDecoratedService($serviceId)
320
            ;
321
        }
322
323 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...
324
            $container
325
                ->register($serviceId.'.batch_client', BatchClient::class)
326
                ->setArguments([new Reference($serviceId.'.batch_client.inner')])
327
                ->setPublic(false)
328
                ->setDecoratedService($serviceId)
329
            ;
330
        }
331
    }
332
333
    /**
334
     * Create a URI object with the default URI factory.
335
     *
336
     * @param ContainerBuilder $container
337
     * @param string           $serviceId Name of the private service to create
338
     * @param string           $uri       String representation of the URI
339
     */
340
    private function createUri(ContainerBuilder $container, $serviceId, $uri)
341
    {
342
        $container
343
            ->register($serviceId, UriInterface::class)
344
            ->setPublic(false)
345
            ->setFactory([new Reference('httplug.uri_factory'), 'createUri'])
346
            ->addArgument($uri)
347
        ;
348
    }
349
350
    /**
351
     * Make the user can select what client is used for auto discovery. If none is provided, a service will be created
352
     * by finding a client using auto discovery.
353
     *
354
     * @param ContainerBuilder $container
355
     * @param array            $config
356
     */
357
    private function configureAutoDiscoveryClients(ContainerBuilder $container, array $config)
358
    {
359
        $httpClient = $config['discovery']['client'];
360 View Code Duplication
        if ($httpClient !== 'auto') {
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...
361
            $container->removeDefinition('httplug.auto_discovery.auto_discovered_client');
362
            $container->removeDefinition('httplug.collector.auto_discovered_client');
363
            $container->removeDefinition('httplug.auto_discovery.auto_discovered_client.plugin');
364
365
            if (!empty($httpClient)) {
366
                $container->setAlias('httplug.auto_discovery.auto_discovered_client', $httpClient);
367
                $container->getAlias('httplug.auto_discovery.auto_discovered_client')->setPublic(false);
368
            }
369
        }
370
371
        $asyncHttpClient = $config['discovery']['async_client'];
372 View Code Duplication
        if ($asyncHttpClient !== 'auto') {
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...
373
            $container->removeDefinition('httplug.auto_discovery.auto_discovered_async');
374
            $container->removeDefinition('httplug.collector.auto_discovered_async');
375
            $container->removeDefinition('httplug.auto_discovery.auto_discovered_async.plugin');
376
377
            if (!empty($asyncHttpClient)) {
378
                $container->setAlias('httplug.auto_discovery.auto_discovered_async', $asyncHttpClient);
379
                $container->getAlias('httplug.auto_discovery.auto_discovered_async')->setPublic(false);
380
            }
381
        }
382
383
        if (null === $httpClient && null === $asyncHttpClient) {
384
            $container->removeDefinition('httplug.strategy');
385
386
            return;
387
        }
388
389
        if (!class_exists(ServiceLocator::class)) {
390
            $container->getDefinition('httplug.strategy')->setArguments([
391
                new Reference('service_container'),
392
                in_array($httpClient, ['auto', null], true) ? 'httplug.auto_discovery.auto_discovered_client' : $httpClient,
393
                in_array($asyncHttpClient, ['auto', null], true) ? 'httplug.auto_discovery.auto_discovered_async' : $asyncHttpClient,
394
            ]);
395
        }
396
    }
397
398
    /**
399
     * {@inheritdoc}
400
     */
401
    public function getConfiguration(array $config, ContainerBuilder $container)
402
    {
403
        return new Configuration($container->getParameter('kernel.debug'));
404
    }
405
406
    /**
407
     * Configure a plugin using the parent definition from plugins.xml.
408
     *
409
     * @param ContainerBuilder $container
410
     * @param string           $serviceId
411
     * @param string           $pluginName
412
     * @param array            $pluginConfig
413
     *
414
     * @return string configured service id
415
     */
416
    private function configurePlugin(ContainerBuilder $container, $serviceId, $pluginName, array $pluginConfig)
417
    {
418
        $pluginServiceId = $serviceId.'.plugin.'.$pluginName;
419
420
        $definition = class_exists(ChildDefinition::class)
421
            ? new ChildDefinition('httplug.plugin.'.$pluginName)
422
            : new DefinitionDecorator('httplug.plugin.'.$pluginName);
0 ignored issues
show
Deprecated Code introduced by
The class Symfony\Component\Depend...ion\DefinitionDecorator has been deprecated with message: The DefinitionDecorator class is deprecated since version 3.3 and will be removed in 4.0. Use the Symfony\Component\DependencyInjection\ChildDefinition class instead.

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
423
424
        $this->configurePluginByName($pluginName, $definition, $pluginConfig, $container, $pluginServiceId);
425
        $container->setDefinition($pluginServiceId, $definition);
426
427
        return $pluginServiceId;
428
    }
429
430
    /**
431
     * Decorate the plugin service with a ProfilePlugin service.
432
     *
433
     * @param ContainerBuilder $container
434
     * @param string           $pluginServiceId
435
     */
436
    private function decoratePluginWithProfilePlugin(ContainerBuilder $container, $pluginServiceId)
437
    {
438
        $container->register($pluginServiceId.'.debug', ProfilePlugin::class)
439
            ->setDecoratedService($pluginServiceId)
440
            ->setArguments([
441
                new Reference($pluginServiceId.'.debug.inner'),
442
                new Reference('httplug.collector.collector'),
443
                new Reference('httplug.collector.formatter'),
444
            ])
445
            ->setPublic(false);
446
    }
447
448
    /**
449
     * Configure a StackPlugin for a client.
450
     *
451
     * @param ContainerBuilder $container
452
     * @param string           $clientName Client name to display in the profiler.
453
     * @param string           $serviceId  Client service id. Used as base for the StackPlugin service id.
454
     *
455
     * @return string configured StackPlugin service id
456
     */
457
    private function configureStackPlugin(ContainerBuilder $container, $clientName, $serviceId)
458
    {
459
        $pluginServiceId = $serviceId.'.plugin.stack';
460
461
        $definition = class_exists(ChildDefinition::class)
462
            ? new ChildDefinition('httplug.plugin.stack')
463
            : new DefinitionDecorator('httplug.plugin.stack');
0 ignored issues
show
Deprecated Code introduced by
The class Symfony\Component\Depend...ion\DefinitionDecorator has been deprecated with message: The DefinitionDecorator class is deprecated since version 3.3 and will be removed in 4.0. Use the Symfony\Component\DependencyInjection\ChildDefinition class instead.

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
464
465
        $definition->addArgument($clientName);
466
        $container->setDefinition($pluginServiceId, $definition);
467
468
        return $pluginServiceId;
469
    }
470
}
471