Completed
Push — master ( b587fb...d05392 )
by David
20:34
created

HttplugExtension::getConfiguration()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
crap 1
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\HttpClient;
14
use Http\Message\Authentication\BasicAuth;
15
use Http\Message\Authentication\Bearer;
16
use Http\Message\Authentication\QueryParam;
17
use Http\Message\Authentication\Wsse;
18
use Http\Mock\Client as MockClient;
19
use Psr\Http\Message\UriInterface;
20
use Symfony\Component\Config\FileLocator;
21
use Symfony\Component\DependencyInjection\Alias;
22
use Symfony\Component\DependencyInjection\ChildDefinition;
23
use Symfony\Component\DependencyInjection\ContainerBuilder;
24
use Symfony\Component\DependencyInjection\Definition;
25
use Symfony\Component\DependencyInjection\DefinitionDecorator;
26
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
27
use Symfony\Component\DependencyInjection\Reference;
28
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
29
30
/**
31
 * @author David Buchmann <[email protected]>
32
 * @author Tobias Nyholm <[email protected]>
33
 */
34
class HttplugExtension extends Extension
35
{
36
    /**
37
     * {@inheritdoc}
38
     */
39 26
    public function load(array $configs, ContainerBuilder $container)
40
    {
41 26
        $configuration = $this->getConfiguration($configs, $container);
42 26
        $config = $this->processConfiguration($configuration, $configs);
43
44 26
        $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
45
46 26
        $loader->load('services.xml');
47 26
        $loader->load('plugins.xml');
48 26
        if (\class_exists(MockClient::class)) {
49 26
            $loader->load('mock-client.xml');
50
        }
51
52
        // Register default services
53 26
        foreach ($config['classes'] as $service => $class) {
54 26
            if (!empty($class)) {
55 1
                $container->register(sprintf('httplug.%s.default', $service), $class);
56
            }
57
        }
58
59
        // Set main aliases
60 26
        foreach ($config['main_alias'] as $type => $id) {
61 26
            $container->setAlias(sprintf('httplug.%s', $type), new Alias($id, true));
62
        }
63
64
        // Configure toolbar
65 26
        $profilingEnabled = $this->isConfigEnabled($container, $config['profiling']);
66 26
        if ($profilingEnabled) {
67 23
            $loader->load('data-collector.xml');
68
69 23
            if (!empty($config['profiling']['formatter'])) {
70
                // Add custom formatter
71
                $container
72 1
                    ->getDefinition('httplug.collector.formatter')
73 1
                    ->replaceArgument(0, new Reference($config['profiling']['formatter']))
74
                ;
75
            }
76
77
            $container
78 23
                ->getDefinition('httplug.formatter.full_http_message')
79 23
                ->addArgument($config['profiling']['captured_body_length'])
80
            ;
81
82 23
            if (!class_exists(\Twig_Environment::class)) {
83
                $container->removeDefinition('httplug.collector.twig.http_message');
84
            }
85
        }
86
87 26
        $this->configureClients($container, $config);
88 26
        $this->configurePlugins($container, $config['plugins']); // must be after clients, as clients.X.plugins might use plugins as templates that will be removed
89 26
        $this->configureAutoDiscoveryClients($container, $config);
90 26
    }
91
92
    /**
93
     * Configure client services.
94
     *
95
     * @param ContainerBuilder $container
96
     * @param array            $config
97
     */
98 26
    private function configureClients(ContainerBuilder $container, array $config)
99
    {
100 26
        $first = null;
101 26
        $clients = [];
102
103 26
        foreach ($config['clients'] as $name => $arguments) {
104 18
            if (null === $first) {
105
                // Save the name of the first configured client.
106 18
                $first = $name;
107
            }
108
109 18
            $this->configureClient($container, $name, $arguments);
110 18
            $clients[] = $name;
111
        }
112
113
        // If we have clients configured
114 26
        if (null !== $first) {
115
            // If we do not have a client named 'default'
116 18
            if (!array_key_exists('default', $config['clients'])) {
117 18
                $serviceId = 'httplug.client.'.$first;
118
                // Alias the first client to httplug.client.default
119 18
                $container->setAlias('httplug.client.default', $serviceId);
120 18
                $default = $first;
121
            } else {
122
                $default = 'default';
123
            }
124
125
            // Autowiring alias for special clients, if they are enabled on the default client
126 18
            if ($config['clients'][$default]['flexible_client']) {
127 2
                $container->setAlias(FlexibleHttpClient::class, $serviceId.'.flexible');
0 ignored issues
show
Bug introduced by
The variable $serviceId does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
128
            }
129 18
            if ($config['clients'][$default]['http_methods_client']) {
130 2
                if (\interface_exists(HttpMethodsClientInterface::class)) {
131
                    // support for client-common 1.9
132 2
                    $container->setAlias(HttpMethodsClientInterface::class, $serviceId.'.http_methods');
133
                }
134
            }
135 18
            if ($config['clients'][$default]['batch_client']) {
136 2
                if (\interface_exists(BatchClientInterface::class)) {
137
                    // support for client-common 1.9
138 2
                    $container->setAlias(BatchClientInterface::class, $serviceId.'.batch_client');
139
                }
140
            }
141
        }
142 26
    }
143
144
    /**
145
     * Configure all Httplug plugins or remove their service definition.
146
     *
147
     * @param ContainerBuilder $container
148
     * @param array            $config
149
     */
150 26
    private function configurePlugins(ContainerBuilder $container, array $config)
151
    {
152 26
        if (!empty($config['authentication'])) {
153
            $this->configureAuthentication($container, $config['authentication']);
154
        }
155 26
        unset($config['authentication']);
156
157 26
        foreach ($config as $name => $pluginConfig) {
158 26
            $pluginId = 'httplug.plugin.'.$name;
159
160 26
            if ($this->isConfigEnabled($container, $pluginConfig)) {
161 26
                $def = $container->getDefinition($pluginId);
162 26
                $this->configurePluginByName($name, $def, $pluginConfig, $container, $pluginId);
163
            }
164
        }
165 26
    }
166
167
    /**
168
     * @param string           $name
169
     * @param Definition       $definition
170
     * @param array            $config
171
     * @param ContainerBuilder $container  In case we need to add additional services for this plugin
172
     * @param string           $serviceId  service id of the plugin, in case we need to add additional services for this plugin
173
     */
174 26
    private function configurePluginByName($name, Definition $definition, array $config, ContainerBuilder $container, $serviceId)
175
    {
176 26
        switch ($name) {
177 26
            case 'cache':
178 2
                $options = $config['config'];
179 2
                if (!empty($options['cache_key_generator'])) {
180 1
                    $options['cache_key_generator'] = new Reference($options['cache_key_generator']);
181
                }
182
183
                $definition
184 2
                    ->replaceArgument(0, new Reference($config['cache_pool']))
185 2
                    ->replaceArgument(1, new Reference($config['stream_factory']))
186 2
                    ->replaceArgument(2, $options);
187
188 2
                break;
189
190 26
            case 'cookie':
191
                $definition->replaceArgument(0, new Reference($config['cookie_jar']));
192
193
                break;
194
195 26
            case 'decoder':
196 26
                $definition->addArgument([
197 26
                    'use_content_encoding' => $config['use_content_encoding'],
198
                ]);
199
200 26
                break;
201
202 26
            case 'history':
203
                $definition->replaceArgument(0, new Reference($config['journal']));
204
205
                break;
206
207 26
            case 'logger':
208 26
                $definition->replaceArgument(0, new Reference($config['logger']));
209 26
                if (!empty($config['formatter'])) {
210
                    $definition->replaceArgument(1, new Reference($config['formatter']));
211
                }
212
213 26
                break;
214
215 26
            case 'redirect':
216 26
                $definition->addArgument([
217 26
                    'preserve_header' => $config['preserve_header'],
218 26
                    'use_default_for_multiple' => $config['use_default_for_multiple'],
219
                ]);
220
221 26
                break;
222
223 26
            case 'retry':
224 26
                $definition->addArgument([
225 26
                    'retries' => $config['retry'],
226
                ]);
227
228 26
                break;
229
230 26
            case 'stopwatch':
231 26
                $definition->replaceArgument(0, new Reference($config['stopwatch']));
232
233 26
                break;
234
235
            /* client specific plugins */
236
237 6 View Code Duplication
            case 'add_host':
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...
238 6
                $hostUriService = $serviceId.'.host_uri';
239 6
                $this->createUri($container, $hostUriService, $config['host']);
240 6
                $definition->replaceArgument(0, new Reference($hostUriService));
241 6
                $definition->replaceArgument(1, [
242 6
                    'replace' => $config['replace'],
243
                ]);
244
245 6
                break;
246
247 1
            case 'add_path':
248
                $pathUriService = $serviceId.'.path_uri';
249
                $this->createUri($container, $pathUriService, $config['path']);
250
                $definition->replaceArgument(0, new Reference($pathUriService));
251
252
                break;
253
254 1 View Code Duplication
            case 'base_uri':
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...
255
                $baseUriService = $serviceId.'.base_uri';
256
                $this->createUri($container, $baseUriService, $config['uri']);
257
                $definition->replaceArgument(0, new Reference($baseUriService));
258
                $definition->replaceArgument(1, [
259
                    'replace' => $config['replace'],
260
                ]);
261
262
                break;
263
264 1
            case 'content_type':
265 1
                $definition->replaceArgument(0, $config);
266 1
                break;
267
268 1
            case 'header_append':
269 1
            case 'header_defaults':
270 1
            case 'header_set':
271 1
            case 'header_remove':
272 1
                $definition->replaceArgument(0, $config['headers']);
273
274 1
                break;
275
276 1
            case 'query_defaults':
277 1
                $definition->replaceArgument(0, $config['parameters']);
278
279 1
                break;
280
281
            default:
282
                throw new \InvalidArgumentException(sprintf('Internal exception: Plugin %s is not handled', $name));
283
        }
284 26
    }
285
286
    /**
287
     * @param ContainerBuilder $container
288
     * @param array            $config
289
     *
290
     * @return array list of service ids for the authentication plugins
291
     */
292 6
    private function configureAuthentication(ContainerBuilder $container, array $config, $servicePrefix = 'httplug.plugin.authentication')
293
    {
294 6
        $pluginServices = [];
295
296 6
        foreach ($config as $name => $values) {
297 6
            $authServiceKey = sprintf($servicePrefix.'.%s.auth', $name);
298 6
            switch ($values['type']) {
299 6
                case 'bearer':
300
                    $container->register($authServiceKey, Bearer::class)
301
                        ->addArgument($values['token']);
302
303
                    break;
304 6
                case 'basic':
305 6
                    $container->register($authServiceKey, BasicAuth::class)
306 6
                        ->addArgument($values['username'])
307 6
                        ->addArgument($values['password']);
308
309 6
                    break;
310
                case 'wsse':
311
                    $container->register($authServiceKey, Wsse::class)
312
                        ->addArgument($values['username'])
313
                        ->addArgument($values['password']);
314
315
                    break;
316
                case 'query_param':
317
                    $container->register($authServiceKey, QueryParam::class)
318
                        ->addArgument($values['params']);
319
320
                    break;
321
                case 'service':
322
                    $authServiceKey = $values['service'];
323
324
                    break;
325
                default:
326
                    throw new \LogicException(sprintf('Unknown authentication type: "%s"', $values['type']));
327
            }
328
329 6
            $pluginServiceKey = $servicePrefix.'.'.$name;
330 6
            $container->register($pluginServiceKey, AuthenticationPlugin::class)
331 6
                ->addArgument(new Reference($authServiceKey))
332
            ;
333 6
            $pluginServices[] = $pluginServiceKey;
334
        }
335
336 6
        return $pluginServices;
337
    }
338
339
    /**
340
     * @param ContainerBuilder $container
341
     * @param string           $clientName
342
     * @param array            $arguments
343
     */
344 18
    private function configureClient(ContainerBuilder $container, $clientName, array $arguments)
345
    {
346 18
        $serviceId = 'httplug.client.'.$clientName;
347
348 18
        $plugins = [];
349 18
        foreach ($arguments['plugins'] as $plugin) {
350 9
            $pluginName = key($plugin);
351 9
            $pluginConfig = current($plugin);
352 9
            if ('reference' === $pluginName) {
353 9
                $plugins[] = $pluginConfig['id'];
354 6
            } elseif ('authentication' === $pluginName) {
355 6
                $plugins = array_merge($plugins, $this->configureAuthentication($container, $pluginConfig, $serviceId.'.authentication'));
356
            } else {
357 6
                $plugins[] = $this->configurePlugin($container, $serviceId, $pluginName, $pluginConfig);
358
            }
359
        }
360
361 18
        if (empty($arguments['service'])) {
362
            $container
363 17
                ->register($serviceId.'.client', HttpClient::class)
364 17
                ->setFactory([new Reference($arguments['factory']), 'createClient'])
365 17
                ->addArgument($arguments['config'])
366 17
                ->setPublic(false);
367
        } else {
368
            $container
369 2
                ->setAlias($serviceId.'.client', new Alias($arguments['service'], false));
370
        }
371
372
        $definition = $container
373 18
            ->register($serviceId, PluginClient::class)
374 18
            ->setFactory([new Reference(PluginClientFactory::class), 'createClient'])
375 18
            ->addArgument(new Reference($serviceId.'.client'))
376 18
            ->addArgument(
377 18
                array_map(
378 18
                    function ($id) {
379 9
                        return new Reference($id);
380 18
                    },
381
                    $plugins
382
                )
383
            )
384 18
            ->addArgument([
385 18
                'client_name' => $clientName,
386
            ])
387
        ;
388
389 18
        if (is_bool($arguments['public'])) {
390 5
            $definition->setPublic($arguments['public']);
391
        }
392
393
        /*
394
         * Decorate the client with clients from client-common
395
         */
396 18 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...
397
            $container
398 2
                ->register($serviceId.'.flexible', FlexibleHttpClient::class)
399 2
                ->addArgument(new Reference($serviceId.'.flexible.inner'))
400 2
                ->setPublic($arguments['public'] ? true : false)
401 2
                ->setDecoratedService($serviceId)
402
            ;
403
        }
404
405 18 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...
406
            $container
407 2
                ->register($serviceId.'.http_methods', HttpMethodsClient::class)
408 2
                ->setArguments([new Reference($serviceId.'.http_methods.inner'), new Reference('httplug.message_factory')])
409 2
                ->setPublic($arguments['public'] ? true : false)
410 2
                ->setDecoratedService($serviceId)
411
            ;
412
        }
413
414 18 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...
415
            $container
416 2
                ->register($serviceId.'.batch_client', BatchClient::class)
417 2
                ->setArguments([new Reference($serviceId.'.batch_client.inner')])
418 2
                ->setPublic($arguments['public'] ? true : false)
419 2
                ->setDecoratedService($serviceId)
420
            ;
421
        }
422 18
    }
423
424
    /**
425
     * Create a URI object with the default URI factory.
426
     *
427
     * @param ContainerBuilder $container
428
     * @param string           $serviceId Name of the private service to create
429
     * @param string           $uri       String representation of the URI
430
     */
431 6
    private function createUri(ContainerBuilder $container, $serviceId, $uri)
432
    {
433
        $container
434 6
            ->register($serviceId, UriInterface::class)
435 6
            ->setPublic(false)
436 6
            ->setFactory([new Reference('httplug.uri_factory'), 'createUri'])
437 6
            ->addArgument($uri)
438
        ;
439 6
    }
440
441
    /**
442
     * Make the user can select what client is used for auto discovery. If none is provided, a service will be created
443
     * by finding a client using auto discovery.
444
     *
445
     * @param ContainerBuilder $container
446
     * @param array            $config
447
     */
448 26
    private function configureAutoDiscoveryClients(ContainerBuilder $container, array $config)
449
    {
450 26
        $httpClient = $config['discovery']['client'];
451 26 View Code Duplication
        if ('auto' !== $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...
452 2
            $container->removeDefinition('httplug.auto_discovery.auto_discovered_client');
453 2
            $container->removeDefinition('httplug.collector.auto_discovered_client');
454
455 2
            if (!empty($httpClient)) {
456 1
                $container->setAlias('httplug.auto_discovery.auto_discovered_client', $httpClient);
457 1
                $container->getAlias('httplug.auto_discovery.auto_discovered_client')->setPublic(false);
458
            }
459
        }
460
461 26
        $asyncHttpClient = $config['discovery']['async_client'];
462 26 View Code Duplication
        if ('auto' !== $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...
463 23
            $container->removeDefinition('httplug.auto_discovery.auto_discovered_async');
464 23
            $container->removeDefinition('httplug.collector.auto_discovered_async');
465
466 23
            if (!empty($asyncHttpClient)) {
467 1
                $container->setAlias('httplug.auto_discovery.auto_discovered_async', $asyncHttpClient);
468 1
                $container->getAlias('httplug.auto_discovery.auto_discovered_async')->setPublic(false);
469
            }
470
        }
471
472 26
        if (null === $httpClient && null === $asyncHttpClient) {
473 1
            $container->removeDefinition('httplug.strategy');
474
475 1
            return;
476
        }
477 25
    }
478
479
    /**
480
     * {@inheritdoc}
481
     */
482 26
    public function getConfiguration(array $config, ContainerBuilder $container)
483
    {
484 26
        return new Configuration($container->getParameter('kernel.debug'));
485
    }
486
487
    /**
488
     * Configure a plugin using the parent definition from plugins.xml.
489
     *
490
     * @param ContainerBuilder $container
491
     * @param string           $serviceId
492
     * @param string           $pluginName
493
     * @param array            $pluginConfig
494
     *
495
     * @return string configured service id
496
     */
497 6
    private function configurePlugin(ContainerBuilder $container, $serviceId, $pluginName, array $pluginConfig)
498
    {
499 6
        $pluginServiceId = $serviceId.'.plugin.'.$pluginName;
500
501 6
        $definition = class_exists(ChildDefinition::class)
502 6
            ? new ChildDefinition('httplug.plugin.'.$pluginName)
503 6
            : new DefinitionDecorator('httplug.plugin.'.$pluginName);
504
505 6
        $this->configurePluginByName($pluginName, $definition, $pluginConfig, $container, $pluginServiceId);
506 6
        $container->setDefinition($pluginServiceId, $definition);
507
508 6
        return $pluginServiceId;
509
    }
510
}
511