Completed
Pull Request — master (#111)
by David
07:41
created

HttplugExtension::createUri()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 9
ccs 0
cts 0
cp 0
rs 9.6666
cc 1
eloc 6
nc 1
nop 3
crap 2
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\AddHostPlugin;
9
use Http\Client\Common\Plugin\AuthenticationPlugin;
10
use Http\Discovery\HttpAsyncClientDiscovery;
11
use Http\Discovery\HttpClientDiscovery;
12
use Http\HttplugBundle\ClientFactory\DummyClient;
13
use Http\HttplugBundle\ClientFactory\PluginClientFactory;
14
use Http\HttplugBundle\Collector\DebugPlugin;
15
use Http\Message\Authentication\BasicAuth;
16
use Http\Message\Authentication\Bearer;
17
use Http\Message\Authentication\Wsse;
18
use Psr\Http\Message\UriInterface;
19
use Symfony\Component\Config\FileLocator;
20
use Symfony\Component\DependencyInjection\ContainerBuilder;
21
use Symfony\Component\DependencyInjection\Definition;
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
     */
35 8
    public function load(array $configs, ContainerBuilder $container)
36
    {
37 8
        $configuration = $this->getConfiguration($configs, $container);
38 8
        $config = $this->processConfiguration($configuration, $configs);
1 ignored issue
show
Bug introduced by
It seems like $configuration defined by $this->getConfiguration($configs, $container) on line 37 can be null; however, Symfony\Component\Depend...:processConfiguration() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
39
40 8
        $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
41
42 8
        $loader->load('services.xml');
43 8
        $loader->load('plugins.xml');
44
45
        // Register default services
46 8
        foreach ($config['classes'] as $service => $class) {
47 8
            if (!empty($class)) {
48 1
                $container->register(sprintf('httplug.%s.default', $service), $class);
49 1
            }
50 8
        }
51
52
        // Set main aliases
53 8
        foreach ($config['main_alias'] as $type => $id) {
54 8
            $container->setAlias(sprintf('httplug.%s', $type), $id);
55 8
        }
56
57
        // Configure toolbar
58 8
        if ($config['profiling']['enabled']) {
59 5
            $loader->load('data-collector.xml');
60
61 5
            if (!empty($config['profiling']['formatter'])) {
62
                // Add custom formatter
63
                $container
64
                    ->getDefinition('httplug.collector.debug_collector')
65
                    ->replaceArgument(0, new Reference($config['profiling']['formatter']))
66
                ;
67
            }
68
69
            $container
70 5
                ->getDefinition('httplug.formatter.full_http_message')
71 5
                ->addArgument($config['profiling']['captured_body_length'])
72
            ;
73 5
        }
74
75 8
        $this->configurePlugins($container, $config['plugins']);
76 8
        $this->configureClients($container, $config);
77 8
        $this->configureAutoDiscoveryClients($container, $config);
78 8
    }
79
80
    /**
81
     * Configure client services.
82
     *
83
     * @param ContainerBuilder $container
84
     * @param array            $config
85
     */
86 8
    private function configureClients(ContainerBuilder $container, array $config)
87
    {
88 8
        $first = null;
89
90 8
        foreach ($config['clients'] as $name => $arguments) {
91 3
            if ($first === null) {
92
                // Save the name of the first configurated client.
93 3
                $first = $name;
94 3
            }
95
96 3
            $this->configureClient($container, $name, $arguments, $config['profiling']['enabled']);
97 8
        }
98
99
        // If we have clients configured
100 8
        if ($first !== null) {
101
            // If we do not have a client named 'default'
102 3
            if (!isset($config['clients']['default'])) {
103
                // Alias the first client to httplug.client.default
104 3
                $container->setAlias('httplug.client.default', 'httplug.client.'.$first);
105 3
            }
106 3
        }
107 8
    }
108
109
    /**
110
     * @param ContainerBuilder $container
111
     * @param array            $config
112
     */
113 8
    private function configurePlugins(ContainerBuilder $container, array $config)
114
    {
115 8
        if (!empty($config['authentication'])) {
116
            $this->configureAuthentication($container, $config['authentication']);
117
        }
118 8
        unset($config['authentication']);
119
120 8
        foreach ($config as $name => $pluginConfig) {
121 8
            $pluginId = 'httplug.plugin.'.$name;
122
123 8
            if ($pluginConfig['enabled']) {
124 8
                $def = $container->getDefinition($pluginId);
125 8
                $this->configurePluginByName($name, $def, $pluginConfig);
126 8
            } else {
127 8
                $container->removeDefinition($pluginId);
128
            }
129 8
        }
130 8
    }
131
132
    /**
133
     * @param string     $name
134
     * @param Definition $definition
135
     * @param array      $config
136
     */
137 8
    private function configurePluginByName($name, Definition $definition, array $config)
138
    {
139
        switch ($name) {
140 8
            case 'cache':
141
                $definition
142
                    ->replaceArgument(0, new Reference($config['cache_pool']))
143
                    ->replaceArgument(1, new Reference($config['stream_factory']))
144
                    ->replaceArgument(2, $config['config']);
145
                break;
146 8
            case 'cookie':
147
                $definition->replaceArgument(0, new Reference($config['cookie_jar']));
148
                break;
149 8
            case 'decoder':
150 8
                $definition->addArgument($config['use_content_encoding']);
151 8
                break;
152 8
            case 'history':
153
                $definition->replaceArgument(0, new Reference($config['journal']));
154
                break;
155 8
            case 'logger':
156 8
                $definition->replaceArgument(0, new Reference($config['logger']));
157 8
                if (!empty($config['formatter'])) {
158
                    $definition->replaceArgument(1, new Reference($config['formatter']));
159
                }
160 8
                break;
161 8
            case 'redirect':
162
                $definition
163 8
                    ->addArgument($config['preserve_header'])
164 8
                    ->addArgument($config['use_default_for_multiple']);
165 8
                break;
166 8
            case 'retry':
167 8
                $definition->addArgument($config['retry']);
168 8
                break;
169 8
            case 'stopwatch':
170 8
                $definition->replaceArgument(0, new Reference($config['stopwatch']));
171 8
                break;
172
        }
173 8
    }
174
175
    /**
176
     * @param ContainerBuilder $container
177
     * @param array            $config
178
     */
179
    private function configureAuthentication(ContainerBuilder $container, array $config)
180
    {
181
        foreach ($config as $name => $values) {
182
            $authServiceKey = sprintf('httplug.plugin.authentication.%s.auth', $name);
183
            switch ($values['type']) {
184
                case 'bearer':
185
                    $container->register($authServiceKey, Bearer::class)
186
                        ->addArgument($values['token']);
187
                    break;
188
                case 'basic':
189
                    $container->register($authServiceKey, BasicAuth::class)
190
                        ->addArgument($values['username'])
191
                        ->addArgument($values['password']);
192
                    break;
193
                case 'wsse':
194
                    $container->register($authServiceKey, Wsse::class)
195
                        ->addArgument($values['username'])
196
                        ->addArgument($values['password']);
197
                    break;
198
                case 'service':
199
                    $authServiceKey = $values['service'];
200
                    break;
201
                default:
202
                    throw new \LogicException(sprintf('Unknown authentication type: "%s"', $values['type']));
203
            }
204
205
            $container->register('httplug.plugin.authentication.'.$name, AuthenticationPlugin::class)
206
                ->addArgument(new Reference($authServiceKey));
207
        }
208
    }
209
210
    /**
211
     * @param ContainerBuilder $container
212
     * @param string           $name
213
     * @param array            $arguments
214
     * @param bool             $profiling
215
     */
216
    private function configureClient(ContainerBuilder $container, $name, array $arguments, $profiling)
217
    {
218
        $serviceId = 'httplug.client.'.$name;
219
220
        $plugins = $arguments['plugins'];
221
        $pluginClientOptions = [];
222
223
        if ($profiling) {
224
            if (!in_array('httplug.plugin.stopwatch', $arguments['plugins'])) {
225
                // Add the stopwatch plugin
226
                array_unshift($arguments['plugins'], 'httplug.plugin.stopwatch');
227
            }
228
229
            // Tell the plugin journal what plugins we used
230
            $container
231
                ->getDefinition('httplug.collector.plugin_journal')
232
                ->addMethodCall('setPlugins', [$name, $arguments['plugins']])
233
            ;
234
235
            $debugPluginServiceId = $this->registerDebugPlugin($container, $serviceId);
236
237
            $pluginClientOptions['debug_plugins'] = [new Reference($debugPluginServiceId)];
238
        }
239
240
        if (array_key_exists('options', $arguments)) {
241
            foreach ($arguments['options'] as $option => $value) {
242
                switch ($option) {
243 View Code Duplication
                    case 'default_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...
244
                        $uriService = $serviceId.'.default_host_uri';
245
                        $addHostPlugin = $serviceId.'.default_host_plugin';
246
                        $this->createUri($container, $uriService, $value);
247
                        $container
248
                            ->register($addHostPlugin, AddHostPlugin::class)
249
                            ->setPublic(false)
250
                            ->addArgument(new Reference($uriService))
251
                        ;
252
                        if (!in_array($addHostPlugin, $plugins)) {
253
                            $plugins[] = $addHostPlugin;
254
                        }
255
                        break;
256 View Code Duplication
                    case 'force_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...
257
                        $uriService = $serviceId.'.force_host_uri';
258
                        $addHostPlugin = $serviceId.'.force_host_plugin';
259
                        $this->createUri($container, $uriService, $value);
260
                        $container
261
                            ->register($addHostPlugin, AddHostPlugin::class)
262
                            ->setPublic(false)
263
                            ->addArgument(new Reference($uriService))
264
                            ->addArgument(['replace' => true])
265
                        ;
266
                        if (!in_array($addHostPlugin, $plugins)) {
267
                            $plugins[] = $addHostPlugin;
268
                        }
269
                        break;
270
                }
271
            }
272
        }
273
274
        $container
275
            ->register($serviceId, DummyClient::class)
276
            ->setFactory([PluginClientFactory::class, 'createPluginClient'])
277
            ->addArgument(
278
                array_map(
279
                    function ($id) {
280
                        return new Reference($id);
281
                    },
282
                    $plugins
283
                )
284
            )
285
            ->addArgument(new Reference($arguments['factory']))
286
            ->addArgument($arguments['config'])
287
            ->addArgument($pluginClientOptions)
288
        ;
289
290
291
        /*
292
         * Decorate the client with clients from client-common
293
         */
294 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...
295
            $container
296
                ->register($serviceId.'.flexible', FlexibleHttpClient::class)
297
                ->addArgument(new Reference($serviceId.'.flexible.inner'))
298
                ->setPublic(false)
299
                ->setDecoratedService($serviceId)
300
            ;
301
        }
302
303 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...
304
            $container
305
                ->register($serviceId.'.http_methods', HttpMethodsClient::class)
306
                ->setArguments([new Reference($serviceId.'.http_methods.inner'), new Reference('httplug.message_factory')])
307
                ->setPublic(false)
308
                ->setDecoratedService($serviceId)
309
            ;
310
        }
311
312 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...
313
            $container
314
                ->register($serviceId.'.batch_client', BatchClient::class)
315
                ->setArguments([new Reference($serviceId.'.batch_client.inner')])
316
                ->setPublic(false)
317
                ->setDecoratedService($serviceId)
318
            ;
319
        }
320
    }
321
322
    /**
323
     * Create a URI object with the default URI factory.
324
     *
325
     * @param ContainerBuilder $container
326
     * @param string           $serviceId Name of the private service to create
327
     * @param string           $uri       String representation of the URI
328
     */
329
    private function createUri(ContainerBuilder $container, $serviceId, $uri)
330
    {
331
        $container
332
            ->register($serviceId, UriInterface::class)
333
            ->setPublic(false)
334
            ->setFactory([new Reference('httplug.uri_factory'), 'createUri'])
335
            ->addArgument($uri)
336
        ;
337
    }
338
339
    /**
340
     * Make the user can select what client is used for auto discovery. If none is provided, a service will be created
341
     * by finding a client using auto discovery.
342
     *
343
     * @param ContainerBuilder $container
344
     * @param array            $config
345
     */
346
    private function configureAutoDiscoveryClients(ContainerBuilder $container, array $config)
347
    {
348
        $httpClient = $config['discovery']['client'];
349
350 View Code Duplication
        if (!empty($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...
351
            if ($httpClient === 'auto') {
352
                $httpClient = $this->registerAutoDiscoverableClient(
353
                    $container,
354
                    'auto_discovered_client',
355
                    [HttpClientDiscovery::class, 'find'],
356
                    $config['profiling']['enabled']
357
                );
358
            }
359
360
            $httpClient = new Reference($httpClient);
361
        }
362
363
        $asyncHttpClient = $config['discovery']['async_client'];
364
365 View Code Duplication
        if (!empty($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...
366
            if ($asyncHttpClient === 'auto') {
367
                $asyncHttpClient = $this->registerAutoDiscoverableClient(
368
                    $container,
369
                    'auto_discovered_async',
370
                    [HttpAsyncClientDiscovery::class, 'find'],
371
                    $config['profiling']['enabled']
372
                );
373
            }
374
375
            $asyncHttpClient = new Reference($asyncHttpClient);
376
        }
377
378
        $container
379
            ->getDefinition('httplug.strategy')
380
            ->addArgument($httpClient)
381
            ->addArgument($asyncHttpClient)
382
        ;
383
    }
384
385
    /**
386
     * Find a client with auto discovery and return a service Reference to it.
387
     *
388
     * @param ContainerBuilder $container
389
     * @param string           $name
390
     * @param callable         $factory
391
     * @param bool             $profiling
392
     *
393
     * @return string service id
394
     */
395
    private function registerAutoDiscoverableClient(ContainerBuilder $container, $name, $factory, $profiling)
396
    {
397
        $serviceId = 'httplug.auto_discovery.'.$name;
398
399
        $pluginClientOptions = [];
400
401
        if ($profiling) {
402
            // Tell the plugin journal what plugins we used
403
            $container
404
                ->getDefinition('httplug.collector.plugin_journal')
405
                ->addMethodCall('setPlugins', [$name, ['httplug.plugin.stopwatch']])
406
            ;
407
408
            $debugPluginServiceId = $this->registerDebugPlugin($container, $serviceId);
409
410
            $pluginClientOptions['debug_plugins'] = [new Reference($debugPluginServiceId)];
411
        }
412
413
        $container
414
            ->register($serviceId, DummyClient::class)
415
            ->setFactory([PluginClientFactory::class, 'createPluginClient'])
416
            ->setArguments([[new Reference('httplug.plugin.stopwatch')], $factory, [], $pluginClientOptions])
417
        ;
418
419
        return $serviceId;
420
    }
421
422
    /**
423
     * Create a new plugin service for this client.
424
     *
425
     * @param ContainerBuilder $container
426
     * @param string           $serviceId
427
     *
428
     * @return string
429
     */
430
    private function registerDebugPlugin(ContainerBuilder $container, $serviceId)
431
    {
432
        $serviceIdDebugPlugin = $serviceId.'.debug_plugin';
433
434
        $container
435
            ->register($serviceIdDebugPlugin, DebugPlugin::class)
436
            ->addArgument(new Reference('httplug.collector.debug_collector'))
437
            ->addArgument(substr($serviceId, strrpos($serviceId, '.') + 1))
438
            ->setPublic(false)
439
        ;
440
441
        return $serviceIdDebugPlugin;
442
    }
443
444
    /**
445
     * {@inheritdoc}
446
     */
447
    public function getConfiguration(array $config, ContainerBuilder $container)
448
    {
449
        return new Configuration($container->getParameter('kernel.debug'));
450
    }
451
}
452