Completed
Pull Request — master (#112)
by David
11:35 queued 06:00
created

Configuration::configureClientPlugins()   B

Complexity

Conditions 6
Paths 1

Size

Total Lines 70
Code Lines 52

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 54
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 70
ccs 54
cts 54
cp 1
rs 8.5454
cc 6
eloc 52
nc 1
nop 1
crap 6

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 Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
6
use Symfony\Component\Config\Definition\Builder\NodeBuilder;
7
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
8
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
9
use Symfony\Component\Config\Definition\ConfigurationInterface;
10
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
11
12
/**
13
 * This class contains the configuration information for the bundle.
14
 *
15
 * This information is solely responsible for how the different configuration
16
 * sections are normalized, and merged.
17
 *
18
 * @author David Buchmann <[email protected]>
19
 * @author Tobias Nyholm <[email protected]>
20
 */
21
class Configuration implements ConfigurationInterface
22
{
23
    /**
24
     * Whether to use the debug mode.
25
     *
26
     * @see https://github.com/doctrine/DoctrineBundle/blob/v1.5.2/DependencyInjection/Configuration.php#L31-L41
27
     *
28
     * @var bool
29
     */
30
    private $debug;
31
32
    /**
33
     * @param bool $debug
34
     */
35 16
    public function __construct($debug)
36
    {
37 16
        $this->debug = (bool) $debug;
38 16
    }
39
40
    /**
41
     * {@inheritdoc}
42
     */
43 16
    public function getConfigTreeBuilder()
44
    {
45 16
        $treeBuilder = new TreeBuilder();
46 16
        $rootNode = $treeBuilder->root('httplug');
47
48 16
        $this->configureClients($rootNode);
49 16
        $this->configureSharedPlugins($rootNode);
50
51
        $rootNode
52 16
            ->validate()
53
                ->ifTrue(function ($v) {
54 13
                    return !empty($v['classes']['client'])
55 13
                        || !empty($v['classes']['message_factory'])
56 10
                        || !empty($v['classes']['uri_factory'])
57 13
                        || !empty($v['classes']['stream_factory']);
58 16
                })
59
                ->then(function ($v) {
60 3
                    foreach ($v['classes'] as $key => $class) {
61 3
                        if (null !== $class && !class_exists($class)) {
62 1
                            throw new InvalidConfigurationException(sprintf(
63 1
                                'Class %s specified for httplug.classes.%s does not exist.',
64 1
                                $class,
65
                                $key
66 1
                            ));
67
                        }
68 2
                    }
69
70 2
                    return $v;
71 16
                })
72 16
            ->end()
73 16
            ->beforeNormalization()
74
                ->ifTrue(function ($v) {
75 16
                    return is_array($v) && array_key_exists('toolbar', $v) && is_array($v['toolbar']);
76 16
                })
77
                ->then(function ($v) {
78 4
                    if (array_key_exists('profiling', $v)) {
79 1
                        throw new InvalidConfigurationException('Can\'t configure both "toolbar" and "profiling" section. The "toolbar" config is deprecated as of version 1.3.0, please only use "profiling".');
80
                    }
81
82 3
                    @trigger_error('"httplug.toolbar" config is deprecated since version 1.3 and will be removed in 2.0. Use "httplug.profiling" instead.', E_USER_DEPRECATED);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
83
84 3
                    if (array_key_exists('enabled', $v['toolbar']) && 'auto' === $v['toolbar']['enabled']) {
85 1
                        @trigger_error('"auto" value in "httplug.toolbar" config is deprecated since version 1.3 and will be removed in 2.0. Use a boolean value instead.', E_USER_DEPRECATED);
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
86 1
                        $v['toolbar']['enabled'] = $this->debug;
87 1
                    }
88
89 3
                    $v['profiling'] = $v['toolbar'];
90
91 3
                    unset($v['toolbar']);
92
93 3
                    return $v;
94 16
                })
95 16
            ->end()
96 16
            ->fixXmlConfig('client')
97 16
            ->children()
98 16
                ->arrayNode('main_alias')
99 16
                    ->addDefaultsIfNotSet()
100 16
                    ->info('Configure which service the main alias point to.')
101 16
                    ->children()
102 16
                        ->scalarNode('client')->defaultValue('httplug.client.default')->end()
103 16
                        ->scalarNode('message_factory')->defaultValue('httplug.message_factory.default')->end()
104 16
                        ->scalarNode('uri_factory')->defaultValue('httplug.uri_factory.default')->end()
105 16
                        ->scalarNode('stream_factory')->defaultValue('httplug.stream_factory.default')->end()
106 16
                    ->end()
107 16
                ->end()
108 16
                ->arrayNode('classes')
109 16
                    ->addDefaultsIfNotSet()
110 16
                    ->info('Overwrite a service class instead of using the discovery mechanism.')
111 16
                    ->children()
112 16
                        ->scalarNode('client')->defaultNull()->end()
113 16
                        ->scalarNode('message_factory')->defaultNull()->end()
114 16
                        ->scalarNode('uri_factory')->defaultNull()->end()
115 16
                        ->scalarNode('stream_factory')->defaultNull()->end()
116 16
                    ->end()
117 16
                ->end()
118 16
                ->arrayNode('profiling')
119 16
                    ->addDefaultsIfNotSet()
120 16
                    ->treatFalseLike(['enabled' => false])
121 16
                    ->treatTrueLike(['enabled' => true])
122 16
                    ->treatNullLike(['enabled' => $this->debug])
123 16
                    ->info('Extend the debug profiler with information about requests.')
124 16
                    ->children()
125 16
                        ->booleanNode('enabled')
126 16
                            ->info('Turn the toolbar on or off. Defaults to kernel debug mode.')
127 16
                            ->defaultValue($this->debug)
128 16
                        ->end()
129 16
                        ->scalarNode('formatter')->defaultNull()->end()
130 16
                        ->integerNode('captured_body_length')
131 16
                            ->defaultValue(0)
132 16
                            ->info('Limit long HTTP message bodies to x characters. If set to 0 we do not read the message body. Only available with the default formatter (FullHttpMessageFormatter).')
133 16
                        ->end()
134 16
                    ->end()
135 16
                ->end()
136 16
                ->arrayNode('discovery')
137 16
                    ->addDefaultsIfNotSet()
138 16
                    ->info('Control what clients should be found by the discovery.')
139 16
                    ->children()
140 16
                        ->scalarNode('client')
141 16
                            ->defaultValue('auto')
142 16
                            ->info('Set to "auto" to see auto discovered client in the web profiler. If provided a service id for a client then this client will be found by auto discovery.')
143 16
                        ->end()
144 16
                        ->scalarNode('async_client')
145 16
                            ->defaultNull()
146 16
                            ->info('Set to "auto" to see auto discovered client in the web profiler. If provided a service id for a client then this client will be found by auto discovery.')
147 16
                        ->end()
148 16
                    ->end()
149 16
                ->end()
150 16
            ->end();
151
152 16
        return $treeBuilder;
153
    }
154
155 16
    private function configureClients(ArrayNodeDefinition $root)
156
    {
157 16
        $pluginNode = $root->children()
158 16
            ->arrayNode('clients')
159 16
                ->fixXmlConfig('plugin')
160 16
                ->validate()
161
                    ->ifTrue(function ($clients) {
162 7
                        foreach ($clients as $name => $config) {
163
                            // Make sure we only allow one of these to be true
164 7
                            return (bool) $config['flexible_client'] + (bool) $config['http_methods_client'] + (bool) $config['batch_client'] >= 2;
165
                        }
166
167
                        return false;
168 16
                    })
169 16
                    ->thenInvalid('A http client can\'t be decorated with both FlexibleHttpClient and HttpMethodsClient. Only one of the following options can be true. ("flexible_client", "http_methods_client")')->end()
170 16
                ->useAttributeAsKey('name')
171 16
                ->prototype('array')
172 16
                ->children()
173 16
                    ->scalarNode('factory')
174 16
                        ->isRequired()
175 16
                        ->cannotBeEmpty()
176 16
                        ->info('The service id of a factory to use when creating the adapter.')
177 16
                    ->end()
178 16
                    ->booleanNode('flexible_client')
179 16
                        ->defaultFalse()
180 16
                        ->info('Set to true to get the client wrapped in a FlexibleHttpClient which emulates async or sync behavior.')
181 16
                    ->end()
182 16
                    ->booleanNode('http_methods_client')
183 16
                        ->defaultFalse()
184 16
                        ->info('Set to true to get the client wrapped in a HttpMethodsClient which emulates provides functions for HTTP verbs.')
185 16
                    ->end()
186 16
                    ->booleanNode('batch_client')
187 16
                        ->defaultFalse()
188 16
                        ->info('Set to true to get the client wrapped in a BatchClient which allows you to send multiple request at the same time.')
189 16
                    ->end()
190 16
                    ->variableNode('config')->defaultValue([])->end()
191 16
                    ->arrayNode('plugins')
192 16
                        ->info('A list of plugin service ids and client specific plugin definitions. The order is important.')
193 16
                        ->prototype('array')
194 16
        ;
195
196 16
        $this->configureClientPlugins($pluginNode);
197 16
    }
198
199
    /**
200
     * @param ArrayNodeDefinition $root
201
     */
202 16
    private function configureSharedPlugins(ArrayNodeDefinition $root)
203
    {
204
        $pluginsNode = $root
205 16
            ->children()
206 16
                ->arrayNode('plugins')
207 16
                ->addDefaultsIfNotSet()
208 16
        ;
209 16
        $this->addSharedPluginNodes($pluginsNode);
210 16
    }
211
212
    /**
213
     * Configure plugins node of a client.
214
     *
215
     * @param ArrayNodeDefinition $pluginNode The node to add plugin definitions to.
216
     */
217 16
    private function configureClientPlugins(ArrayNodeDefinition $pluginNode)
218
    {
219
        $pluginNode
220
            // support having just a service id in the list
221 16
            ->beforeNormalization()
222
                ->always(function ($plugin) {
223 8
                    if (is_string($plugin)) {
224
                        return [
225
                            'reference' => [
226 6
                                'enabled' => true,
227 6
                                'id' => $plugin,
228 6
                            ],
229 6
                        ];
230
                    }
231
232 5
                    return $plugin;
233 16
                })
234 16
            ->end()
235
236 16
            ->validate()
237
                ->always(function ($plugins) {
238 7
                    if (isset($plugins['authentication']) && !count($plugins['authentication'])) {
239 7
                        unset($plugins['authentication']);
240 7
                    }
241 7
                    foreach ($plugins as $name => $definition) {
242 7
                        if (!$definition['enabled']) {
243 7
                            unset($plugins[$name]);
244 7
                        }
245 7
                    }
246
247 7
                    return $plugins;
248 16
                })
249 16
            ->end()
250
        ;
251 16
        $this->addSharedPluginNodes($pluginNode, true);
252
253
        $pluginNode
254 16
            ->children()
255 16
                ->arrayNode('reference')
256 16
                    ->canBeEnabled()
257 16
                    ->info('Reference to a plugin service')
258 16
                    ->children()
259 16
                        ->scalarNode('id')
260 16
                            ->info('Service id of a plugin')
261 16
                            ->isRequired()
262 16
                            ->cannotBeEmpty()
263 16
                        ->end()
264 16
                    ->end()
265 16
                ->end()
266 16
                ->arrayNode('add_host')
267 16
                    ->canBeEnabled()
268 16
                    ->addDefaultsIfNotSet()
269 16
                    ->info('Configure the AddHostPlugin for this client.')
270 16
                    ->children()
271 16
                        ->scalarNode('host')
272 16
                            ->info('Host name including protocol and optionally the port number, e.g. https://api.local:8000')
273 16
                            ->isRequired()
274 16
                            ->cannotBeEmpty()
275 16
                        ->end()
276 16
                        ->scalarNode('replace')
277 16
                            ->info('Whether to replace the host if request already specifies it')
278 16
                            ->defaultValue(false)
279 16
                        ->end()
280 16
                    ->end()
281 16
                ->end()
282
283
                // TODO add aditional plugins that are only usable on a specific client
284 16
            ->end()
285 16
        ->end();
286 16
    }
287
288
    /**
289
     * Add the definitions for shared plugin configurations.
290
     *
291
     * @param ArrayNodeDefinition $pluginNode The node to add to.
292
     * @param bool                $disableAll Some shared plugins are enabled by default. On the client, all are disabled by default.
293
     */
294 16
    private function addSharedPluginNodes(ArrayNodeDefinition $pluginNode, $disableAll = false)
295
    {
296 16
        $children = $pluginNode->children();
297
298 16
        $children->append($this->createAuthenticationPluginNode());
299
300 16
        $children->arrayNode('cache')
301 16
            ->canBeEnabled()
302 16
            ->addDefaultsIfNotSet()
303 16
                ->children()
304 16
                    ->scalarNode('cache_pool')
305 16
                        ->info('This must be a service id to a service implementing Psr\Cache\CacheItemPoolInterface')
306 16
                        ->isRequired()
307 16
                        ->cannotBeEmpty()
308 16
                    ->end()
309 16
                    ->scalarNode('stream_factory')
310 16
                        ->info('This must be a service id to a service implementing Http\Message\StreamFactory')
311 16
                        ->defaultValue('httplug.stream_factory')
312 16
                        ->cannotBeEmpty()
313 16
                    ->end()
314 16
                    ->arrayNode('config')
315 16
                        ->addDefaultsIfNotSet()
316 16
                        ->children()
317 16
                            ->scalarNode('default_ttl')->defaultNull()->end()
318 16
                            ->scalarNode('respect_cache_headers')->defaultTrue()->end()
319 16
                        ->end()
320 16
                    ->end()
321 16
                ->end()
322 16
            ->end();
323
        // End cache plugin
324
325 16
        $children->arrayNode('cookie')
326 16
            ->canBeEnabled()
327 16
                ->children()
328 16
                    ->scalarNode('cookie_jar')
329 16
                        ->info('This must be a service id to a service implementing Http\Message\CookieJar')
330 16
                        ->isRequired()
331 16
                        ->cannotBeEmpty()
332 16
                    ->end()
333 16
                ->end()
334 16
            ->end();
335
        // End cookie plugin
336
337 16
        $decoder = $children->arrayNode('decoder');
338 16
        $disableAll ? $decoder->canBeEnabled() : $decoder->canBeDisabled();
339 16
        $decoder->addDefaultsIfNotSet()
340 16
            ->children()
341 16
                ->scalarNode('use_content_encoding')->defaultTrue()->end()
342 16
            ->end()
343 16
        ->end();
344
        // End decoder plugin
345
346 16
        $children->arrayNode('history')
347 16
            ->canBeEnabled()
348 16
                ->children()
349 16
                    ->scalarNode('journal')
350 16
                        ->info('This must be a service id to a service implementing Http\Client\Plugin\Journal')
351 16
                        ->isRequired()
352 16
                        ->cannotBeEmpty()
353 16
                    ->end()
354 16
                ->end()
355 16
            ->end();
356
        // End history plugin
357
358 16
        $logger = $children->arrayNode('logger');
359 16
        $disableAll ?  $logger->canBeEnabled() : $logger->canBeDisabled();
360 16
        $logger->addDefaultsIfNotSet()
361 16
            ->children()
362 16
                ->scalarNode('logger')
363 16
                    ->info('This must be a service id to a service implementing Psr\Log\LoggerInterface')
364 16
                    ->defaultValue('logger')
365 16
                    ->cannotBeEmpty()
366 16
                ->end()
367 16
                ->scalarNode('formatter')
368 16
                    ->info('This must be a service id to a service implementing Http\Message\Formatter')
369 16
                    ->defaultNull()
370 16
                ->end()
371 16
            ->end()
372 16
        ->end();
373
        // End logger plugin
374
375 16
        $redirect = $children->arrayNode('redirect');
376 16
        $disableAll ? $redirect->canBeEnabled() : $redirect->canBeDisabled();
377 16
        $redirect->addDefaultsIfNotSet()
378 16
            ->children()
379 16
                ->scalarNode('preserve_header')->defaultTrue()->end()
380 16
                ->scalarNode('use_default_for_multiple')->defaultTrue()->end()
381 16
            ->end()
382 16
        ->end();
383
        // End redirect plugin
384
385 16
        $retry = $children->arrayNode('retry');
386 16
        $disableAll ? $retry->canBeEnabled() : $retry->canBeDisabled();
387 16
        $retry->addDefaultsIfNotSet()
388 16
            ->children()
389 16
                ->scalarNode('retry')->defaultValue(1)->end() // TODO: should be called retries for consistency with the class
390 16
            ->end()
391 16
        ->end();
392
        // End retry plugin
393
394 16
        $stopwatch = $children->arrayNode('stopwatch');
395 16
        $disableAll ? $stopwatch->canBeEnabled() : $stopwatch->canBeDisabled();
396 16
        $stopwatch->addDefaultsIfNotSet()
397 16
            ->children()
398 16
                ->scalarNode('stopwatch')
399 16
                    ->info('This must be a service id to a service extending Symfony\Component\Stopwatch\Stopwatch')
400 16
                    ->defaultValue('debug.stopwatch')
401 16
                    ->cannotBeEmpty()
402 16
                ->end()
403 16
            ->end()
404 16
        ->end();
405
        // End stopwatch plugin
406 16
    }
407
408
    /**
409
     * Create configuration for authentication plugin.
410
     *
411
     * @return NodeDefinition Definition for the authentication node in the plugins list.
412
     */
413 16
    private function createAuthenticationPluginNode()
414
    {
415 16
        $builder = new TreeBuilder();
416 16
        $node = $builder->root('authentication');
417
        $node
418 16
            ->useAttributeAsKey('name')
419 16
            ->prototype('array')
420 16
                ->validate()
421 16
                    ->always()
422 16
                    ->then(function ($config) {
423 2
                        switch ($config['type']) {
424 2
                            case 'basic':
425 1
                                $this->validateAuthenticationType(['username', 'password'], $config, 'basic');
426 1
                                break;
427 2
                            case 'bearer':
428 1
                                $this->validateAuthenticationType(['token'], $config, 'bearer');
429 1
                                break;
430 2
                            case 'service':
431 2
                                $this->validateAuthenticationType(['service'], $config, 'service');
432 1
                                break;
433 1
                            case 'wsse':
434 1
                                $this->validateAuthenticationType(['username', 'password'], $config, 'wsse');
435 1
                                break;
436 1
                        }
437
438 1
                        return $config;
439 16
                    })
440 16
                ->end()
441 16
                ->children()
442 16
                    ->enumNode('type')
443 16
                        ->values(['basic', 'bearer', 'wsse', 'service'])
444 16
                        ->isRequired()
445 16
                        ->cannotBeEmpty()
446 16
                    ->end()
447 16
                    ->scalarNode('username')->end()
448 16
                    ->scalarNode('password')->end()
449 16
                    ->scalarNode('token')->end()
450 16
                    ->scalarNode('service')->end()
451 16
                    ->end()
452 16
                ->end()
453 16
            ->end(); // End authentication plugin
454
455 16
        return $node;
456
    }
457
458
    /**
459
     * Validate that the configuration fragment has the specified keys and none other.
460
     *
461
     * @param array  $expected Fields that must exist
462
     * @param array  $actual   Actual configuration hashmap
463
     * @param string $authName Name of authentication method for error messages
464
     *
465
     * @throws InvalidConfigurationException If $actual does not have exactly the keys specified in $expected (plus 'type')
466
     */
467 2
    private function validateAuthenticationType(array $expected, array $actual, $authName)
468
    {
469 2
        unset($actual['type']);
470 2
        $actual = array_keys($actual);
471 2
        sort($actual);
472 2
        sort($expected);
473
474 2
        if ($expected === $actual) {
475 1
            return;
476
        }
477
478 1
        throw new InvalidConfigurationException(sprintf(
479 1
            'Authentication "%s" requires %s but got %s',
480 1
            $authName,
481 1
            implode(', ', $expected),
482 1
            implode(', ', $actual)
483 1
        ));
484
    }
485
}
486