Completed
Push — master ( 853c4f...e19839 )
by David
15:41 queued 10s
created

HttplugExtension::configureClients()   A

Complexity

Conditions 5
Paths 9

Size

Total Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 5

Importance

Changes 0
Metric Value
dl 0
loc 24
ccs 12
cts 12
cp 1
rs 9.2248
c 0
b 0
f 0
cc 5
nc 9
nop 2
crap 5
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
            default:
236
                throw new \InvalidArgumentException(sprintf('Internal exception: Plugin %s is not handled', $name));
237
        }
238 22
    }
239
240
    /**
241
     * @param ContainerBuilder $container
242
     * @param array            $config
243
     *
244
     * @return array List of service ids for the authentication plugins.
245
     */
246 5
    private function configureAuthentication(ContainerBuilder $container, array $config, $servicePrefix = 'httplug.plugin.authentication')
247
    {
248 5
        $pluginServices = [];
249
250 5
        foreach ($config as $name => $values) {
251 5
            $authServiceKey = sprintf($servicePrefix.'.%s.auth', $name);
252 5
            switch ($values['type']) {
253 5
                case 'bearer':
254
                    $container->register($authServiceKey, Bearer::class)
255
                        ->addArgument($values['token']);
256
257
                    break;
258 5
                case 'basic':
259 5
                    $container->register($authServiceKey, BasicAuth::class)
260 5
                        ->addArgument($values['username'])
261 5
                        ->addArgument($values['password']);
262
263 5
                    break;
264
                case 'wsse':
265
                    $container->register($authServiceKey, Wsse::class)
266
                        ->addArgument($values['username'])
267
                        ->addArgument($values['password']);
268
269
                    break;
270
                case 'query_param':
271
                    $container->register($authServiceKey, QueryParam::class)
272
                        ->addArgument($values['params']);
273
274
                    break;
275
                case 'service':
276
                    $authServiceKey = $values['service'];
277
278
                    break;
279
                default:
280
                    throw new \LogicException(sprintf('Unknown authentication type: "%s"', $values['type']));
281
            }
282
283 5
            $pluginServiceKey = $servicePrefix.'.'.$name;
284 5
            $container->register($pluginServiceKey, AuthenticationPlugin::class)
285 5
                ->addArgument(new Reference($authServiceKey))
286
            ;
287 5
            $pluginServices[] = $pluginServiceKey;
288
        }
289
290 5
        return $pluginServices;
291
    }
292
293
    /**
294
     * @param ContainerBuilder $container
295
     * @param string           $clientName
296
     * @param array            $arguments
297
     */
298 15
    private function configureClient(ContainerBuilder $container, $clientName, array $arguments)
299
    {
300 15
        $serviceId = 'httplug.client.'.$clientName;
301
302 15
        $plugins = [];
303 15
        foreach ($arguments['plugins'] as $plugin) {
304 6
            $pluginName = key($plugin);
305 6
            $pluginConfig = current($plugin);
306 6
            if ('reference' === $pluginName) {
307 6
                $plugins[] = $pluginConfig['id'];
308 5
            } elseif ('authentication' === $pluginName) {
309 5
                $plugins = array_merge($plugins, $this->configureAuthentication($container, $pluginConfig, $serviceId.'.authentication'));
310
            } else {
311 6
                $plugins[] = $this->configurePlugin($container, $serviceId, $pluginName, $pluginConfig);
312
            }
313
        }
314
315 15
        if (empty($arguments['service'])) {
316
            $container
317 14
                ->register($serviceId.'.client', HttpClient::class)
318 14
                ->setFactory([new Reference($arguments['factory']), 'createClient'])
319 14
                ->addArgument($arguments['config'])
320 14
                ->setPublic(false);
321
        } else {
322
            $container
323 1
                ->setAlias($serviceId.'.client', new Alias($arguments['service'], false));
324
        }
325
326
        $definition = $container
327 15
            ->register($serviceId, PluginClient::class)
328 15
            ->setFactory([new Reference(PluginClientFactory::class), 'createClient'])
329 15
            ->addArgument(new Reference($serviceId.'.client'))
330 15
            ->addArgument(
331 15
                array_map(
332 15
                    function ($id) {
333 6
                        return new Reference($id);
334 15
                    },
335 15
                    $plugins
336
                )
337
            )
338 15
            ->addArgument([
339 15
                'client_name' => $clientName,
340
            ])
341
        ;
342
343 15
        if (is_bool($arguments['public'])) {
344 4
            $definition->setPublic($arguments['public']);
345
        }
346
347
        /*
348
         * Decorate the client with clients from client-common
349
         */
350 15 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...
351
            $container
352 2
                ->register($serviceId.'.flexible', FlexibleHttpClient::class)
353 2
                ->addArgument(new Reference($serviceId.'.flexible.inner'))
354 2
                ->setPublic($arguments['public'] ? true : false)
355 2
                ->setDecoratedService($serviceId)
356
            ;
357
        }
358
359 15 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...
360
            $container
361 2
                ->register($serviceId.'.http_methods', HttpMethodsClient::class)
362 2
                ->setArguments([new Reference($serviceId.'.http_methods.inner'), new Reference('httplug.message_factory')])
363 2
                ->setPublic($arguments['public'] ? true : false)
364 2
                ->setDecoratedService($serviceId)
365
            ;
366
        }
367
368 15 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...
369
            $container
370 2
                ->register($serviceId.'.batch_client', BatchClient::class)
371 2
                ->setArguments([new Reference($serviceId.'.batch_client.inner')])
372 2
                ->setPublic($arguments['public'] ? true : false)
373 2
                ->setDecoratedService($serviceId)
374
            ;
375
        }
376 15
    }
377
378
    /**
379
     * Create a URI object with the default URI factory.
380
     *
381
     * @param ContainerBuilder $container
382
     * @param string           $serviceId Name of the private service to create
383
     * @param string           $uri       String representation of the URI
384
     */
385 5
    private function createUri(ContainerBuilder $container, $serviceId, $uri)
386
    {
387
        $container
388 5
            ->register($serviceId, UriInterface::class)
389 5
            ->setPublic(false)
390 5
            ->setFactory([new Reference('httplug.uri_factory'), 'createUri'])
391 5
            ->addArgument($uri)
392
        ;
393 5
    }
394
395
    /**
396
     * Make the user can select what client is used for auto discovery. If none is provided, a service will be created
397
     * by finding a client using auto discovery.
398
     *
399
     * @param ContainerBuilder $container
400
     * @param array            $config
401
     */
402 22
    private function configureAutoDiscoveryClients(ContainerBuilder $container, array $config)
403
    {
404 22
        $httpClient = $config['discovery']['client'];
405 22 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...
406 2
            $container->removeDefinition('httplug.auto_discovery.auto_discovered_client');
407 2
            $container->removeDefinition('httplug.collector.auto_discovered_client');
408
409 2
            if (!empty($httpClient)) {
410 1
                $container->setAlias('httplug.auto_discovery.auto_discovered_client', $httpClient);
411 1
                $container->getAlias('httplug.auto_discovery.auto_discovered_client')->setPublic(false);
412
            }
413
        }
414
415 22
        $asyncHttpClient = $config['discovery']['async_client'];
416 22 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...
417 20
            $container->removeDefinition('httplug.auto_discovery.auto_discovered_async');
418 20
            $container->removeDefinition('httplug.collector.auto_discovered_async');
419
420 20
            if (!empty($asyncHttpClient)) {
421 1
                $container->setAlias('httplug.auto_discovery.auto_discovered_async', $asyncHttpClient);
422 1
                $container->getAlias('httplug.auto_discovery.auto_discovered_async')->setPublic(false);
423
            }
424
        }
425
426 22
        if (null === $httpClient && null === $asyncHttpClient) {
427 1
            $container->removeDefinition('httplug.strategy');
428
429 1
            return;
430
        }
431 21
    }
432
433
    /**
434
     * {@inheritdoc}
435
     */
436 22
    public function getConfiguration(array $config, ContainerBuilder $container)
437
    {
438 22
        return new Configuration($container->getParameter('kernel.debug'));
439
    }
440
441
    /**
442
     * Configure a plugin using the parent definition from plugins.xml.
443
     *
444
     * @param ContainerBuilder $container
445
     * @param string           $serviceId
446
     * @param string           $pluginName
447
     * @param array            $pluginConfig
448
     *
449
     * @return string configured service id
450
     */
451 5
    private function configurePlugin(ContainerBuilder $container, $serviceId, $pluginName, array $pluginConfig)
452
    {
453 5
        $pluginServiceId = $serviceId.'.plugin.'.$pluginName;
454
455 5
        $definition = class_exists(ChildDefinition::class)
456 5
            ? new ChildDefinition('httplug.plugin.'.$pluginName)
457 5
            : new DefinitionDecorator('httplug.plugin.'.$pluginName);
458
459 5
        $this->configurePluginByName($pluginName, $definition, $pluginConfig, $container, $pluginServiceId);
460 5
        $container->setDefinition($pluginServiceId, $definition);
461
462 5
        return $pluginServiceId;
463
    }
464
}
465