Completed
Push — master ( aacd61...f09345 )
by David
03:18
created

HttplugExtension::createUri()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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