Completed
Push — master ( f034b1...756441 )
by David
08:54
created

HttplugExtension::load()   C

Complexity

Conditions 7
Paths 36

Size

Total Lines 48
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 7

Importance

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