Completed
Push — master ( 3a0383...1b0026 )
by Fabien
03:21
created

HttplugExtension::configureAutoDiscoveryClients()   C

Complexity

Conditions 7
Paths 18

Size

Total Lines 32
Code Lines 20

Duplication

Lines 20
Ratio 62.5 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 0
Metric Value
dl 20
loc 32
ccs 0
cts 0
cp 0
rs 6.7272
c 0
b 0
f 0
cc 7
eloc 20
nc 18
nop 2
crap 56
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\HttplugBundle\ClientFactory\DummyClient;
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
                break;
153 15
            case 'cookie':
154
                $definition->replaceArgument(0, new Reference($config['cookie_jar']));
155
                break;
156 15
            case 'decoder':
157 15
                $definition->addArgument([
158 15
                    'use_content_encoding' => $config['use_content_encoding'],
159 15
                ]);
160 15
                break;
161 15
            case 'history':
162
                $definition->replaceArgument(0, new Reference($config['journal']));
163
                break;
164 15
            case 'logger':
165 15
                $definition->replaceArgument(0, new Reference($config['logger']));
166 15
                if (!empty($config['formatter'])) {
167
                    $definition->replaceArgument(1, new Reference($config['formatter']));
168
                }
169 15
                break;
170 15
            case 'redirect':
171 15
                $definition->addArgument([
172 15
                    'preserve_header' => $config['preserve_header'],
173 15
                    'use_default_for_multiple' => $config['use_default_for_multiple'],
174 15
                ]);
175 15
                break;
176 15
            case 'retry':
177 15
                $definition->addArgument([
178 15
                    'retries' => $config['retry'],
179 15
                ]);
180 15
                break;
181 15
            case 'stopwatch':
182 15
                $definition->replaceArgument(0, new Reference($config['stopwatch']));
183 15
                break;
184
185
            /* client specific plugins */
186
187 5
            case 'add_host':
188 5
                $uriService = $serviceId.'.host_uri';
189 5
                $this->createUri($container, $uriService, $config['host']);
190 5
                $definition->replaceArgument(0, new Reference($uriService));
191 5
                $definition->replaceArgument(1, [
192 5
                    'replace' => $config['replace'],
193 5
                ]);
194 5
                break;
195 1
            case 'header_append':
196 1
            case 'header_defaults':
197 1
            case 'header_set':
198 1
            case 'header_remove':
199 1
                $definition->replaceArgument(0, $config['headers']);
200 1
                break;
201
202
            default:
203
                throw new \InvalidArgumentException(sprintf('Internal exception: Plugin %s is not handled', $name));
204
        }
205 15
    }
206
207
    /**
208
     * @param ContainerBuilder $container
209
     * @param array            $config
210
     *
211
     * @return array List of service ids for the authentication plugins.
212
     */
213 5
    private function configureAuthentication(ContainerBuilder $container, array $config, $servicePrefix = 'httplug.plugin.authentication')
214
    {
215 5
        $pluginServices = [];
216
217 5
        foreach ($config as $name => $values) {
218 5
            $authServiceKey = sprintf($servicePrefix.'.%s.auth', $name);
219 5
            switch ($values['type']) {
220 5
                case 'bearer':
221
                    $container->register($authServiceKey, Bearer::class)
222
                        ->addArgument($values['token']);
223
                    break;
224 5
                case 'basic':
225
                    $container->register($authServiceKey, BasicAuth::class)
226 5
                        ->addArgument($values['username'])
227 5
                        ->addArgument($values['password']);
228 5
                    break;
229
                case 'wsse':
230
                    $container->register($authServiceKey, Wsse::class)
231
                        ->addArgument($values['username'])
232
                        ->addArgument($values['password']);
233
                    break;
234
                case 'service':
235
                    $authServiceKey = $values['service'];
236
                    break;
237
                default:
238
                    throw new \LogicException(sprintf('Unknown authentication type: "%s"', $values['type']));
239 5
            }
240
241 5
            $pluginServiceKey = $servicePrefix.'.'.$name;
242
            $container->register($pluginServiceKey, AuthenticationPlugin::class)
243
                ->addArgument(new Reference($authServiceKey))
244
            ;
245
            $pluginServices[] = $pluginServiceKey;
246
        }
247
248
        return $pluginServices;
249
    }
250
251
    /**
252
     * @param ContainerBuilder $container
253
     * @param string           $clientName
254
     * @param array            $arguments
255
     * @param bool             $profiling
256
     */
257
    private function configureClient(ContainerBuilder $container, $clientName, array $arguments, $profiling)
258
    {
259
        $serviceId = 'httplug.client.'.$clientName;
260
261
        $plugins = [];
262
        foreach ($arguments['plugins'] as $plugin) {
263
            list($pluginName, $pluginConfig) = each($plugin);
264
            if ('reference' === $pluginName) {
265
                $plugins[] = $pluginConfig['id'];
266
            } elseif ('authentication' === $pluginName) {
267
                $plugins = array_merge($plugins, $this->configureAuthentication($container, $pluginConfig, $serviceId.'.authentication'));
268
            } else {
269
                $plugins[] = $this->configurePlugin($container, $serviceId, $pluginName, $pluginConfig);
270
            }
271
        }
272
273
        $pluginClientOptions = [];
274
        if ($profiling) {
275
            //Decorate each plugin with a ProfilePlugin instance.
276
            foreach ($plugins as $pluginServiceId) {
277
                $this->decoratePluginWithProfilePlugin($container, $pluginServiceId);
278
            }
279
280
            // To profile the requests, add a StackPlugin as first plugin in the chain.
281
            $stackPluginId = $this->configureStackPlugin($container, $clientName, $serviceId);
282
            array_unshift($plugins, $stackPluginId);
283
        }
284
285
        $container
286
            ->register($serviceId, DummyClient::class)
287
            ->setFactory([PluginClientFactory::class, 'createPluginClient'])
288
            ->addArgument(
289
                array_map(
290
                    function ($id) {
291
                        return new Reference($id);
292
                    },
293
                    $plugins
294
                )
295
            )
296
            ->addArgument(new Reference($arguments['factory']))
297
            ->addArgument($arguments['config'])
298
            ->addArgument($pluginClientOptions)
299
        ;
300
301
        /*
302
         * Decorate the client with clients from client-common
303
         */
304 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...
305
            $container
306
                ->register($serviceId.'.flexible', FlexibleHttpClient::class)
307
                ->addArgument(new Reference($serviceId.'.flexible.inner'))
308
                ->setPublic(false)
309
                ->setDecoratedService($serviceId)
310
            ;
311
        }
312
313 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...
314
            $container
315
                ->register($serviceId.'.http_methods', HttpMethodsClient::class)
316
                ->setArguments([new Reference($serviceId.'.http_methods.inner'), new Reference('httplug.message_factory')])
317
                ->setPublic(false)
318
                ->setDecoratedService($serviceId)
319
            ;
320
        }
321
322 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...
323
            $container
324
                ->register($serviceId.'.batch_client', BatchClient::class)
325
                ->setArguments([new Reference($serviceId.'.batch_client.inner')])
326
                ->setPublic(false)
327
                ->setDecoratedService($serviceId)
328
            ;
329
        }
330
    }
331
332
    /**
333
     * Create a URI object with the default URI factory.
334
     *
335
     * @param ContainerBuilder $container
336
     * @param string           $serviceId Name of the private service to create
337
     * @param string           $uri       String representation of the URI
338
     */
339
    private function createUri(ContainerBuilder $container, $serviceId, $uri)
340
    {
341
        $container
342
            ->register($serviceId, UriInterface::class)
343
            ->setPublic(false)
344
            ->setFactory([new Reference('httplug.uri_factory'), 'createUri'])
345
            ->addArgument($uri)
346
        ;
347
    }
348
349
    /**
350
     * Make the user can select what client is used for auto discovery. If none is provided, a service will be created
351
     * by finding a client using auto discovery.
352
     *
353
     * @param ContainerBuilder $container
354
     * @param array            $config
355
     */
356
    private function configureAutoDiscoveryClients(ContainerBuilder $container, array $config)
357
    {
358
        $httpClient = $config['discovery']['client'];
359 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...
360
            $container->removeDefinition('httplug.auto_discovery.auto_discovered_client');
361
            $container->removeDefinition('httplug.collector.auto_discovered_client');
362
            $container->removeDefinition('httplug.auto_discovery.auto_discovered_client.plugin');
363
364
            if (!empty($httpClient)) {
365
                $container->setAlias('httplug.auto_discovery.auto_discovered_client', $httpClient);
366
                $container->getAlias('httplug.auto_discovery.auto_discovered_client')->setPublic(false);
367
            }
368
        }
369
370
        $asyncHttpClient = $config['discovery']['async_client'];
371 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...
372
            $container->removeDefinition('httplug.auto_discovery.auto_discovered_async');
373
            $container->removeDefinition('httplug.collector.auto_discovered_async');
374
            $container->removeDefinition('httplug.auto_discovery.auto_discovered_async.plugin');
375
376
            if (!empty($asyncHttpClient)) {
377
                $container->setAlias('httplug.auto_discovery.auto_discovered_async', $asyncHttpClient);
378
                $container->getAlias('httplug.auto_discovery.auto_discovered_async')->setPublic(false);
379
            }
380
        }
381
382
        if (null === $httpClient && null === $asyncHttpClient) {
383
            $container->removeDefinition('httplug.strategy');
384
385
            return;
386
        }
387
    }
388
389
    /**
390
     * {@inheritdoc}
391
     */
392
    public function getConfiguration(array $config, ContainerBuilder $container)
393
    {
394
        return new Configuration($container->getParameter('kernel.debug'));
395
    }
396
397
    /**
398
     * Configure a plugin using the parent definition from plugins.xml.
399
     *
400
     * @param ContainerBuilder $container
401
     * @param string           $serviceId
402
     * @param string           $pluginName
403
     * @param array            $pluginConfig
404
     *
405
     * @return string configured service id
406
     */
407
    private function configurePlugin(ContainerBuilder $container, $serviceId, $pluginName, array $pluginConfig)
408
    {
409
        $pluginServiceId = $serviceId.'.plugin.'.$pluginName;
410
411
        $definition = class_exists(ChildDefinition::class)
412
            ? new ChildDefinition('httplug.plugin.'.$pluginName)
413
            : new DefinitionDecorator('httplug.plugin.'.$pluginName);
0 ignored issues
show
Deprecated Code introduced by
The class Symfony\Component\Depend...ion\DefinitionDecorator has been deprecated with message: The DefinitionDecorator class is deprecated since version 3.3 and will be removed in 4.0. Use the Symfony\Component\DependencyInjection\ChildDefinition class instead.

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
414
415
        $this->configurePluginByName($pluginName, $definition, $pluginConfig, $container, $pluginServiceId);
416
        $container->setDefinition($pluginServiceId, $definition);
417
418
        return $pluginServiceId;
419
    }
420
421
    /**
422
     * Decorate the plugin service with a ProfilePlugin service.
423
     *
424
     * @param ContainerBuilder $container
425
     * @param string           $pluginServiceId
426
     */
427
    private function decoratePluginWithProfilePlugin(ContainerBuilder $container, $pluginServiceId)
428
    {
429
        $container->register($pluginServiceId.'.debug', ProfilePlugin::class)
430
            ->setDecoratedService($pluginServiceId)
431
            ->setArguments([
432
                new Reference($pluginServiceId.'.debug.inner'),
433
                new Reference('httplug.collector.collector'),
434
                new Reference('httplug.collector.formatter'),
435
            ])
436
            ->setPublic(false);
437
    }
438
439
    /**
440
     * Configure a StackPlugin for a client.
441
     *
442
     * @param ContainerBuilder $container
443
     * @param string           $clientName Client name to display in the profiler.
444
     * @param string           $serviceId  Client service id. Used as base for the StackPlugin service id.
445
     *
446
     * @return string configured StackPlugin service id
447
     */
448
    private function configureStackPlugin(ContainerBuilder $container, $clientName, $serviceId)
449
    {
450
        $pluginServiceId = $serviceId.'.plugin.stack';
451
452
        $definition = class_exists(ChildDefinition::class)
453
            ? new ChildDefinition('httplug.plugin.stack')
454
            : new DefinitionDecorator('httplug.plugin.stack');
0 ignored issues
show
Deprecated Code introduced by
The class Symfony\Component\Depend...ion\DefinitionDecorator has been deprecated with message: The DefinitionDecorator class is deprecated since version 3.3 and will be removed in 4.0. Use the Symfony\Component\DependencyInjection\ChildDefinition class instead.

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
455
456
        $definition->addArgument($clientName);
457
        $container->setDefinition($pluginServiceId, $definition);
458
459
        return $pluginServiceId;
460
    }
461
}
462