Completed
Pull Request — master (#112)
by David
09:50
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 0
Metric Value
c 1
b 0
f 0
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\AuthenticationPlugin;
9
use Http\Discovery\HttpAsyncClientDiscovery;
10
use Http\Discovery\HttpClientDiscovery;
11
use Http\HttplugBundle\ClientFactory\DummyClient;
12
use Http\HttplugBundle\ClientFactory\PluginClientFactory;
13
use Http\HttplugBundle\Collector\DebugPlugin;
14
use Http\Message\Authentication\BasicAuth;
15
use Http\Message\Authentication\Bearer;
16
use Http\Message\Authentication\Wsse;
17
use Psr\Http\Message\UriInterface;
18
use Symfony\Component\Config\FileLocator;
19
use Symfony\Component\DependencyInjection\ContainerBuilder;
20
use Symfony\Component\DependencyInjection\ContainerInterface;
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 8
     * {@inheritdoc}
34
     */
35 8
    public function load(array $configs, ContainerBuilder $container)
36 8
    {
37
        $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 8
42
        $loader->load('services.xml');
43
        $loader->load('plugins.xml');
44 8
45 8
        // Register default services
46 1
        foreach ($config['classes'] as $service => $class) {
47 1
            if (!empty($class)) {
48 8
                $container->register(sprintf('httplug.%s.default', $service), $class);
49
            }
50
        }
51 8
52 8
        // Set main aliases
53 8
        foreach ($config['main_alias'] as $type => $id) {
54
            $container->setAlias(sprintf('httplug.%s', $type), $id);
55
        }
56 8
57 5
        // Configure toolbar
58
        if ($config['profiling']['enabled']) {
59 5
            $loader->load('data-collector.xml');
60
61
            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 5
69 5
            $container
70
                ->getDefinition('httplug.formatter.full_http_message')
71 5
                ->addArgument($config['profiling']['captured_body_length'])
72
            ;
73 8
        }
74 8
75 8
        $this->configureClients($container, $config);
76 8
        $this->configurePlugins($container, $config['plugins']); // must be after clients, as the extra_plugins in clients might use plugins as template that will be removed
77
        $this->configureAutoDiscoveryClients($container, $config);
78
    }
79
80
    /**
81
     * Configure client services.
82
     *
83
     * @param ContainerBuilder $container
84 8
     * @param array            $config
85
     */
86 8
    private function configureClients(ContainerBuilder $container, array $config)
87
    {
88 8
        $first = null;
89 3
90
        foreach ($config['clients'] as $name => $arguments) {
91 3
            if ($first === null) {
92 3
                // Save the name of the first configurated client.
93
                $first = $name;
94 3
            }
95 8
96
            $this->configureClient($container, $name, $arguments, $config['profiling']['enabled']);
97
        }
98 8
99
        // If we have clients configured
100 3
        if ($first !== null) {
101
            // If we do not have a client named 'default'
102 3
            if (!isset($config['clients']['default'])) {
103 3
                // Alias the first client to httplug.client.default
104 3
                $container->setAlias('httplug.client.default', 'httplug.client.'.$first);
105 8
            }
106
        }
107
    }
108
109
    /**
110
     * @param ContainerBuilder $container
111 8
     * @param array            $config
112
     * @param string           $idPrefix  Start of service id for these plugins.
113 8
     */
114
    private function configurePlugins(ContainerBuilder $container, array $config, $idPrefix = 'httplug.plugin')
115
    {
116 8
        $sharedPluginPrefix = 'httplug.plugin';
117
        $shared = $sharedPluginPrefix === $idPrefix;
118 8
        if (!empty($config['authentication'])) {
119 8
            // TODO: handle extra auth plugin on client
120
            $this->configureAuthentication($container, $config['authentication']);
121 8
        }
122 8
        unset($config['authentication']);
123 8
124 8
        foreach ($config as $name => $pluginConfig) {
125 8
            $pluginId = $idPrefix.'.'.$name;
126
127 8
            if ($pluginConfig['enabled']) {
128 8
                $def = $container->getDefinition($sharedPluginPrefix.'.'.$name);
129
                if (!$shared) {
130
                    $def = clone $def;
131
                    $def->setAbstract(false);
132
                    $container->setDefinition($pluginId, $def);
133
                }
134
                $this->configurePluginByName($name, $def, $pluginConfig, $container, $pluginId);
135 8
            } elseif ($shared) {
136
                $container->removeDefinition($pluginId);
137
            }
138 8
        }
139
    }
140
141
    /**
142
     * @param string           $name
143
     * @param Definition       $definition
144 8
     * @param array            $config
145
     * @param ContainerBuilder $container  In case we need to add additional services for this plugin
146
     * @param string           $serviceId  Service id of the plugin, in case we need to add additional services for this plugin.
147 8
     */
148 8
    private function configurePluginByName($name, Definition $definition, array $config, ContainerInterface $container, $serviceId)
149 8
    {
150 8
        switch ($name) {
151
            case 'cache':
152
                $definition
153 8
                    ->replaceArgument(0, new Reference($config['cache_pool']))
154 8
                    ->replaceArgument(1, new Reference($config['stream_factory']))
155 8
                    ->replaceArgument(2, $config['config']);
156
                break;
157
            case 'cookie':
158 8
                $definition->replaceArgument(0, new Reference($config['cookie_jar']));
159 8
                break;
160
            case 'decoder':
161 8
                $definition->addArgument([
162 8
                    'use_content_encoding' => $config['use_content_encoding'],
163 8
                ]);
164 8
                break;
165 8
            case 'history':
166 8
                $definition->replaceArgument(0, new Reference($config['journal']));
167 8
                break;
168 8
            case 'logger':
169 8
                $definition->replaceArgument(0, new Reference($config['logger']));
170
                if (!empty($config['formatter'])) {
171 8
                    $definition->replaceArgument(1, new Reference($config['formatter']));
172
                }
173
                break;
174
            case 'redirect':
175
                $definition->addArgument([
176
                    'preserve_header' => $config['preserve_header'],
177
                    'use_default_for_multiple' => $config['use_default_for_multiple'],
178
                ]);
179
                break;
180
            case 'retry':
181
                $definition->addArgument([
182
                    'retries' => $config['retry'],
183
                ]);
184
                break;
185
            case 'stopwatch':
186
                $definition->replaceArgument(0, new Reference($config['stopwatch']));
187
                break;
188
189
            // client specific plugins
190
            case 'add_host':
191
                $uriService = $serviceId.'.host_uri';
192
                $this->createUri($container, $uriService, $config['host']);
0 ignored issues
show
Compatibility introduced by
$container of type object<Symfony\Component...ion\ContainerInterface> is not a sub-type of object<Symfony\Component...ction\ContainerBuilder>. It seems like you assume a concrete implementation of the interface Symfony\Component\Depend...tion\ContainerInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
193
                $definition->replaceArgument(0, new Reference($uriService));
194
                $definition->replaceArgument(1, [
195
                    'replace' => $config['replace'],
196
                ]);
197
                break;
198
199
            default:
200
                throw new \InvalidArgumentException(sprintf('Internal exception: Plugin %s is not handled', $name));
201
        }
202
    }
203
204
    /**
205
     * @param ContainerBuilder $container
206
     * @param array            $config
207
     */
208
    private function configureAuthentication(ContainerBuilder $container, array $config)
209
    {
210
        foreach ($config as $name => $values) {
211
            $authServiceKey = sprintf('httplug.plugin.authentication.%s.auth', $name);
212
            switch ($values['type']) {
213
                case 'bearer':
214
                    $container->register($authServiceKey, Bearer::class)
215
                        ->addArgument($values['token']);
216
                    break;
217
                case 'basic':
218
                    $container->register($authServiceKey, BasicAuth::class)
219
                        ->addArgument($values['username'])
220
                        ->addArgument($values['password']);
221
                    break;
222
                case 'wsse':
223
                    $container->register($authServiceKey, Wsse::class)
224
                        ->addArgument($values['username'])
225
                        ->addArgument($values['password']);
226
                    break;
227
                case 'service':
228
                    $authServiceKey = $values['service'];
229
                    break;
230
                default:
231
                    throw new \LogicException(sprintf('Unknown authentication type: "%s"', $values['type']));
232
            }
233
234
            $container->register('httplug.plugin.authentication.'.$name, AuthenticationPlugin::class)
235
                ->addArgument(new Reference($authServiceKey));
236
        }
237
    }
238
239
    /**
240
     * @param ContainerBuilder $container
241
     * @param string           $name
242
     * @param array            $arguments
243
     * @param bool             $profiling
244
     */
245
    private function configureClient(ContainerBuilder $container, $name, array $arguments, $profiling)
246
    {
247
        $serviceId = 'httplug.client.'.$name;
248
249
        $plugins = $arguments['plugins'];
250
        $pluginClientOptions = [];
251
252
        if ($profiling) {
253
            if (!in_array('httplug.plugin.stopwatch', $arguments['plugins'])) {
254
                // Add the stopwatch plugin
255
                array_unshift($arguments['plugins'], 'httplug.plugin.stopwatch');
256
            }
257
258
            // Tell the plugin journal what plugins we used
259
            $container
260
                ->getDefinition('httplug.collector.plugin_journal')
261
                ->addMethodCall('setPlugins', [$name, $arguments['plugins']])
262
            ;
263
264
            $debugPluginServiceId = $this->registerDebugPlugin($container, $serviceId);
265
266
            $pluginClientOptions['debug_plugins'] = [new Reference($debugPluginServiceId)];
267
        }
268
269
        if (array_key_exists('extra_plugins', $arguments)) {
270
            $this->configurePlugins($container, $arguments['extra_plugins'], $serviceId.'.plugin');
271
272
            // add to end of plugins list unless explicitly configured
273
            foreach ($arguments['extra_plugins'] as $name => $config) {
274
                if (!in_array($serviceId.'.plugin.'.$name, $plugins)) {
275
                    $plugins[] = $serviceId.'.plugin.'.$name;
276
                }
277
            }
278
        }
279
280
        $container
281
            ->register($serviceId, DummyClient::class)
282
            ->setFactory([PluginClientFactory::class, 'createPluginClient'])
283
            ->addArgument(
284
                array_map(
285
                    function ($id) {
286
                        return new Reference($id);
287
                    },
288
                    $plugins
289
                )
290
            )
291
            ->addArgument(new Reference($arguments['factory']))
292
            ->addArgument($arguments['config'])
293
            ->addArgument($pluginClientOptions)
294
        ;
295
296
297
        /*
298
         * Decorate the client with clients from client-common
299
         */
300 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...
301
            $container
302
                ->register($serviceId.'.flexible', FlexibleHttpClient::class)
303
                ->addArgument(new Reference($serviceId.'.flexible.inner'))
304
                ->setPublic(false)
305
                ->setDecoratedService($serviceId)
306
            ;
307
        }
308
309 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...
310
            $container
311
                ->register($serviceId.'.http_methods', HttpMethodsClient::class)
312
                ->setArguments([new Reference($serviceId.'.http_methods.inner'), new Reference('httplug.message_factory')])
313
                ->setPublic(false)
314
                ->setDecoratedService($serviceId)
315
            ;
316
        }
317
318 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...
319
            $container
320
                ->register($serviceId.'.batch_client', BatchClient::class)
321
                ->setArguments([new Reference($serviceId.'.batch_client.inner')])
322
                ->setPublic(false)
323
                ->setDecoratedService($serviceId)
324
            ;
325
        }
326
    }
327
328
    /**
329
     * Create a URI object with the default URI factory.
330
     *
331
     * @param ContainerBuilder $container
332
     * @param string           $serviceId Name of the private service to create
333
     * @param string           $uri       String representation of the URI
334
     */
335
    private function createUri(ContainerBuilder $container, $serviceId, $uri)
336
    {
337
        $container
338
            ->register($serviceId, UriInterface::class)
339
            ->setPublic(false)
340
            ->setFactory([new Reference('httplug.uri_factory'), 'createUri'])
341
            ->addArgument($uri)
342
        ;
343
    }
344
345
    /**
346
     * Make the user can select what client is used for auto discovery. If none is provided, a service will be created
347
     * by finding a client using auto discovery.
348
     *
349
     * @param ContainerBuilder $container
350
     * @param array            $config
351
     */
352
    private function configureAutoDiscoveryClients(ContainerBuilder $container, array $config)
353
    {
354
        $httpClient = $config['discovery']['client'];
355
356 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...
357
            if ($httpClient === 'auto') {
358
                $httpClient = $this->registerAutoDiscoverableClient(
359
                    $container,
360
                    'auto_discovered_client',
361
                    [HttpClientDiscovery::class, 'find'],
362
                    $config['profiling']['enabled']
363
                );
364
            }
365
366
            $httpClient = new Reference($httpClient);
367
        }
368
369
        $asyncHttpClient = $config['discovery']['async_client'];
370
371 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...
372
            if ($asyncHttpClient === 'auto') {
373
                $asyncHttpClient = $this->registerAutoDiscoverableClient(
374
                    $container,
375
                    'auto_discovered_async',
376
                    [HttpAsyncClientDiscovery::class, 'find'],
377
                    $config['profiling']['enabled']
378
                );
379
            }
380
381
            $asyncHttpClient = new Reference($asyncHttpClient);
382
        }
383
384
        $container
385
            ->getDefinition('httplug.strategy')
386
            ->addArgument($httpClient)
387
            ->addArgument($asyncHttpClient)
388
        ;
389
    }
390
391
    /**
392
     * Find a client with auto discovery and return a service Reference to it.
393
     *
394
     * @param ContainerBuilder $container
395
     * @param string           $name
396
     * @param callable         $factory
397
     * @param bool             $profiling
398
     *
399
     * @return string service id
400
     */
401
    private function registerAutoDiscoverableClient(ContainerBuilder $container, $name, $factory, $profiling)
402
    {
403
        $serviceId = 'httplug.auto_discovery.'.$name;
404
405
        $pluginClientOptions = [];
406
407
        if ($profiling) {
408
            // Tell the plugin journal what plugins we used
409
            $container
410
                ->getDefinition('httplug.collector.plugin_journal')
411
                ->addMethodCall('setPlugins', [$name, ['httplug.plugin.stopwatch']])
412
            ;
413
414
            $debugPluginServiceId = $this->registerDebugPlugin($container, $serviceId);
415
416
            $pluginClientOptions['debug_plugins'] = [new Reference($debugPluginServiceId)];
417
        }
418
419
        $container
420
            ->register($serviceId, DummyClient::class)
421
            ->setFactory([PluginClientFactory::class, 'createPluginClient'])
422
            ->setArguments([[new Reference('httplug.plugin.stopwatch')], $factory, [], $pluginClientOptions])
423
        ;
424
425
        return $serviceId;
426
    }
427
428
    /**
429
     * Create a new plugin service for this client.
430
     *
431
     * @param ContainerBuilder $container
432
     * @param string           $serviceId
433
     *
434
     * @return string
435
     */
436
    private function registerDebugPlugin(ContainerBuilder $container, $serviceId)
437
    {
438
        $serviceIdDebugPlugin = $serviceId.'.debug_plugin';
439
440
        $container
441
            ->register($serviceIdDebugPlugin, DebugPlugin::class)
442
            ->addArgument(new Reference('httplug.collector.debug_collector'))
443
            ->addArgument(substr($serviceId, strrpos($serviceId, '.') + 1))
444
            ->setPublic(false)
445
        ;
446
447
        return $serviceIdDebugPlugin;
448
    }
449
450
    /**
451
     * {@inheritdoc}
452
     */
453
    public function getConfiguration(array $config, ContainerBuilder $container)
454
    {
455
        return new Configuration($container->getParameter('kernel.debug'));
456
    }
457
}
458