Completed
Push — master ( b579f1...f83ab0 )
by Tobias
08:05
created

HttplugExtension::configureClients()   B

Complexity

Conditions 10
Paths 111

Size

Total Lines 46

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 10.0578

Importance

Changes 0
Metric Value
dl 0
loc 46
ccs 22
cts 24
cp 0.9167
rs 7.2381
c 0
b 0
f 0
cc 10
nc 111
nop 2
crap 10.0578

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\BatchClientInterface;
7
use Http\Client\Common\FlexibleHttpClient;
8
use Http\Client\Common\HttpMethodsClient;
9
use Http\Client\Common\HttpMethodsClientInterface;
10
use Http\Client\Common\Plugin\AuthenticationPlugin;
11
use Http\Client\Common\PluginClient;
12
use Http\Client\Common\PluginClientFactory;
13
use Http\Client\HttpAsyncClient;
14
use Http\Client\HttpClient;
15
use Http\Client\Plugin\Vcr\RecordPlugin;
16
use Http\Client\Plugin\Vcr\ReplayPlugin;
17
use Http\Message\Authentication\BasicAuth;
18
use Http\Message\Authentication\Bearer;
19
use Http\Message\Authentication\QueryParam;
20
use Http\Message\Authentication\Wsse;
21
use Http\Mock\Client as MockClient;
22
use Psr\Http\Message\UriInterface;
23
use Symfony\Component\Config\FileLocator;
24
use Symfony\Component\DependencyInjection\Alias;
25
use Symfony\Component\DependencyInjection\ChildDefinition;
26
use Symfony\Component\DependencyInjection\ContainerBuilder;
27
use Symfony\Component\DependencyInjection\Definition;
28
use Symfony\Component\DependencyInjection\DefinitionDecorator;
29
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
30
use Symfony\Component\DependencyInjection\Reference;
31
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
32
use Twig\Environment as TwigEnvironment;
33
34
/**
35
 * @author David Buchmann <[email protected]>
36
 * @author Tobias Nyholm <[email protected]>
37
 */
38
class HttplugExtension extends Extension
39
{
40
    public const HTTPLUG_CLIENT_TAG = 'httplug.client';
41
42
    /**
43
     * Used to check is the VCR plugin is installed.
44
     *
45
     * @var bool
46
     */
47
    private $useVcrPlugin = false;
48
49
    /**
50
     * {@inheritdoc}
51
     */
52 36
    public function load(array $configs, ContainerBuilder $container)
53
    {
54 36
        $configuration = $this->getConfiguration($configs, $container);
55 36
        $config = $this->processConfiguration($configuration, $configs);
56
57 36
        $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
58
59 36
        $loader->load('services.xml');
60 36
        $loader->load('plugins.xml');
61 36
        if (\class_exists(MockClient::class)) {
62 36
            $loader->load('mock-client.xml');
63
        }
64
65
        // Register default services
66 36
        foreach ($config['classes'] as $service => $class) {
67 36
            if (!empty($class)) {
68 1
                $container->register(sprintf('httplug.%s.default', $service), $class);
69
            }
70
        }
71
72
        // Set main aliases
73 36
        foreach ($config['main_alias'] as $type => $id) {
74 36
            $container->setAlias(sprintf('httplug.%s', $type), new Alias($id, true));
75
        }
76
77
        // Configure toolbar
78 36
        $profilingEnabled = $this->isConfigEnabled($container, $config['profiling']);
79 36
        if ($profilingEnabled) {
80 33
            $loader->load('data-collector.xml');
81
82 33
            if (!empty($config['profiling']['formatter'])) {
83
                // Add custom formatter
84
                $container
85 1
                    ->getDefinition('httplug.collector.formatter')
86 1
                    ->replaceArgument(0, new Reference($config['profiling']['formatter']))
87
                ;
88
            }
89
90
            $container
91 33
                ->getDefinition('httplug.formatter.full_http_message')
92 33
                ->addArgument($config['profiling']['captured_body_length'])
93
            ;
94
95 33
            if (!class_exists(TwigEnvironment::class) && !class_exists(\Twig_Environment::class)) {
96
                $container->removeDefinition('httplug.collector.twig.http_message');
97
            }
98
        }
99
100 36
        $this->configureClients($container, $config);
101 36
        $this->configurePlugins($container, $config['plugins']); // must be after clients, as clients.X.plugins might use plugins as templates that will be removed
102 36
        $this->configureAutoDiscoveryClients($container, $config);
103
104 36
        if (!$config['default_client_autowiring']) {
105 1
            $container->removeAlias(HttpAsyncClient::class);
106 1
            $container->removeAlias(HttpClient::class);
107
        }
108
109 36
        if ($this->useVcrPlugin) {
110 5
            if (!\class_exists(RecordPlugin::class)) {
111
                throw new \Exception('You need to require the VCR plugin to be able to use it: "composer require --dev php-http/vcr-plugin".');
112
            }
113
114 5
            $loader->load('vcr-plugin.xml');
115
        }
116 36
    }
117
118
    /**
119
     * Configure client services.
120
     *
121
     * @param ContainerBuilder $container
122
     * @param array            $config
123
     */
124 36
    private function configureClients(ContainerBuilder $container, array $config)
125
    {
126 36
        $first = null;
127 36
        $clients = [];
128
129 36
        foreach ($config['clients'] as $name => $arguments) {
130 26
            if (null === $first) {
131
                // Save the name of the first configured client.
132 26
                $first = $name;
133
            }
134
135 26
            $this->configureClient($container, $name, $arguments);
136 26
            $clients[] = $name;
137
        }
138
139
        // If we have clients configured
140 36
        if (null !== $first) {
141
            // If we do not have a client named 'default'
142 26
            if (!array_key_exists('default', $config['clients'])) {
143 26
                $serviceId = 'httplug.client.'.$first;
144
                // Alias the first client to httplug.client.default
145 26
                $container->setAlias('httplug.client.default', $serviceId);
146 26
                $default = $first;
147
            } else {
148
                $default = 'default';
149
                $serviceId = 'httplug.client.'.$default;
150
            }
151
152
            // Autowiring alias for special clients, if they are enabled on the default client
153 26
            if ($config['clients'][$default]['flexible_client']) {
154 2
                $container->setAlias(FlexibleHttpClient::class, $serviceId.'.flexible');
155
            }
156 26
            if ($config['clients'][$default]['http_methods_client']) {
157 2
                if (\interface_exists(HttpMethodsClientInterface::class)) {
158
                    // support for client-common 1.9
159 2
                    $container->setAlias(HttpMethodsClientInterface::class, $serviceId.'.http_methods');
160
                }
161
            }
162 26
            if ($config['clients'][$default]['batch_client']) {
163 2
                if (\interface_exists(BatchClientInterface::class)) {
164
                    // support for client-common 1.9
165 2
                    $container->setAlias(BatchClientInterface::class, $serviceId.'.batch_client');
166
                }
167
            }
168
        }
169 36
    }
170
171
    /**
172
     * Configure all Httplug plugins or remove their service definition.
173
     *
174
     * @param ContainerBuilder $container
175
     * @param array            $config
176
     */
177 36
    private function configurePlugins(ContainerBuilder $container, array $config)
178
    {
179 36
        if (!empty($config['authentication'])) {
180
            $this->configureAuthentication($container, $config['authentication']);
181
        }
182 36
        unset($config['authentication']);
183
184 36
        foreach ($config as $name => $pluginConfig) {
185 36
            $pluginId = 'httplug.plugin.'.$name;
186
187 36
            if ($this->isConfigEnabled($container, $pluginConfig)) {
188 36
                $def = $container->getDefinition($pluginId);
189 36
                $this->configurePluginByName($name, $def, $pluginConfig, $container, $pluginId);
190
            }
191
        }
192 36
    }
193
194
    /**
195
     * @param string           $name
196
     * @param Definition       $definition
197
     * @param array            $config
198
     * @param ContainerBuilder $container  In case we need to add additional services for this plugin
199
     * @param string           $serviceId  service id of the plugin, in case we need to add additional services for this plugin
200
     */
201 36
    private function configurePluginByName($name, Definition $definition, array $config, ContainerBuilder $container, $serviceId)
202
    {
203 36
        switch ($name) {
204 36
            case 'cache':
205 2
                $options = $config['config'];
206 2
                if (!empty($options['cache_key_generator'])) {
207 1
                    $options['cache_key_generator'] = new Reference($options['cache_key_generator']);
208
                }
209
210 2
                if (empty($options['blacklisted_paths'])) {
211 2
                    unset($options['blacklisted_paths']);
212
                }
213
214
                $definition
215 2
                    ->replaceArgument(0, new Reference($config['cache_pool']))
216 2
                    ->replaceArgument(1, new Reference($config['stream_factory']))
217 2
                    ->replaceArgument(2, $options);
218
219 2
                break;
220
221 36
            case 'cookie':
222
                $definition->replaceArgument(0, new Reference($config['cookie_jar']));
223
224
                break;
225
226 36
            case 'decoder':
227 36
                $definition->addArgument([
228 36
                    'use_content_encoding' => $config['use_content_encoding'],
229
                ]);
230
231 36
                break;
232
233 36
            case 'history':
234
                $definition->replaceArgument(0, new Reference($config['journal']));
235
236
                break;
237
238 36
            case 'logger':
239 36
                $definition->replaceArgument(0, new Reference($config['logger']));
240 36
                if (!empty($config['formatter'])) {
241
                    $definition->replaceArgument(1, new Reference($config['formatter']));
242
                }
243
244 36
                break;
245
246 36
            case 'redirect':
247 36
                $definition->addArgument([
248 36
                    'preserve_header' => $config['preserve_header'],
249 36
                    'use_default_for_multiple' => $config['use_default_for_multiple'],
250
                ]);
251
252 36
                break;
253
254 36
            case 'retry':
255 36
                $definition->addArgument([
256 36
                    'retries' => $config['retry'],
257
                ]);
258
259 36
                break;
260
261 36
            case 'stopwatch':
262 36
                $definition->replaceArgument(0, new Reference($config['stopwatch']));
263
264 36
                break;
265
266
            /* client specific plugins */
267
268 7
            case 'add_host':
269 6
                $hostUriService = $serviceId.'.host_uri';
270 6
                $this->createUri($container, $hostUriService, $config['host']);
271 6
                $definition->replaceArgument(0, new Reference($hostUriService));
272 6
                $definition->replaceArgument(1, [
273 6
                    'replace' => $config['replace'],
274
                ]);
275
276 6
                break;
277
278 2
            case 'add_path':
279
                $pathUriService = $serviceId.'.path_uri';
280
                $this->createUri($container, $pathUriService, $config['path']);
281
                $definition->replaceArgument(0, new Reference($pathUriService));
282
283
                break;
284
285 2
            case 'base_uri':
286
                $baseUriService = $serviceId.'.base_uri';
287
                $this->createUri($container, $baseUriService, $config['uri']);
288
                $definition->replaceArgument(0, new Reference($baseUriService));
289
                $definition->replaceArgument(1, [
290
                    'replace' => $config['replace'],
291
                ]);
292
293
                break;
294
295 2
            case 'content_type':
296 2
                unset($config['enabled']);
297 2
                $definition->replaceArgument(0, $config);
298
299 2
                break;
300
301 1
            case 'header_append':
302 1
            case 'header_defaults':
303 1
            case 'header_set':
304 1
            case 'header_remove':
305 1
                $definition->replaceArgument(0, $config['headers']);
306
307 1
                break;
308
309 1
            case 'query_defaults':
310 1
                $definition->replaceArgument(0, $config['parameters']);
311
312 1
                break;
313
314
            default:
315
                throw new \InvalidArgumentException(sprintf('Internal exception: Plugin %s is not handled', $name));
316
        }
317 36
    }
318
319
    /**
320
     * @param ContainerBuilder $container
321
     * @param array            $config
322
     *
323
     * @return array list of service ids for the authentication plugins
324
     */
325 6
    private function configureAuthentication(ContainerBuilder $container, array $config, $servicePrefix = 'httplug.plugin.authentication')
326
    {
327 6
        $pluginServices = [];
328
329 6
        foreach ($config as $name => $values) {
330 6
            $authServiceKey = sprintf($servicePrefix.'.%s.auth', $name);
331 6
            switch ($values['type']) {
332 6
                case 'bearer':
333
                    $container->register($authServiceKey, Bearer::class)
334
                        ->addArgument($values['token']);
335
336
                    break;
337 6
                case 'basic':
338 6
                    $container->register($authServiceKey, BasicAuth::class)
339 6
                        ->addArgument($values['username'])
340 6
                        ->addArgument($values['password']);
341
342 6
                    break;
343
                case 'wsse':
344
                    $container->register($authServiceKey, Wsse::class)
345
                        ->addArgument($values['username'])
346
                        ->addArgument($values['password']);
347
348
                    break;
349
                case 'query_param':
350
                    $container->register($authServiceKey, QueryParam::class)
351
                        ->addArgument($values['params']);
352
353
                    break;
354
                case 'service':
355
                    $authServiceKey = $values['service'];
356
357
                    break;
358
                default:
359
                    throw new \LogicException(sprintf('Unknown authentication type: "%s"', $values['type']));
360
            }
361
362 6
            $pluginServiceKey = $servicePrefix.'.'.$name;
363 6
            $container->register($pluginServiceKey, AuthenticationPlugin::class)
364 6
                ->addArgument(new Reference($authServiceKey))
365
            ;
366 6
            $pluginServices[] = $pluginServiceKey;
367
        }
368
369 6
        return $pluginServices;
370
    }
371
372
    /**
373
     * @param ContainerBuilder $container
374
     * @param string           $clientName
375
     * @param array            $arguments
376
     */
377 26
    private function configureClient(ContainerBuilder $container, $clientName, array $arguments)
378
    {
379 26
        $serviceId = 'httplug.client.'.$clientName;
380
381 26
        $plugins = [];
382 26
        foreach ($arguments['plugins'] as $plugin) {
383 15
            $pluginName = key($plugin);
384 15
            $pluginConfig = current($plugin);
385
386 15
            switch ($pluginName) {
387 15
                case 'reference':
388 9
                    $plugins[] = $pluginConfig['id'];
389 9
                    break;
390 12
                case 'authentication':
391 6
                    $plugins = array_merge($plugins, $this->configureAuthentication($container, $pluginConfig, $serviceId.'.authentication'));
392 6
                    break;
393 12
                case 'vcr':
394 5
                    $this->useVcrPlugin = true;
395 5
                    $plugins = array_merge($plugins, $this->configureVcrPlugin($container, $pluginConfig, $serviceId.'.vcr'));
396 5
                    break;
397
                default:
398 7
                    $plugins[] = $this->configurePlugin($container, $serviceId, $pluginName, $pluginConfig);
399
            }
400
        }
401
402 26
        if (empty($arguments['service'])) {
403
            $container
404 25
                ->register($serviceId.'.client', HttpClient::class)
405 25
                ->setFactory([new Reference($arguments['factory']), 'createClient'])
406 25
                ->addArgument($arguments['config'])
407 25
                ->setPublic(false);
408
        } else {
409
            $container
410 2
                ->setAlias($serviceId.'.client', new Alias($arguments['service'], false));
411
        }
412
413
        $definition = $container
414 26
            ->register($serviceId, PluginClient::class)
415 26
            ->setFactory([new Reference(PluginClientFactory::class), 'createClient'])
416 26
            ->addArgument(new Reference($serviceId.'.client'))
417 26
            ->addArgument(
418
                array_map(
419
                    function ($id) {
420 15
                        return new Reference($id);
421 26
                    },
422
                    $plugins
423
                )
424
            )
425 26
            ->addArgument([
426 26
                'client_name' => $clientName,
427
            ])
428 26
            ->addTag(self::HTTPLUG_CLIENT_TAG)
429
        ;
430
431 26
        if (is_bool($arguments['public'])) {
432 5
            $definition->setPublic($arguments['public']);
433
        }
434
435
        /*
436
         * Decorate the client with clients from client-common
437
         */
438 26
        if ($arguments['flexible_client']) {
439
            $container
440 2
                ->register($serviceId.'.flexible', FlexibleHttpClient::class)
441 2
                ->addArgument(new Reference($serviceId.'.flexible.inner'))
442 2
                ->setPublic($arguments['public'] ? true : false)
443 2
                ->setDecoratedService($serviceId)
444
            ;
445
        }
446
447 26
        if ($arguments['http_methods_client']) {
448
            $container
449 2
                ->register($serviceId.'.http_methods', HttpMethodsClient::class)
450 2
                ->setArguments([new Reference($serviceId.'.http_methods.inner'), new Reference('httplug.message_factory')])
451 2
                ->setPublic($arguments['public'] ? true : false)
452 2
                ->setDecoratedService($serviceId)
453
            ;
454
        }
455
456 26
        if ($arguments['batch_client']) {
457
            $container
458 2
                ->register($serviceId.'.batch_client', BatchClient::class)
459 2
                ->setArguments([new Reference($serviceId.'.batch_client.inner')])
460 2
                ->setPublic($arguments['public'] ? true : false)
461 2
                ->setDecoratedService($serviceId)
462
            ;
463
        }
464 26
    }
465
466
    /**
467
     * Create a URI object with the default URI factory.
468
     *
469
     * @param ContainerBuilder $container
470
     * @param string           $serviceId Name of the private service to create
471
     * @param string           $uri       String representation of the URI
472
     */
473 6
    private function createUri(ContainerBuilder $container, $serviceId, $uri)
474
    {
475
        $container
476 6
            ->register($serviceId, UriInterface::class)
477 6
            ->setPublic(false)
478 6
            ->setFactory([new Reference('httplug.uri_factory'), 'createUri'])
479 6
            ->addArgument($uri)
480
        ;
481 6
    }
482
483
    /**
484
     * Make the user can select what client is used for auto discovery. If none is provided, a service will be created
485
     * by finding a client using auto discovery.
486
     *
487
     * @param ContainerBuilder $container
488
     * @param array            $config
489
     */
490 36
    private function configureAutoDiscoveryClients(ContainerBuilder $container, array $config)
491
    {
492 36
        $httpClient = $config['discovery']['client'];
493 36
        if ('auto' !== $httpClient) {
494 2
            $container->removeDefinition('httplug.auto_discovery.auto_discovered_client');
495 2
            $container->removeDefinition('httplug.collector.auto_discovered_client');
496
497 2
            if (!empty($httpClient)) {
498 1
                $container->setAlias('httplug.auto_discovery.auto_discovered_client', $httpClient);
499 1
                $container->getAlias('httplug.auto_discovery.auto_discovered_client')->setPublic(false);
500
            }
501
        }
502
503 36
        $asyncHttpClient = $config['discovery']['async_client'];
504 36
        if ('auto' !== $asyncHttpClient) {
505 33
            $container->removeDefinition('httplug.auto_discovery.auto_discovered_async');
506 33
            $container->removeDefinition('httplug.collector.auto_discovered_async');
507
508 33
            if (!empty($asyncHttpClient)) {
509 1
                $container->setAlias('httplug.auto_discovery.auto_discovered_async', $asyncHttpClient);
510 1
                $container->getAlias('httplug.auto_discovery.auto_discovered_async')->setPublic(false);
511
            }
512
        }
513
514 36
        if (null === $httpClient && null === $asyncHttpClient) {
515 1
            $container->removeDefinition('httplug.strategy');
516
517 1
            return;
518
        }
519 35
    }
520
521
    /**
522
     * {@inheritdoc}
523
     */
524 36
    public function getConfiguration(array $config, ContainerBuilder $container)
525
    {
526 36
        return new Configuration($container->getParameter('kernel.debug'));
527
    }
528
529
    /**
530
     * Configure a plugin using the parent definition from plugins.xml.
531
     *
532
     * @param ContainerBuilder $container
533
     * @param string           $serviceId
534
     * @param string           $pluginName
535
     * @param array            $pluginConfig
536
     *
537
     * @return string configured service id
538
     */
539 7
    private function configurePlugin(ContainerBuilder $container, $serviceId, $pluginName, array $pluginConfig)
540
    {
541 7
        $pluginServiceId = $serviceId.'.plugin.'.$pluginName;
542
543 7
        $definition = $this->createChildDefinition('httplug.plugin.'.$pluginName);
544
545 7
        $this->configurePluginByName($pluginName, $definition, $pluginConfig, $container, $pluginServiceId);
546 7
        $container->setDefinition($pluginServiceId, $definition);
547
548 7
        return $pluginServiceId;
549
    }
550
551 5
    private function configureVcrPlugin(ContainerBuilder $container, array $config, $prefix)
552
    {
553 5
        $recorder = $config['recorder'];
554 5
        $recorderId = in_array($recorder, ['filesystem', 'in_memory']) ? 'httplug.plugin.vcr.recorder.'.$recorder : $recorder;
555 5
        $namingStrategyId = $config['naming_strategy'];
556 5
        $replayId = $prefix.'.replay';
557 5
        $recordId = $prefix.'.record';
558
559 5
        if ('filesystem' === $recorder) {
560 2
            $recorderDefinition = $this->createChildDefinition('httplug.plugin.vcr.recorder.filesystem');
561 2
            $recorderDefinition->replaceArgument(0, $config['fixtures_directory']);
562 2
            $recorderId = $prefix.'.recorder';
563
564 2
            $container->setDefinition($recorderId, $recorderDefinition);
565
        }
566
567 5
        if ('default' === $config['naming_strategy']) {
568 2
            $namingStrategyId = $prefix.'.naming_strategy';
569 2
            $namingStrategy = $this->createChildDefinition('httplug.plugin.vcr.naming_strategy.path');
570
571 2
            if (!empty($config['naming_strategy_options'])) {
572 1
                $namingStrategy->setArguments([$config['naming_strategy_options']]);
573
            }
574
575 2
            $container->setDefinition($namingStrategyId, $namingStrategy);
576
        }
577
578
        $arguments = [
579 5
            new Reference($namingStrategyId),
580 5
            new Reference($recorderId),
581
        ];
582 5
        $record = new Definition(RecordPlugin::class, $arguments);
583 5
        $replay = new Definition(ReplayPlugin::class, $arguments);
584 5
        $plugins = [];
585
586 5
        switch ($config['mode']) {
587 5
            case 'replay':
588 1
                $container->setDefinition($replayId, $replay);
589 1
                $plugins[] = $replayId;
590 1
                break;
591 4
            case 'replay_or_record':
592 3
                $replay->setArgument(2, false);
593 3
                $container->setDefinition($replayId, $replay);
594 3
                $container->setDefinition($recordId, $record);
595 3
                $plugins[] = $replayId;
596 3
                $plugins[] = $recordId;
597 3
                break;
598 1
            case 'record':
599 1
                $container->setDefinition($recordId, $record);
600 1
                $plugins[] = $recordId;
601 1
                break;
602
        }
603
604 5
        return $plugins;
605
    }
606
607
    /**
608
     * BC for old Symfony versions. Remove this method and use new ChildDefinition directly when we drop support for Symfony 2.
609
     *
610
     * @param string $parent the parent service id
611
     *
612
     * @return ChildDefinition|DefinitionDecorator
613
     */
614 9
    private function createChildDefinition($parent)
615
    {
616 9
        $definitionClass = class_exists(ChildDefinition::class) ? ChildDefinition::class : DefinitionDecorator::class;
617
618 9
        return new $definitionClass($parent);
619
    }
620
}
621