Completed
Push — master ( 3bd469...f9d5ad )
by Tobias
12:41
created

HttplugExtension   F

Complexity

Total Complexity 86

Size/Duplication

Total Lines 579
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 8

Test Coverage

Coverage 87.58%

Importance

Changes 0
Metric Value
wmc 86
lcom 1
cbo 8
dl 0
loc 579
ccs 261
cts 298
cp 0.8758
rs 2
c 0
b 0
f 0

12 Methods

Rating   Name   Duplication   Size   Complexity  
D load() 0 65 12
B configureClients() 0 46 10
A configurePlugins() 0 16 4
F configurePluginByName() 0 113 20
B configureAuthentication() 0 46 7
A createUri() 0 9 1
B configureAutoDiscoveryClients() 0 30 7
A getConfiguration() 0 4 1
A configurePlugin() 0 11 1
B configureVcrPlugin() 0 55 8
A createChildDefinition() 0 6 2
C configureClient() 0 88 13

How to fix   Complexity   

Complex Class

Complex classes like HttplugExtension often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use HttplugExtension, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Http\HttplugBundle\DependencyInjection;
4
5
use Http\Client\Common\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
                $definition
211 2
                    ->replaceArgument(0, new Reference($config['cache_pool']))
212 2
                    ->replaceArgument(1, new Reference($config['stream_factory']))
213 2
                    ->replaceArgument(2, $options);
214
215 2
                break;
216
217 36
            case 'cookie':
218
                $definition->replaceArgument(0, new Reference($config['cookie_jar']));
219
220
                break;
221
222 36
            case 'decoder':
223 36
                $definition->addArgument([
224 36
                    'use_content_encoding' => $config['use_content_encoding'],
225
                ]);
226
227 36
                break;
228
229 36
            case 'history':
230
                $definition->replaceArgument(0, new Reference($config['journal']));
231
232
                break;
233
234 36
            case 'logger':
235 36
                $definition->replaceArgument(0, new Reference($config['logger']));
236 36
                if (!empty($config['formatter'])) {
237
                    $definition->replaceArgument(1, new Reference($config['formatter']));
238
                }
239
240 36
                break;
241
242 36
            case 'redirect':
243 36
                $definition->addArgument([
244 36
                    'preserve_header' => $config['preserve_header'],
245 36
                    'use_default_for_multiple' => $config['use_default_for_multiple'],
246
                ]);
247
248 36
                break;
249
250 36
            case 'retry':
251 36
                $definition->addArgument([
252 36
                    'retries' => $config['retry'],
253
                ]);
254
255 36
                break;
256
257 36
            case 'stopwatch':
258 36
                $definition->replaceArgument(0, new Reference($config['stopwatch']));
259
260 36
                break;
261
262
            /* client specific plugins */
263
264 7
            case 'add_host':
265 6
                $hostUriService = $serviceId.'.host_uri';
266 6
                $this->createUri($container, $hostUriService, $config['host']);
267 6
                $definition->replaceArgument(0, new Reference($hostUriService));
268 6
                $definition->replaceArgument(1, [
269 6
                    'replace' => $config['replace'],
270
                ]);
271
272 6
                break;
273
274 2
            case 'add_path':
275
                $pathUriService = $serviceId.'.path_uri';
276
                $this->createUri($container, $pathUriService, $config['path']);
277
                $definition->replaceArgument(0, new Reference($pathUriService));
278
279
                break;
280
281 2
            case 'base_uri':
282
                $baseUriService = $serviceId.'.base_uri';
283
                $this->createUri($container, $baseUriService, $config['uri']);
284
                $definition->replaceArgument(0, new Reference($baseUriService));
285
                $definition->replaceArgument(1, [
286
                    'replace' => $config['replace'],
287
                ]);
288
289
                break;
290
291 2
            case 'content_type':
292 2
                unset($config['enabled']);
293 2
                $definition->replaceArgument(0, $config);
294
295 2
                break;
296
297 1
            case 'header_append':
298 1
            case 'header_defaults':
299 1
            case 'header_set':
300 1
            case 'header_remove':
301 1
                $definition->replaceArgument(0, $config['headers']);
302
303 1
                break;
304
305 1
            case 'query_defaults':
306 1
                $definition->replaceArgument(0, $config['parameters']);
307
308 1
                break;
309
310
            default:
311
                throw new \InvalidArgumentException(sprintf('Internal exception: Plugin %s is not handled', $name));
312
        }
313 36
    }
314
315
    /**
316
     * @param ContainerBuilder $container
317
     * @param array            $config
318
     *
319
     * @return array list of service ids for the authentication plugins
320
     */
321 6
    private function configureAuthentication(ContainerBuilder $container, array $config, $servicePrefix = 'httplug.plugin.authentication')
322
    {
323 6
        $pluginServices = [];
324
325 6
        foreach ($config as $name => $values) {
326 6
            $authServiceKey = sprintf($servicePrefix.'.%s.auth', $name);
327 6
            switch ($values['type']) {
328 6
                case 'bearer':
329
                    $container->register($authServiceKey, Bearer::class)
330
                        ->addArgument($values['token']);
331
332
                    break;
333 6
                case 'basic':
334 6
                    $container->register($authServiceKey, BasicAuth::class)
335 6
                        ->addArgument($values['username'])
336 6
                        ->addArgument($values['password']);
337
338 6
                    break;
339
                case 'wsse':
340
                    $container->register($authServiceKey, Wsse::class)
341
                        ->addArgument($values['username'])
342
                        ->addArgument($values['password']);
343
344
                    break;
345
                case 'query_param':
346
                    $container->register($authServiceKey, QueryParam::class)
347
                        ->addArgument($values['params']);
348
349
                    break;
350
                case 'service':
351
                    $authServiceKey = $values['service'];
352
353
                    break;
354
                default:
355
                    throw new \LogicException(sprintf('Unknown authentication type: "%s"', $values['type']));
356
            }
357
358 6
            $pluginServiceKey = $servicePrefix.'.'.$name;
359 6
            $container->register($pluginServiceKey, AuthenticationPlugin::class)
360 6
                ->addArgument(new Reference($authServiceKey))
361
            ;
362 6
            $pluginServices[] = $pluginServiceKey;
363
        }
364
365 6
        return $pluginServices;
366
    }
367
368
    /**
369
     * @param ContainerBuilder $container
370
     * @param string           $clientName
371
     * @param array            $arguments
372
     */
373 26
    private function configureClient(ContainerBuilder $container, $clientName, array $arguments)
374
    {
375 26
        $serviceId = 'httplug.client.'.$clientName;
376
377 26
        $plugins = [];
378 26
        foreach ($arguments['plugins'] as $plugin) {
379 15
            $pluginName = key($plugin);
380 15
            $pluginConfig = current($plugin);
381
382 15
            switch ($pluginName) {
383 15
                case 'reference':
384 9
                    $plugins[] = $pluginConfig['id'];
385 9
                    break;
386 12
                case 'authentication':
387 6
                    $plugins = array_merge($plugins, $this->configureAuthentication($container, $pluginConfig, $serviceId.'.authentication'));
388 6
                    break;
389 12
                case 'vcr':
390 5
                    $this->useVcrPlugin = true;
391 5
                    $plugins = array_merge($plugins, $this->configureVcrPlugin($container, $pluginConfig, $serviceId.'.vcr'));
392 5
                    break;
393
                default:
394 7
                    $plugins[] = $this->configurePlugin($container, $serviceId, $pluginName, $pluginConfig);
395
            }
396
        }
397
398 26
        if (empty($arguments['service'])) {
399
            $container
400 25
                ->register($serviceId.'.client', HttpClient::class)
401 25
                ->setFactory([new Reference($arguments['factory']), 'createClient'])
402 25
                ->addArgument($arguments['config'])
403 25
                ->setPublic(false);
404
        } else {
405
            $container
406 2
                ->setAlias($serviceId.'.client', new Alias($arguments['service'], false));
407
        }
408
409
        $definition = $container
410 26
            ->register($serviceId, PluginClient::class)
411 26
            ->setFactory([new Reference(PluginClientFactory::class), 'createClient'])
412 26
            ->addArgument(new Reference($serviceId.'.client'))
413 26
            ->addArgument(
414 26
                array_map(
415
                    function ($id) {
416 15
                        return new Reference($id);
417 26
                    },
418
                    $plugins
419
                )
420
            )
421 26
            ->addArgument([
422 26
                'client_name' => $clientName,
423
            ])
424 26
            ->addTag(self::HTTPLUG_CLIENT_TAG)
425
        ;
426
427 26
        if (is_bool($arguments['public'])) {
428 5
            $definition->setPublic($arguments['public']);
429
        }
430
431
        /*
432
         * Decorate the client with clients from client-common
433
         */
434 26
        if ($arguments['flexible_client']) {
435
            $container
436 2
                ->register($serviceId.'.flexible', FlexibleHttpClient::class)
437 2
                ->addArgument(new Reference($serviceId.'.flexible.inner'))
438 2
                ->setPublic($arguments['public'] ? true : false)
439 2
                ->setDecoratedService($serviceId)
440
            ;
441
        }
442
443 26
        if ($arguments['http_methods_client']) {
444
            $container
445 2
                ->register($serviceId.'.http_methods', HttpMethodsClient::class)
446 2
                ->setArguments([new Reference($serviceId.'.http_methods.inner'), new Reference('httplug.message_factory')])
447 2
                ->setPublic($arguments['public'] ? true : false)
448 2
                ->setDecoratedService($serviceId)
449
            ;
450
        }
451
452 26
        if ($arguments['batch_client']) {
453
            $container
454 2
                ->register($serviceId.'.batch_client', BatchClient::class)
455 2
                ->setArguments([new Reference($serviceId.'.batch_client.inner')])
456 2
                ->setPublic($arguments['public'] ? true : false)
457 2
                ->setDecoratedService($serviceId)
458
            ;
459
        }
460 26
    }
461
462
    /**
463
     * Create a URI object with the default URI factory.
464
     *
465
     * @param ContainerBuilder $container
466
     * @param string           $serviceId Name of the private service to create
467
     * @param string           $uri       String representation of the URI
468
     */
469 6
    private function createUri(ContainerBuilder $container, $serviceId, $uri)
470
    {
471
        $container
472 6
            ->register($serviceId, UriInterface::class)
473 6
            ->setPublic(false)
474 6
            ->setFactory([new Reference('httplug.uri_factory'), 'createUri'])
475 6
            ->addArgument($uri)
476
        ;
477 6
    }
478
479
    /**
480
     * Make the user can select what client is used for auto discovery. If none is provided, a service will be created
481
     * by finding a client using auto discovery.
482
     *
483
     * @param ContainerBuilder $container
484
     * @param array            $config
485
     */
486 36
    private function configureAutoDiscoveryClients(ContainerBuilder $container, array $config)
487
    {
488 36
        $httpClient = $config['discovery']['client'];
489 36
        if ('auto' !== $httpClient) {
490 2
            $container->removeDefinition('httplug.auto_discovery.auto_discovered_client');
491 2
            $container->removeDefinition('httplug.collector.auto_discovered_client');
492
493 2
            if (!empty($httpClient)) {
494 1
                $container->setAlias('httplug.auto_discovery.auto_discovered_client', $httpClient);
495 1
                $container->getAlias('httplug.auto_discovery.auto_discovered_client')->setPublic(false);
496
            }
497
        }
498
499 36
        $asyncHttpClient = $config['discovery']['async_client'];
500 36
        if ('auto' !== $asyncHttpClient) {
501 33
            $container->removeDefinition('httplug.auto_discovery.auto_discovered_async');
502 33
            $container->removeDefinition('httplug.collector.auto_discovered_async');
503
504 33
            if (!empty($asyncHttpClient)) {
505 1
                $container->setAlias('httplug.auto_discovery.auto_discovered_async', $asyncHttpClient);
506 1
                $container->getAlias('httplug.auto_discovery.auto_discovered_async')->setPublic(false);
507
            }
508
        }
509
510 36
        if (null === $httpClient && null === $asyncHttpClient) {
511 1
            $container->removeDefinition('httplug.strategy');
512
513 1
            return;
514
        }
515 35
    }
516
517
    /**
518
     * {@inheritdoc}
519
     */
520 36
    public function getConfiguration(array $config, ContainerBuilder $container)
521
    {
522 36
        return new Configuration($container->getParameter('kernel.debug'));
523
    }
524
525
    /**
526
     * Configure a plugin using the parent definition from plugins.xml.
527
     *
528
     * @param ContainerBuilder $container
529
     * @param string           $serviceId
530
     * @param string           $pluginName
531
     * @param array            $pluginConfig
532
     *
533
     * @return string configured service id
534
     */
535 7
    private function configurePlugin(ContainerBuilder $container, $serviceId, $pluginName, array $pluginConfig)
536
    {
537 7
        $pluginServiceId = $serviceId.'.plugin.'.$pluginName;
538
539 7
        $definition = $this->createChildDefinition('httplug.plugin.'.$pluginName);
540
541 7
        $this->configurePluginByName($pluginName, $definition, $pluginConfig, $container, $pluginServiceId);
542 7
        $container->setDefinition($pluginServiceId, $definition);
543
544 7
        return $pluginServiceId;
545
    }
546
547 5
    private function configureVcrPlugin(ContainerBuilder $container, array $config, $prefix)
548
    {
549 5
        $recorder = $config['recorder'];
550 5
        $recorderId = in_array($recorder, ['filesystem', 'in_memory']) ? 'httplug.plugin.vcr.recorder.'.$recorder : $recorder;
551 5
        $namingStrategyId = $config['naming_strategy'];
552 5
        $replayId = $prefix.'.replay';
553 5
        $recordId = $prefix.'.record';
554
555 5
        if ('filesystem' === $recorder) {
556 2
            $recorderDefinition = $this->createChildDefinition('httplug.plugin.vcr.recorder.filesystem');
557 2
            $recorderDefinition->replaceArgument(0, $config['fixtures_directory']);
558 2
            $recorderId = $prefix.'.recorder';
559
560 2
            $container->setDefinition($recorderId, $recorderDefinition);
561
        }
562
563 5
        if ('default' === $config['naming_strategy']) {
564 2
            $namingStrategyId = $prefix.'.naming_strategy';
565 2
            $namingStrategy = $this->createChildDefinition('httplug.plugin.vcr.naming_strategy.path');
566
567 2
            if (!empty($config['naming_strategy_options'])) {
568 1
                $namingStrategy->setArguments([$config['naming_strategy_options']]);
569
            }
570
571 2
            $container->setDefinition($namingStrategyId, $namingStrategy);
572
        }
573
574
        $arguments = [
575 5
            new Reference($namingStrategyId),
576 5
            new Reference($recorderId),
577
        ];
578 5
        $record = new Definition(RecordPlugin::class, $arguments);
579 5
        $replay = new Definition(ReplayPlugin::class, $arguments);
580 5
        $plugins = [];
581
582 5
        switch ($config['mode']) {
583 5
            case 'replay':
584 1
                $container->setDefinition($replayId, $replay);
585 1
                $plugins[] = $replayId;
586 1
                break;
587 4
            case 'replay_or_record':
588 3
                $replay->setArgument(2, false);
589 3
                $container->setDefinition($replayId, $replay);
590 3
                $container->setDefinition($recordId, $record);
591 3
                $plugins[] = $replayId;
592 3
                $plugins[] = $recordId;
593 3
                break;
594 1
            case 'record':
595 1
                $container->setDefinition($recordId, $record);
596 1
                $plugins[] = $recordId;
597 1
                break;
598
        }
599
600 5
        return $plugins;
601
    }
602
603
    /**
604
     * BC for old Symfony versions. Remove this method and use new ChildDefinition directly when we drop support for Symfony 2.
605
     *
606
     * @param string $parent the parent service id
607
     *
608
     * @return ChildDefinition|DefinitionDecorator
609
     */
610 9
    private function createChildDefinition($parent)
611
    {
612 9
        $definitionClass = class_exists(ChildDefinition::class) ? ChildDefinition::class : DefinitionDecorator::class;
613
614 9
        return new $definitionClass($parent);
615
    }
616
}
617