Completed
Push — master ( 3bd469...f9d5ad )
by Tobias
12:41
created

Configuration::createAuthenticationPluginNode()   B

Complexity

Conditions 7
Paths 2

Size

Total Lines 59

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 39
CRAP Score 7.0394

Importance

Changes 0
Metric Value
dl 0
loc 59
ccs 39
cts 43
cp 0.907
rs 7.9612
c 0
b 0
f 0
cc 7
nc 2
nop 0
crap 7.0394

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\Plugin\Cache\Generator\CacheKeyGenerator;
6
use Http\Client\Common\Plugin\CachePlugin;
7
use Http\Client\Common\Plugin\Journal;
8
use Http\Client\Plugin\Vcr\NamingStrategy\NamingStrategyInterface;
9
use Http\Client\Plugin\Vcr\Recorder\PlayerInterface;
10
use Http\Client\Plugin\Vcr\Recorder\RecorderInterface;
11
use Http\Message\CookieJar;
12
use Http\Message\Formatter;
13
use Http\Message\StreamFactory;
14
use Psr\Cache\CacheItemPoolInterface;
15
use Psr\Log\LoggerInterface;
16
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
17
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
18
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
19
use Symfony\Component\Config\Definition\ConfigurationInterface;
20
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
21
use Symfony\Component\Config\Definition\Exception\InvalidTypeException;
22
23
/**
24
 * This class contains the configuration information for the bundle.
25
 *
26
 * This information is solely responsible for how the different configuration
27
 * sections are normalized, and merged.
28
 *
29
 * @author David Buchmann <[email protected]>
30
 * @author Tobias Nyholm <[email protected]>
31
 */
32
class Configuration implements ConfigurationInterface
33
{
34
    /**
35
     * Whether to use the debug mode.
36
     *
37
     * @see https://github.com/doctrine/DoctrineBundle/blob/v1.5.2/DependencyInjection/Configuration.php#L31-L41
38
     *
39
     * @var bool
40
     */
41
    private $debug;
42
43
    /**
44
     * @param bool $debug
45
     */
46 50
    public function __construct($debug)
47
    {
48 50
        $this->debug = (bool) $debug;
49 50
    }
50
51
    /**
52
     * {@inheritdoc}
53
     */
54 50
    public function getConfigTreeBuilder()
55
    {
56 50
        $treeBuilder = new TreeBuilder('httplug');
57
        // Keep compatibility with symfony/config < 4.2
58 50
        if (!method_exists($treeBuilder, 'getRootNode')) {
59
            $rootNode = $treeBuilder->root('httplug');
0 ignored issues
show
Bug introduced by
The method root() does not seem to exist on object<Symfony\Component...on\Builder\TreeBuilder>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
60
        } else {
61 50
            $rootNode = $treeBuilder->getRootNode();
62
        }
63
64 50
        $this->configureClients($rootNode);
65 50
        $this->configureSharedPlugins($rootNode);
66
67
        $rootNode
68 50
            ->validate()
69
                ->ifTrue(function ($v) {
70 43
                    return !empty($v['classes']['client'])
71 40
                        || !empty($v['classes']['message_factory'])
72 40
                        || !empty($v['classes']['uri_factory'])
73 43
                        || !empty($v['classes']['stream_factory']);
74 50
                })
75
                ->then(function ($v) {
76 3
                    foreach ($v['classes'] as $key => $class) {
77 3
                        if (null !== $class && !class_exists($class)) {
78 1
                            throw new InvalidConfigurationException(sprintf(
79 1
                                'Class %s specified for httplug.classes.%s does not exist.',
80
                                $class,
81
                                $key
82
                            ));
83
                        }
84
                    }
85
86 2
                    return $v;
87 50
                })
88 50
            ->end()
89 50
            ->beforeNormalization()
90
                ->ifTrue(function ($v) {
91 50
                    return is_array($v) && array_key_exists('toolbar', $v) && is_array($v['toolbar']);
92 50
                })
93
                ->then(function ($v) {
94 4
                    if (array_key_exists('profiling', $v)) {
95 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".');
96
                    }
97
98 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);
99
100 3
                    if (array_key_exists('enabled', $v['toolbar']) && 'auto' === $v['toolbar']['enabled']) {
101 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);
102 1
                        $v['toolbar']['enabled'] = $this->debug;
103
                    }
104
105 3
                    $v['profiling'] = $v['toolbar'];
106
107 3
                    unset($v['toolbar']);
108
109 3
                    return $v;
110 50
                })
111 50
            ->end()
112 50
            ->fixXmlConfig('client')
113 50
            ->children()
114 50
                ->booleanNode('default_client_autowiring')
115 50
                    ->defaultTrue()
116 50
                    ->info('Set to false to not autowire HttpClient and HttpAsyncClient.')
117 50
                ->end()
118 50
                ->arrayNode('main_alias')
119 50
                    ->addDefaultsIfNotSet()
120 50
                    ->info('Configure which service the main alias point to.')
121 50
                    ->children()
122 50
                        ->scalarNode('client')->defaultValue('httplug.client.default')->end()
123 50
                        ->scalarNode('message_factory')->defaultValue('httplug.message_factory.default')->end()
124 50
                        ->scalarNode('uri_factory')->defaultValue('httplug.uri_factory.default')->end()
125 50
                        ->scalarNode('stream_factory')->defaultValue('httplug.stream_factory.default')->end()
126 50
                    ->end()
127 50
                ->end()
128 50
                ->arrayNode('classes')
129 50
                    ->addDefaultsIfNotSet()
130 50
                    ->info('Overwrite a service class instead of using the discovery mechanism.')
131 50
                    ->children()
132 50
                        ->scalarNode('client')->defaultNull()->end()
133 50
                        ->scalarNode('message_factory')->defaultNull()->end()
134 50
                        ->scalarNode('uri_factory')->defaultNull()->end()
135 50
                        ->scalarNode('stream_factory')->defaultNull()->end()
136 50
                    ->end()
137 50
                ->end()
138 50
                ->arrayNode('profiling')
139 50
                    ->addDefaultsIfNotSet()
140 50
                    ->treatFalseLike(['enabled' => false])
141 50
                    ->treatTrueLike(['enabled' => true])
142 50
                    ->treatNullLike(['enabled' => $this->debug])
143 50
                    ->info('Extend the debug profiler with information about requests.')
144 50
                    ->children()
145 50
                        ->booleanNode('enabled')
146 50
                            ->info('Turn the toolbar on or off. Defaults to kernel debug mode.')
147 50
                            ->defaultValue($this->debug)
148 50
                        ->end()
149 50
                        ->scalarNode('formatter')->defaultNull()->end()
150 50
                        ->scalarNode('captured_body_length')
151 50
                            ->validate()
152
                                ->ifTrue(function ($v) {
153 4
                                    return null !== $v && !is_int($v);
154 50
                                })
155 50
                                ->thenInvalid('The child node "captured_body_length" at path "httplug.profiling" must be an integer or null ("%s" given).')
156 50
                            ->end()
157 50
                            ->defaultValue(0)
158 50
                            ->info('Limit long HTTP message bodies to x characters. If set to 0 we do not read the message body. If null the body will not be truncated. Only available with the default formatter (FullHttpMessageFormatter).')
159 50
                        ->end()
160 50
                    ->end()
161 50
                ->end()
162 50
                ->arrayNode('discovery')
163 50
                    ->addDefaultsIfNotSet()
164 50
                    ->info('Control what clients should be found by the discovery.')
165 50
                    ->children()
166 50
                        ->scalarNode('client')
167 50
                            ->defaultValue('auto')
168 50
                            ->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.')
169 50
                        ->end()
170 50
                        ->scalarNode('async_client')
171 50
                            ->defaultNull()
172 50
                            ->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.')
173 50
                        ->end()
174 50
                    ->end()
175 50
                ->end()
176 50
            ->end();
177
178 50
        return $treeBuilder;
179
    }
180
181 50
    private function configureClients(ArrayNodeDefinition $root)
182
    {
183 50
        $root->children()
184 50
            ->arrayNode('clients')
185 50
                ->useAttributeAsKey('name')
186 50
                ->prototype('array')
187 50
                ->fixXmlConfig('plugin')
188 50
                ->validate()
189
                    ->ifTrue(function ($config) {
190
                        // Make sure we only allow one of these to be true
191 27
                        return (bool) $config['flexible_client'] + (bool) $config['http_methods_client'] + (bool) $config['batch_client'] >= 2;
192 50
                    })
193 50
                    ->thenInvalid('A http client can\'t be decorated with several of FlexibleHttpClient, HttpMethodsClient and BatchClient. Only one of the following options can be true. ("flexible_client", "http_methods_client", "batch_client")')
194 50
                ->end()
195 50
                ->validate()
196
                    ->ifTrue(function ($config) {
197 27
                        return 'httplug.factory.auto' === $config['factory'] && !empty($config['config']);
198 50
                    })
199 50
                    ->thenInvalid('If you want to use the "config" key you must also specify a valid "factory".')
200 50
                ->end()
201 50
                ->validate()
202
                    ->ifTrue(function ($config) {
203 27
                        return !empty($config['service']) && ('httplug.factory.auto' !== $config['factory'] || !empty($config['config']));
204 50
                    })
205 50
                    ->thenInvalid('If you want to use the "service" key you cannot specify "factory" or "config".')
206 50
                ->end()
207 50
                ->children()
208 50
                    ->scalarNode('factory')
209 50
                        ->defaultValue('httplug.factory.auto')
210 50
                        ->cannotBeEmpty()
211 50
                        ->info('The service id of a factory to use when creating the adapter.')
212 50
                    ->end()
213 50
                    ->scalarNode('service')
214 50
                        ->defaultNull()
215 50
                        ->info('The service id of the client to use.')
216 50
                    ->end()
217 50
                    ->booleanNode('public')
218 50
                        ->defaultNull()
219 50
                        ->info('Set to true if you really cannot use dependency injection and need to make the client service public.')
220 50
                    ->end()
221 50
                    ->booleanNode('flexible_client')
222 50
                        ->defaultFalse()
223 50
                        ->info('Set to true to get the client wrapped in a FlexibleHttpClient which emulates async or sync behavior.')
224 50
                    ->end()
225 50
                    ->booleanNode('http_methods_client')
226 50
                        ->defaultFalse()
227 50
                        ->info('Set to true to get the client wrapped in a HttpMethodsClient which emulates provides functions for HTTP verbs.')
228 50
                    ->end()
229 50
                    ->booleanNode('batch_client')
230 50
                        ->defaultFalse()
231 50
                        ->info('Set to true to get the client wrapped in a BatchClient which allows you to send multiple request at the same time.')
232 50
                    ->end()
233 50
                    ->variableNode('config')->defaultValue([])->end()
234 50
                    ->append($this->createClientPluginNode())
235 50
                ->end()
236 50
            ->end()
237 50
        ->end();
238 50
    }
239
240
    /**
241
     * @param ArrayNodeDefinition $root
242
     */
243 50
    private function configureSharedPlugins(ArrayNodeDefinition $root)
244
    {
245
        $pluginsNode = $root
246 50
            ->children()
247 50
                ->arrayNode('plugins')
248 50
                ->info('Global plugin configuration. Plugins need to be explicitly added to clients.')
249 50
                ->addDefaultsIfNotSet()
250
            // don't call end to get the plugins node
251
        ;
252 50
        $this->addSharedPluginNodes($pluginsNode);
253 50
    }
254
255
    /**
256
     * Createplugins node of a client.
257
     *
258
     * @return ArrayNodeDefinition The plugin node
259
     */
260 50
    private function createClientPluginNode()
261
    {
262 50
        $treeBuilder = new TreeBuilder('plugins');
263
        // Keep compatibility with symfony/config < 4.2
264 50
        if (!method_exists($treeBuilder, 'getRootNode')) {
265
            $node = $treeBuilder->root('plugins');
0 ignored issues
show
Bug introduced by
The method root() does not seem to exist on object<Symfony\Component...on\Builder\TreeBuilder>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
266
        } else {
267 50
            $node = $treeBuilder->getRootNode();
268
        }
269
270
        /** @var ArrayNodeDefinition $pluginList */
271
        $pluginList = $node
272 50
            ->info('A list of plugin service ids and client specific plugin definitions. The order is important.')
273 50
            ->prototype('array')
274
        ;
275
        $pluginList
276
            // support having just a service id in the list
277 50
            ->beforeNormalization()
278
                ->always(function ($plugin) {
279 18
                    if (is_string($plugin)) {
280
                        return [
281
                            'reference' => [
282 10
                                'enabled' => true,
283 10
                                'id' => $plugin,
284
                            ],
285
                        ];
286
                    }
287
288 15
                    return $plugin;
289 50
                })
290 50
            ->end()
291
292 50
            ->validate()
293
                ->always(function ($plugins) {
294 16
                    foreach ($plugins as $name => $definition) {
295 16
                        if ('authentication' === $name) {
296 16
                            if (!count($definition)) {
297 16
                                unset($plugins['authentication']);
298
                            }
299 16
                        } elseif (!$definition['enabled']) {
300 16
                            unset($plugins[$name]);
301
                        }
302
                    }
303
304 16
                    return $plugins;
305 50
                })
306 50
            ->end()
307
        ;
308 50
        $this->addSharedPluginNodes($pluginList, true);
309
310
        $pluginList
311 50
            ->children()
312 50
                ->arrayNode('reference')
313 50
                    ->canBeEnabled()
314 50
                    ->info('Reference to a plugin service')
315 50
                    ->children()
316 50
                        ->scalarNode('id')
317 50
                            ->info('Service id of a plugin')
318 50
                            ->isRequired()
319 50
                            ->cannotBeEmpty()
320 50
                        ->end()
321 50
                    ->end()
322 50
                ->end()
323 50
                ->arrayNode('add_host')
324 50
                    ->canBeEnabled()
325 50
                    ->addDefaultsIfNotSet()
326 50
                    ->info('Set scheme, host and port in the request URI.')
327 50
                    ->children()
328 50
                        ->scalarNode('host')
329 50
                            ->info('Host name including protocol and optionally the port number, e.g. https://api.local:8000')
330 50
                            ->isRequired()
331 50
                            ->cannotBeEmpty()
332 50
                        ->end()
333 50
                        ->scalarNode('replace')
334 50
                            ->info('Whether to replace the host if request already specifies one')
335 50
                            ->defaultValue(false)
336 50
                        ->end()
337 50
                    ->end()
338 50
                ->end()
339 50
                ->arrayNode('add_path')
340 50
                    ->canBeEnabled()
341 50
                    ->addDefaultsIfNotSet()
342 50
                    ->info('Add a base path to the request.')
343 50
                    ->children()
344 50
                        ->scalarNode('path')
345 50
                            ->info('Path to be added, e.g. /api/v1')
346 50
                            ->isRequired()
347 50
                            ->cannotBeEmpty()
348 50
                        ->end()
349 50
                    ->end()
350 50
                ->end()
351 50
                ->arrayNode('base_uri')
352 50
                    ->canBeEnabled()
353 50
                    ->addDefaultsIfNotSet()
354 50
                    ->info('Set a base URI to the request.')
355 50
                    ->children()
356 50
                        ->scalarNode('uri')
357 50
                            ->info('Base Uri including protocol, optionally the port number and prepend path, e.g. https://api.local:8000/api')
358 50
                            ->isRequired()
359 50
                            ->cannotBeEmpty()
360 50
                        ->end()
361 50
                        ->scalarNode('replace')
362 50
                            ->info('Whether to replace the host if request already specifies one')
363 50
                            ->defaultValue(false)
364 50
                        ->end()
365 50
                    ->end()
366 50
                ->end()
367 50
                ->arrayNode('content_type')
368 50
                    ->canBeEnabled()
369 50
                    ->info('Detect the content type of a request body and set the Content-Type header if it is not already set.')
370 50
                    ->children()
371 50
                        ->booleanNode('skip_detection')
372 50
                            ->info('Whether to skip detection when request body is larger than size_limit')
373 50
                            ->defaultFalse()
374 50
                        ->end()
375 50
                        ->scalarNode('size_limit')
376 50
                            ->info('Skip content type detection if request body is larger than size_limit bytes')
377 50
                        ->end()
378 50
                    ->end()
379 50
                ->end()
380 50
                ->arrayNode('header_append')
381 50
                    ->canBeEnabled()
382 50
                    ->info('Append headers to the request. If the header already exists the value will be appended to the current value.')
383 50
                    ->fixXmlConfig('header')
384 50
                    ->children()
385 50
                        ->arrayNode('headers')
386 50
                            ->info('Keys are the header names, values the header values')
387 50
                            ->normalizeKeys(false)
388 50
                            ->useAttributeAsKey('name')
389 50
                            ->prototype('scalar')->end()
390 50
                        ->end()
391 50
                    ->end()
392 50
                ->end()
393 50
                ->arrayNode('header_defaults')
394 50
                    ->canBeEnabled()
395 50
                    ->info('Set header to default value if it does not exist.')
396 50
                    ->fixXmlConfig('header')
397 50
                    ->children()
398 50
                        ->arrayNode('headers')
399 50
                            ->info('Keys are the header names, values the header values')
400 50
                            ->normalizeKeys(false)
401 50
                            ->useAttributeAsKey('name')
402 50
                            ->prototype('scalar')->end()
403 50
                        ->end()
404 50
                    ->end()
405 50
                ->end()
406 50
                ->arrayNode('header_set')
407 50
                    ->canBeEnabled()
408 50
                    ->info('Set headers to requests. If the header does not exist it wil be set, if the header already exists it will be replaced.')
409 50
                    ->fixXmlConfig('header')
410 50
                    ->children()
411 50
                        ->arrayNode('headers')
412 50
                            ->info('Keys are the header names, values the header values')
413 50
                            ->normalizeKeys(false)
414 50
                            ->useAttributeAsKey('name')
415 50
                            ->prototype('scalar')->end()
416 50
                        ->end()
417 50
                    ->end()
418 50
                ->end()
419 50
                ->arrayNode('header_remove')
420 50
                    ->canBeEnabled()
421 50
                    ->info('Remove headers from requests.')
422 50
                    ->fixXmlConfig('header')
423 50
                    ->children()
424 50
                        ->arrayNode('headers')
425 50
                            ->info('List of header names to remove')
426 50
                            ->prototype('scalar')->end()
427 50
                        ->end()
428 50
                    ->end()
429 50
                ->end()
430 50
                ->arrayNode('query_defaults')
431 50
                    ->canBeEnabled()
432 50
                    ->info('Sets query parameters to default value if they are not present in the request.')
433 50
                    ->fixXmlConfig('parameter')
434 50
                    ->children()
435 50
                        ->arrayNode('parameters')
436 50
                            ->info('List of query parameters. Names and values must not be url encoded as the plugin will encode them.')
437 50
                            ->normalizeKeys(false)
438 50
                            ->useAttributeAsKey('name')
439 50
                            ->prototype('scalar')->end()
440 50
                        ->end()
441 50
                    ->end()
442 50
                ->end()
443 50
                ->arrayNode('vcr')
444 50
                    ->canBeEnabled()
445 50
                    ->addDefaultsIfNotSet()
446 50
                    ->info('Record response to be replayed during tests or development cycle.')
447 50
                    ->validate()
448
                        ->ifTrue(function ($config) {
449 5
                            return 'filesystem' === $config['recorder'] && empty($config['fixtures_directory']);
450 50
                        })
451 50
                        ->thenInvalid('If you want to use the "filesystem" recorder you must also specify a "fixtures_directory".')
452 50
                    ->end()
453 50
                    ->children()
454 50
                        ->enumNode('mode')
455 50
                        ->info('What should be the behavior of the plugin?')
456 50
                        ->values(['record', 'replay', 'replay_or_record'])
457 50
                        ->isRequired()
458 50
                        ->cannotBeEmpty()
459 50
                    ->end()
460 50
                    ->scalarNode('recorder')
461 50
                        ->info(sprintf('Which recorder to use. Can be "in_memory", "filesystem" or the ID of your service implementing %s and %s. When using filesystem, specify "fixtures_directory" as well.', RecorderInterface::class, PlayerInterface::class))
462 50
                        ->defaultValue('filesystem')
463 50
                        ->cannotBeEmpty()
464 50
                    ->end()
465 50
                    ->scalarNode('naming_strategy')
466 50
                        ->info(sprintf('Which naming strategy to use. Add the ID of your service implementing %s to override the default one.', NamingStrategyInterface::class))
467 50
                        ->defaultValue('default')
468 50
                        ->cannotBeEmpty()
469 50
                    ->end()
470 50
                    ->arrayNode('naming_strategy_options')
471 50
                        ->info('See http://docs.php-http.org/en/latest/plugins/vcr.html#the-naming-strategy for more details')
472 50
                        ->children()
473 50
                            ->arrayNode('hash_headers')
474 50
                                ->info('List of header(s) that make the request unique (Ex: ‘Authorization’)')
475 50
                                ->prototype('scalar')->end()
476 50
                            ->end()
477 50
                            ->arrayNode('hash_body_methods')
478 50
                                ->info('for which request methods the body makes requests distinct.')
479 50
                                ->prototype('scalar')->end()
480 50
                            ->end()
481 50
                        ->end()
482 50
                    ->end() // End naming_strategy_options
483 50
                    ->scalarNode('fixtures_directory')
484 50
                        ->info('Where the responses will be stored and replay from when using the filesystem recorder. Should be accessible to your VCS.')
485 50
                    ->end()
486 50
                ->end()
487 50
            ->end()
488 50
        ->end();
489
490 50
        return $node;
491
    }
492
493
    /**
494
     * Add the definitions for shared plugin configurations.
495
     *
496
     * @param ArrayNodeDefinition $pluginNode the node to add to
497
     * @param bool                $disableAll Some shared plugins are enabled by default. On the client, all are disabled by default.
498
     */
499 50
    private function addSharedPluginNodes(ArrayNodeDefinition $pluginNode, $disableAll = false)
500
    {
501 50
        $children = $pluginNode->children();
502
503 50
        $children->append($this->createAuthenticationPluginNode());
504 50
        $children->append($this->createCachePluginNode());
505
506
        $children
507 50
            ->arrayNode('cookie')
508 50
                ->canBeEnabled()
509 50
                ->children()
510 50
                    ->scalarNode('cookie_jar')
511 50
                        ->info('This must be a service id to a service implementing '.CookieJar::class)
512 50
                        ->isRequired()
513 50
                        ->cannotBeEmpty()
514 50
                    ->end()
515 50
                ->end()
516 50
            ->end();
517
        // End cookie plugin
518
519
        $children
520 50
            ->arrayNode('history')
521 50
                ->canBeEnabled()
522 50
                ->children()
523 50
                    ->scalarNode('journal')
524 50
                        ->info('This must be a service id to a service implementing '.Journal::class)
525 50
                        ->isRequired()
526 50
                        ->cannotBeEmpty()
527 50
                    ->end()
528 50
                ->end()
529 50
            ->end();
530
        // End history plugin
531
532 50
        $decoder = $children->arrayNode('decoder');
533 50
        $disableAll ? $decoder->canBeEnabled() : $decoder->canBeDisabled();
534 50
        $decoder->addDefaultsIfNotSet()
535 50
            ->children()
536 50
                ->scalarNode('use_content_encoding')->defaultTrue()->end()
537 50
            ->end()
538 50
        ->end();
539
        // End decoder plugin
540
541 50
        $logger = $children->arrayNode('logger');
542 50
        $disableAll ? $logger->canBeEnabled() : $logger->canBeDisabled();
543 50
        $logger->addDefaultsIfNotSet()
544 50
            ->children()
545 50
                ->scalarNode('logger')
546 50
                    ->info('This must be a service id to a service implementing '.LoggerInterface::class)
547 50
                    ->defaultValue('logger')
548 50
                    ->cannotBeEmpty()
549 50
                ->end()
550 50
                ->scalarNode('formatter')
551 50
                    ->info('This must be a service id to a service implementing '.Formatter::class)
552 50
                    ->defaultNull()
553 50
                ->end()
554 50
            ->end()
555 50
        ->end();
556
        // End logger plugin
557
558 50
        $redirect = $children->arrayNode('redirect');
559 50
        $disableAll ? $redirect->canBeEnabled() : $redirect->canBeDisabled();
560 50
        $redirect->addDefaultsIfNotSet()
561 50
            ->children()
562 50
                ->scalarNode('preserve_header')->defaultTrue()->end()
563 50
                ->scalarNode('use_default_for_multiple')->defaultTrue()->end()
564 50
            ->end()
565 50
        ->end();
566
        // End redirect plugin
567
568 50
        $retry = $children->arrayNode('retry');
569 50
        $disableAll ? $retry->canBeEnabled() : $retry->canBeDisabled();
570 50
        $retry->addDefaultsIfNotSet()
571 50
            ->children()
572 50
                ->scalarNode('retry')->defaultValue(1)->end() // TODO: should be called retries for consistency with the class
573 50
            ->end()
574 50
        ->end();
575
        // End retry plugin
576
577 50
        $stopwatch = $children->arrayNode('stopwatch');
578 50
        $disableAll ? $stopwatch->canBeEnabled() : $stopwatch->canBeDisabled();
579 50
        $stopwatch->addDefaultsIfNotSet()
580 50
            ->children()
581 50
                ->scalarNode('stopwatch')
582 50
                    ->info('This must be a service id to a service extending Symfony\Component\Stopwatch\Stopwatch')
583 50
                    ->defaultValue('debug.stopwatch')
584 50
                    ->cannotBeEmpty()
585 50
                ->end()
586 50
            ->end()
587 50
        ->end();
588
        // End stopwatch plugin
589 50
    }
590
591
    /**
592
     * Create configuration for authentication plugin.
593
     *
594
     * @return NodeDefinition definition for the authentication node in the plugins list
595
     */
596 50
    private function createAuthenticationPluginNode()
597
    {
598 50
        $treeBuilder = new TreeBuilder('authentication');
599
        // Keep compatibility with symfony/config < 4.2
600 50
        if (!method_exists($treeBuilder, 'getRootNode')) {
601
            $node = $treeBuilder->root('authentication');
0 ignored issues
show
Bug introduced by
The method root() does not seem to exist on object<Symfony\Component...on\Builder\TreeBuilder>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
602
        } else {
603 50
            $node = $treeBuilder->getRootNode();
604
        }
605
606
        $node
607 50
            ->useAttributeAsKey('name')
608 50
            ->prototype('array')
609 50
                ->validate()
610 50
                    ->always()
611
                    ->then(function ($config) {
612 8
                        switch ($config['type']) {
613 8
                            case 'basic':
614 7
                                $this->validateAuthenticationType(['username', 'password'], $config, 'basic');
615
616 7
                                break;
617 2
                            case 'bearer':
618 1
                                $this->validateAuthenticationType(['token'], $config, 'bearer');
619
620 1
                                break;
621 2
                            case 'service':
622 2
                                $this->validateAuthenticationType(['service'], $config, 'service');
623
624 1
                                break;
625 1
                            case 'wsse':
626 1
                                $this->validateAuthenticationType(['username', 'password'], $config, 'wsse');
627
628 1
                                break;
629
                            case 'query_param':
630
                                $this->validateAuthenticationType(['params'], $config, 'query_param');
631
632
                                break;
633
                        }
634
635 7
                        return $config;
636 50
                    })
637 50
                ->end()
638 50
                ->children()
639 50
                    ->enumNode('type')
640 50
                        ->values(['basic', 'bearer', 'wsse', 'service', 'query_param'])
641 50
                        ->isRequired()
642 50
                        ->cannotBeEmpty()
643 50
                    ->end()
644 50
                    ->scalarNode('username')->end()
645 50
                    ->scalarNode('password')->end()
646 50
                    ->scalarNode('token')->end()
647 50
                    ->scalarNode('service')->end()
648 50
                    ->arrayNode('params')->prototype('scalar')->end()
649 50
                    ->end()
650 50
                ->end()
651 50
            ->end(); // End authentication plugin
652
653 50
        return $node;
654
    }
655
656
    /**
657
     * Validate that the configuration fragment has the specified keys and none other.
658
     *
659
     * @param array  $expected Fields that must exist
660
     * @param array  $actual   Actual configuration hashmap
661
     * @param string $authName Name of authentication method for error messages
662
     *
663
     * @throws InvalidConfigurationException If $actual does not have exactly the keys specified in $expected (plus 'type')
664
     */
665 8
    private function validateAuthenticationType(array $expected, array $actual, $authName)
666
    {
667 8
        unset($actual['type']);
668
        // Empty array is always provided, even if the config is not filled.
669 8
        if (empty($actual['params'])) {
670 8
            unset($actual['params']);
671
        }
672 8
        $actual = array_keys($actual);
673 8
        sort($actual);
674 8
        sort($expected);
675
676 8
        if ($expected === $actual) {
677 7
            return;
678
        }
679
680 1
        throw new InvalidConfigurationException(sprintf(
681 1
            'Authentication "%s" requires %s but got %s',
682
            $authName,
683 1
            implode(', ', $expected),
684 1
            implode(', ', $actual)
685
        ));
686
    }
687
688
    /**
689
     * Create configuration for cache plugin.
690
     *
691
     * @return NodeDefinition definition for the cache node in the plugins list
692
     */
693 50
    private function createCachePluginNode()
694
    {
695 50
        $builder = new TreeBuilder('config');
696
        // Keep compatibility with symfony/config < 4.2
697 50
        if (!method_exists($builder, 'getRootNode')) {
698
            $config = $builder->root('config');
0 ignored issues
show
Bug introduced by
The method root() does not seem to exist on object<Symfony\Component...on\Builder\TreeBuilder>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
699
        } else {
700 50
            $config = $builder->getRootNode();
701
        }
702
703
        $config
704 50
            ->fixXmlConfig('method')
705 50
            ->fixXmlConfig('respect_response_cache_directive')
706 50
            ->addDefaultsIfNotSet()
707 50
            ->validate()
708
                ->ifTrue(function ($config) {
709
                    // Cannot set both respect_cache_headers and respect_response_cache_directives
710 5
                    return isset($config['respect_cache_headers'], $config['respect_response_cache_directives']);
711 50
                })
712 50
                ->thenInvalid('You can\'t provide config option "respect_cache_headers" and "respect_response_cache_directives" simultaniously. Use "respect_response_cache_directives" instead.')
713 50
            ->end()
714 50
            ->children()
715 50
                ->scalarNode('cache_key_generator')
716 50
                    ->info('This must be a service id to a service implementing '.CacheKeyGenerator::class)
717 50
                ->end()
718 50
                ->integerNode('cache_lifetime')
719 50
                    ->info('The minimum time we should store a cache item')
720 50
                ->end()
721 50
                ->integerNode('default_ttl')
722 50
                    ->info('The default max age of a Response')
723 50
                ->end()
724 50
                ->enumNode('hash_algo')
725 50
                    ->info('Hashing algorithm to use')
726 50
                    ->values(hash_algos())
727 50
                    ->cannotBeEmpty()
728 50
                ->end()
729 50
                ->arrayNode('methods')
730 50
                    ->info('Which request methods to cache')
731 50
                    ->defaultValue(['GET', 'HEAD'])
732 50
                    ->prototype('scalar')
733 50
                        ->validate()
734
                            ->ifTrue(function ($v) {
735
                                /* RFC7230 sections 3.1.1 and 3.2.6 except limited to uppercase characters. */
736 1
                                return preg_match('/[^A-Z0-9!#$%&\'*+\-.^_`|~]+/', $v);
737 50
                            })
738 50
                            ->thenInvalid('Invalid method: %s')
739 50
                        ->end()
740 50
                    ->end()
741 50
                ->end()
742 50
                ->scalarNode('respect_cache_headers')
743 50
                    ->info('Whether we should care about cache headers or not [DEPRECATED]')
744 50
                    ->beforeNormalization()
745
                        ->always(function ($v) {
746 3
                            @trigger_error('The option "respect_cache_headers" is deprecated since version 1.3 and will be removed in 2.0. Use "respect_response_cache_directives" instead.', E_USER_DEPRECATED);
747
748 3
                            return $v;
749 50
                        })
750 50
                    ->end()
751 50
                    ->validate()
752 50
                        ->ifNotInArray([null, true, false])
753 50
                        ->thenInvalid('Value for "respect_cache_headers" must be null or boolean')
754 50
                    ->end()
755 50
                ->end()
756 50
                ->variableNode('respect_response_cache_directives')
757 50
                    ->info('A list of cache directives to respect when caching responses')
758 50
                    ->validate()
759
                        ->always(function ($v) {
760 2
                            if (is_null($v) || is_array($v)) {
761 2
                                return $v;
762
                            }
763
764
                            throw new InvalidTypeException();
765 50
                        })
766 50
                    ->end()
767 50
                ->end()
768 50
            ->end()
769
        ;
770
771 50
        $treeBuilder = new TreeBuilder('cache');
772
        // Keep compatibility with symfony/config < 4.2
773 50
        if (!method_exists($treeBuilder, 'getRootNode')) {
774
            $cache = $treeBuilder->root('cache');
0 ignored issues
show
Bug introduced by
The method root() does not seem to exist on object<Symfony\Component...on\Builder\TreeBuilder>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
775
        } else {
776 50
            $cache = $treeBuilder->getRootNode();
777
        }
778
779
        $cache
780 50
            ->canBeEnabled()
781 50
            ->info('Configure HTTP caching, requires the php-http/cache-plugin package')
782 50
            ->addDefaultsIfNotSet()
783 50
            ->validate()
784
                ->ifTrue(function ($v) {
785 5
                    return !empty($v['enabled']) && !class_exists(CachePlugin::class);
786 50
                })
787 50
                ->thenInvalid('To use the cache plugin, you need to require php-http/cache-plugin in your project')
788 50
            ->end()
789 50
            ->children()
790 50
                ->scalarNode('cache_pool')
791 50
                    ->info('This must be a service id to a service implementing '.CacheItemPoolInterface::class)
792 50
                    ->isRequired()
793 50
                    ->cannotBeEmpty()
794 50
                ->end()
795 50
                ->scalarNode('stream_factory')
796 50
                    ->info('This must be a service id to a service implementing '.StreamFactory::class)
797 50
                    ->defaultValue('httplug.stream_factory')
798 50
                    ->cannotBeEmpty()
799 50
                ->end()
800 50
            ->end()
801 50
            ->append($config)
802
        ;
803
804 50
        return $cache;
805
    }
806
}
807