Completed
Pull Request — master (#112)
by David
14:40 queued 04:39
created

HttplugExtension::configureClient()   C

Complexity

Conditions 9
Paths 48

Size

Total Lines 82
Code Lines 46

Duplication

Lines 20
Ratio 24.39 %

Code Coverage

Tests 0
CRAP Score 90

Importance

Changes 7
Bugs 1 Features 4
Metric Value
c 7
b 1
f 4
dl 20
loc 82
ccs 0
cts 0
cp 0
rs 5.5259
cc 9
eloc 46
nc 48
nop 4
crap 90

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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