Completed
Push — master ( 57db8a...f718e5 )
by Tobias
06:48
created

HttplugExtension::createUri()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 9
ccs 6
cts 6
cp 1
rs 9.9666
c 0
b 0
f 0
cc 1
nc 1
nop 3
crap 1
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\Client\Common\PluginClient;
10
use Http\Client\Common\PluginClientFactory;
11
use Http\Client\HttpClient;
12
use Http\Message\Authentication\BasicAuth;
13
use Http\Message\Authentication\Bearer;
14
use Http\Message\Authentication\QueryParam;
15
use Http\Message\Authentication\Wsse;
16
use Http\Mock\Client as MockClient;
17
use Psr\Http\Message\UriInterface;
18
use Symfony\Component\Config\FileLocator;
19
use Symfony\Component\DependencyInjection\Alias;
20
use Symfony\Component\DependencyInjection\ChildDefinition;
21
use Symfony\Component\DependencyInjection\ContainerBuilder;
22
use Symfony\Component\DependencyInjection\Definition;
23
use Symfony\Component\DependencyInjection\DefinitionDecorator;
24
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
25
use Symfony\Component\DependencyInjection\Reference;
26
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
27
28
/**
29
 * @author David Buchmann <[email protected]>
30
 * @author Tobias Nyholm <[email protected]>
31
 */
32
class HttplugExtension extends Extension
33
{
34
    /**
35
     * {@inheritdoc}
36
     */
37 25
    public function load(array $configs, ContainerBuilder $container)
38
    {
39 25
        $configuration = $this->getConfiguration($configs, $container);
40 25
        $config = $this->processConfiguration($configuration, $configs);
41
42 25
        $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
43
44 25
        $loader->load('services.xml');
45 25
        $loader->load('plugins.xml');
46 25
        if (\class_exists(MockClient::class)) {
47 25
            $loader->load('mock-client.xml');
48
        }
49
50
        // Register default services
51 25
        foreach ($config['classes'] as $service => $class) {
52 25
            if (!empty($class)) {
53 25
                $container->register(sprintf('httplug.%s.default', $service), $class);
54
            }
55
        }
56
57
        // Set main aliases
58 25
        foreach ($config['main_alias'] as $type => $id) {
59 25
            $container->setAlias(sprintf('httplug.%s', $type), new Alias($id, true));
60
        }
61
62
        // Configure toolbar
63 25
        $profilingEnabled = $this->isConfigEnabled($container, $config['profiling']);
64 25
        if ($profilingEnabled) {
65 22
            $loader->load('data-collector.xml');
66
67 22
            if (!empty($config['profiling']['formatter'])) {
68
                // Add custom formatter
69
                $container
70 1
                    ->getDefinition('httplug.collector.formatter')
71 1
                    ->replaceArgument(0, new Reference($config['profiling']['formatter']))
72
                ;
73
            }
74
75
            $container
76 22
                ->getDefinition('httplug.formatter.full_http_message')
77 22
                ->addArgument($config['profiling']['captured_body_length'])
78
            ;
79
80 22
            if (!class_exists(\Twig_Environment::class)) {
81
                $container->removeDefinition('httplug.collector.twig.http_message');
82
            }
83
        }
84
85 25
        $this->configureClients($container, $config);
86 25
        $this->configurePlugins($container, $config['plugins']); // must be after clients, as clients.X.plugins might use plugins as templates that will be removed
87 25
        $this->configureAutoDiscoveryClients($container, $config);
88 25
    }
89
90
    /**
91
     * Configure client services.
92
     *
93
     * @param ContainerBuilder $container
94
     * @param array            $config
95
     */
96 25
    private function configureClients(ContainerBuilder $container, array $config)
97
    {
98 25
        $first = null;
99 25
        $clients = [];
100
101 25
        foreach ($config['clients'] as $name => $arguments) {
102 17
            if (null === $first) {
103
                // Save the name of the first configured client.
104 17
                $first = $name;
105
            }
106
107 17
            $this->configureClient($container, $name, $arguments);
108 17
            $clients[] = $name;
109
        }
110
111
        // If we have clients configured
112 25
        if (null !== $first) {
113
            // If we do not have a client named 'default'
114 17
            if (!isset($config['clients']['default'])) {
115
                // Alias the first client to httplug.client.default
116 17
                $container->setAlias('httplug.client.default', 'httplug.client.'.$first);
117
            }
118
        }
119 25
    }
120
121
    /**
122
     * Configure all Httplug plugins or remove their service definition.
123
     *
124
     * @param ContainerBuilder $container
125
     * @param array            $config
126
     */
127 25
    private function configurePlugins(ContainerBuilder $container, array $config)
128
    {
129 25
        if (!empty($config['authentication'])) {
130
            $this->configureAuthentication($container, $config['authentication']);
131
        }
132 25
        unset($config['authentication']);
133
134 25
        foreach ($config as $name => $pluginConfig) {
135 25
            $pluginId = 'httplug.plugin.'.$name;
136
137 25
            if ($this->isConfigEnabled($container, $pluginConfig)) {
138 25
                $def = $container->getDefinition($pluginId);
139 25
                $this->configurePluginByName($name, $def, $pluginConfig, $container, $pluginId);
140
            }
141
        }
142 25
    }
143
144
    /**
145
     * @param string           $name
146
     * @param Definition       $definition
147
     * @param array            $config
148
     * @param ContainerBuilder $container  In case we need to add additional services for this plugin
149
     * @param string           $serviceId  service id of the plugin, in case we need to add additional services for this plugin
150
     */
151 25
    private function configurePluginByName($name, Definition $definition, array $config, ContainerBuilder $container, $serviceId)
152
    {
153 25
        switch ($name) {
154 25
            case 'cache':
155 2
                $options = $config['config'];
156 2
                if (!empty($options['cache_key_generator'])) {
157 1
                    $options['cache_key_generator'] = new Reference($options['cache_key_generator']);
158
                }
159
160
                $definition
161 2
                    ->replaceArgument(0, new Reference($config['cache_pool']))
162 2
                    ->replaceArgument(1, new Reference($config['stream_factory']))
163 2
                    ->replaceArgument(2, $options);
164
165 2
                break;
166 25
            case 'cookie':
167
                $definition->replaceArgument(0, new Reference($config['cookie_jar']));
168
169
                break;
170 25
            case 'decoder':
171 25
                $definition->addArgument([
172 25
                    'use_content_encoding' => $config['use_content_encoding'],
173
                ]);
174
175 25
                break;
176 25
            case 'history':
177
                $definition->replaceArgument(0, new Reference($config['journal']));
178
179
                break;
180 25
            case 'logger':
181 25
                $definition->replaceArgument(0, new Reference($config['logger']));
182 25
                if (!empty($config['formatter'])) {
183
                    $definition->replaceArgument(1, new Reference($config['formatter']));
184
                }
185
186 25
                break;
187 25
            case 'redirect':
188 25
                $definition->addArgument([
189 25
                    'preserve_header' => $config['preserve_header'],
190 25
                    'use_default_for_multiple' => $config['use_default_for_multiple'],
191
                ]);
192
193 25
                break;
194 25
            case 'retry':
195 25
                $definition->addArgument([
196 25
                    'retries' => $config['retry'],
197
                ]);
198
199 25
                break;
200 25
            case 'stopwatch':
201 25
                $definition->replaceArgument(0, new Reference($config['stopwatch']));
202
203 25
                break;
204
205
            /* client specific plugins */
206
207 5 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...
208 5
                $hostUriService = $serviceId.'.host_uri';
209 5
                $this->createUri($container, $hostUriService, $config['host']);
210 5
                $definition->replaceArgument(0, new Reference($hostUriService));
211 5
                $definition->replaceArgument(1, [
212 5
                    'replace' => $config['replace'],
213
                ]);
214
215 5
                break;
216 1
            case 'add_path':
217
                $pathUriService = $serviceId.'.path_uri';
218
                $this->createUri($container, $pathUriService, $config['path']);
219
                $definition->replaceArgument(0, new Reference($pathUriService));
220
221
                break;
222 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...
223
                $baseUriService = $serviceId.'.base_uri';
224
                $this->createUri($container, $baseUriService, $config['uri']);
225
                $definition->replaceArgument(0, new Reference($baseUriService));
226
                $definition->replaceArgument(1, [
227
                    'replace' => $config['replace'],
228
                ]);
229
230
                break;
231 1
            case 'header_append':
232 1
            case 'header_defaults':
233 1
            case 'header_set':
234 1
            case 'header_remove':
235 1
                $definition->replaceArgument(0, $config['headers']);
236
237 1
                break;
238
239 1
            case 'query_defaults':
240 1
                $definition->replaceArgument(0, $config['parameters']);
241
242 1
                break;
243
244
            default:
245
                throw new \InvalidArgumentException(sprintf('Internal exception: Plugin %s is not handled', $name));
246
        }
247 25
    }
248
249
    /**
250
     * @param ContainerBuilder $container
251
     * @param array            $config
252
     *
253
     * @return array list of service ids for the authentication plugins
254
     */
255 5
    private function configureAuthentication(ContainerBuilder $container, array $config, $servicePrefix = 'httplug.plugin.authentication')
256
    {
257 5
        $pluginServices = [];
258
259 5
        foreach ($config as $name => $values) {
260 5
            $authServiceKey = sprintf($servicePrefix.'.%s.auth', $name);
261 5
            switch ($values['type']) {
262 5
                case 'bearer':
263
                    $container->register($authServiceKey, Bearer::class)
264
                        ->addArgument($values['token']);
265
266
                    break;
267 5
                case 'basic':
268 5
                    $container->register($authServiceKey, BasicAuth::class)
269 5
                        ->addArgument($values['username'])
270 5
                        ->addArgument($values['password']);
271
272 5
                    break;
273
                case 'wsse':
274
                    $container->register($authServiceKey, Wsse::class)
275
                        ->addArgument($values['username'])
276
                        ->addArgument($values['password']);
277
278
                    break;
279
                case 'query_param':
280
                    $container->register($authServiceKey, QueryParam::class)
281
                        ->addArgument($values['params']);
282
283
                    break;
284
                case 'service':
285
                    $authServiceKey = $values['service'];
286
287
                    break;
288
                default:
289
                    throw new \LogicException(sprintf('Unknown authentication type: "%s"', $values['type']));
290
            }
291
292 5
            $pluginServiceKey = $servicePrefix.'.'.$name;
293 5
            $container->register($pluginServiceKey, AuthenticationPlugin::class)
294 5
                ->addArgument(new Reference($authServiceKey))
295
            ;
296 5
            $pluginServices[] = $pluginServiceKey;
297
        }
298
299 5
        return $pluginServices;
300
    }
301
302
    /**
303
     * @param ContainerBuilder $container
304
     * @param string           $clientName
305
     * @param array            $arguments
306
     */
307 17
    private function configureClient(ContainerBuilder $container, $clientName, array $arguments)
308
    {
309 17
        $serviceId = 'httplug.client.'.$clientName;
310
311 17
        $plugins = [];
312 17
        foreach ($arguments['plugins'] as $plugin) {
313 8
            $pluginName = key($plugin);
314 8
            $pluginConfig = current($plugin);
315 8
            if ('reference' === $pluginName) {
316 8
                $plugins[] = $pluginConfig['id'];
317 5
            } elseif ('authentication' === $pluginName) {
318 5
                $plugins = array_merge($plugins, $this->configureAuthentication($container, $pluginConfig, $serviceId.'.authentication'));
319
            } else {
320 8
                $plugins[] = $this->configurePlugin($container, $serviceId, $pluginName, $pluginConfig);
321
            }
322
        }
323
324 17
        if (empty($arguments['service'])) {
325
            $container
326 16
                ->register($serviceId.'.client', HttpClient::class)
327 16
                ->setFactory([new Reference($arguments['factory']), 'createClient'])
328 16
                ->addArgument($arguments['config'])
329 16
                ->setPublic(false);
330
        } else {
331
            $container
332 1
                ->setAlias($serviceId.'.client', new Alias($arguments['service'], false));
333
        }
334
335
        $definition = $container
336 17
            ->register($serviceId, PluginClient::class)
337 17
            ->setFactory([new Reference(PluginClientFactory::class), 'createClient'])
338 17
            ->addArgument(new Reference($serviceId.'.client'))
339 17
            ->addArgument(
340 17
                array_map(
341 17
                    function ($id) {
342 8
                        return new Reference($id);
343 17
                    },
344
                    $plugins
345
                )
346
            )
347 17
            ->addArgument([
348 17
                'client_name' => $clientName,
349
            ])
350
        ;
351
352 17
        if (is_bool($arguments['public'])) {
353 4
            $definition->setPublic($arguments['public']);
354
        }
355
356
        /*
357
         * Decorate the client with clients from client-common
358
         */
359 17 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...
360
            $container
361 2
                ->register($serviceId.'.flexible', FlexibleHttpClient::class)
362 2
                ->addArgument(new Reference($serviceId.'.flexible.inner'))
363 2
                ->setPublic($arguments['public'] ? true : false)
364 2
                ->setDecoratedService($serviceId)
365
            ;
366
        }
367
368 17 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...
369
            $container
370 2
                ->register($serviceId.'.http_methods', HttpMethodsClient::class)
371 2
                ->setArguments([new Reference($serviceId.'.http_methods.inner'), new Reference('httplug.message_factory')])
372 2
                ->setPublic($arguments['public'] ? true : false)
373 2
                ->setDecoratedService($serviceId)
374
            ;
375
        }
376
377 17 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...
378
            $container
379 2
                ->register($serviceId.'.batch_client', BatchClient::class)
380 2
                ->setArguments([new Reference($serviceId.'.batch_client.inner')])
381 2
                ->setPublic($arguments['public'] ? true : false)
382 2
                ->setDecoratedService($serviceId)
383
            ;
384
        }
385 17
    }
386
387
    /**
388
     * Create a URI object with the default URI factory.
389
     *
390
     * @param ContainerBuilder $container
391
     * @param string           $serviceId Name of the private service to create
392
     * @param string           $uri       String representation of the URI
393
     */
394 5
    private function createUri(ContainerBuilder $container, $serviceId, $uri)
395
    {
396
        $container
397 5
            ->register($serviceId, UriInterface::class)
398 5
            ->setPublic(false)
399 5
            ->setFactory([new Reference('httplug.uri_factory'), 'createUri'])
400 5
            ->addArgument($uri)
401
        ;
402 5
    }
403
404
    /**
405
     * Make the user can select what client is used for auto discovery. If none is provided, a service will be created
406
     * by finding a client using auto discovery.
407
     *
408
     * @param ContainerBuilder $container
409
     * @param array            $config
410
     */
411 25
    private function configureAutoDiscoveryClients(ContainerBuilder $container, array $config)
412
    {
413 25
        $httpClient = $config['discovery']['client'];
414 25 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...
415 2
            $container->removeDefinition('httplug.auto_discovery.auto_discovered_client');
416 2
            $container->removeDefinition('httplug.collector.auto_discovered_client');
417
418 2
            if (!empty($httpClient)) {
419 1
                $container->setAlias('httplug.auto_discovery.auto_discovered_client', $httpClient);
420 1
                $container->getAlias('httplug.auto_discovery.auto_discovered_client')->setPublic(false);
421
            }
422
        }
423
424 25
        $asyncHttpClient = $config['discovery']['async_client'];
425 25 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...
426 23
            $container->removeDefinition('httplug.auto_discovery.auto_discovered_async');
427 23
            $container->removeDefinition('httplug.collector.auto_discovered_async');
428
429 23
            if (!empty($asyncHttpClient)) {
430 1
                $container->setAlias('httplug.auto_discovery.auto_discovered_async', $asyncHttpClient);
431 1
                $container->getAlias('httplug.auto_discovery.auto_discovered_async')->setPublic(false);
432
            }
433
        }
434
435 25
        if (null === $httpClient && null === $asyncHttpClient) {
436 1
            $container->removeDefinition('httplug.strategy');
437
438 1
            return;
439
        }
440 24
    }
441
442
    /**
443
     * {@inheritdoc}
444
     */
445 25
    public function getConfiguration(array $config, ContainerBuilder $container)
446
    {
447 25
        return new Configuration($container->getParameter('kernel.debug'));
448
    }
449
450
    /**
451
     * Configure a plugin using the parent definition from plugins.xml.
452
     *
453
     * @param ContainerBuilder $container
454
     * @param string           $serviceId
455
     * @param string           $pluginName
456
     * @param array            $pluginConfig
457
     *
458
     * @return string configured service id
459
     */
460 5
    private function configurePlugin(ContainerBuilder $container, $serviceId, $pluginName, array $pluginConfig)
461
    {
462 5
        $pluginServiceId = $serviceId.'.plugin.'.$pluginName;
463
464 5
        $definition = class_exists(ChildDefinition::class)
465 5
            ? new ChildDefinition('httplug.plugin.'.$pluginName)
466 5
            : new DefinitionDecorator('httplug.plugin.'.$pluginName);
467
468 5
        $this->configurePluginByName($pluginName, $definition, $pluginConfig, $container, $pluginServiceId);
469 5
        $container->setDefinition($pluginServiceId, $definition);
470
471 5
        return $pluginServiceId;
472
    }
473
}
474