Completed
Push — master ( 720695...de9ede )
by Fabien
14:35
created

HttplugExtension::getConfiguration()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

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
eloc 2
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\FlexibleHttpClient;
7
use Http\Client\Common\HttpMethodsClient;
8
use Http\Client\Common\Plugin\AuthenticationPlugin;
9
use Http\Client\Common\PluginClient;
10
use Http\HttplugBundle\ClientFactory\PluginClientFactory;
11
use Http\HttplugBundle\Collector\ProfilePlugin;
12
use Http\Message\Authentication\BasicAuth;
13
use Http\Message\Authentication\Bearer;
14
use Http\Message\Authentication\Wsse;
15
use Psr\Http\Message\UriInterface;
16
use Symfony\Component\Config\FileLocator;
17
use Symfony\Component\DependencyInjection\ChildDefinition;
18
use Symfony\Component\DependencyInjection\ContainerBuilder;
19
use Symfony\Component\DependencyInjection\Definition;
20
use Symfony\Component\DependencyInjection\DefinitionDecorator;
21
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
22
use Symfony\Component\DependencyInjection\Reference;
23
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
24
25
/**
26
 * @author David Buchmann <[email protected]>
27
 * @author Tobias Nyholm <[email protected]>
28
 */
29
class HttplugExtension extends Extension
30
{
31
    /**
32
     * {@inheritdoc}
33
     */
34 15
    public function load(array $configs, ContainerBuilder $container)
35
    {
36 15
        $configuration = $this->getConfiguration($configs, $container);
37 15
        $config = $this->processConfiguration($configuration, $configs);
38
39 15
        $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
40
41 15
        $loader->load('services.xml');
42 15
        $loader->load('plugins.xml');
43
44
        // Register default services
45 15
        foreach ($config['classes'] as $service => $class) {
46 15
            if (!empty($class)) {
47 1
                $container->register(sprintf('httplug.%s.default', $service), $class);
48 1
            }
49 15
        }
50
51
        // Set main aliases
52 15
        foreach ($config['main_alias'] as $type => $id) {
53 15
            $container->setAlias(sprintf('httplug.%s', $type), $id);
54 15
        }
55
56
        // Configure toolbar
57 15
        $profilingEnabled = $this->isConfigEnabled($container, $config['profiling']);
58 15
        if ($profilingEnabled) {
59 12
            $loader->load('data-collector.xml');
60
61 12
            if (!empty($config['profiling']['formatter'])) {
62
                // Add custom formatter
63
                $container
64 1
                    ->getDefinition('httplug.collector.formatter')
65 1
                    ->replaceArgument(0, new Reference($config['profiling']['formatter']))
66
                ;
67 1
            }
68
69
            $container
70 12
                ->getDefinition('httplug.formatter.full_http_message')
71 12
                ->addArgument($config['profiling']['captured_body_length'])
72
            ;
73 12
        }
74
75 15
        $this->configureClients($container, $config, $profilingEnabled);
76 15
        $this->configurePlugins($container, $config['plugins']); // must be after clients, as clients.X.plugins might use plugins as templates that will be removed
77 15
        $this->configureAutoDiscoveryClients($container, $config);
78 15
    }
79
80
    /**
81
     * Configure client services.
82
     *
83
     * @param ContainerBuilder $container
84
     * @param array            $config
85
     * @param bool             $profiling
86
     */
87 15
    private function configureClients(ContainerBuilder $container, array $config, $profiling)
0 ignored issues
show
Unused Code introduced by
The parameter $profiling is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

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