Completed
Push — master ( f1c022...ec01a6 )
by Tobias
05:55
created

Configuration::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 3
cts 3
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1
1
<?php
2
3
namespace Http\HttplugBundle\DependencyInjection;
4
5
use Http\Client\Common\Plugin\Cache\Generator\CacheKeyGenerator;
6
use Http\Client\Common\Plugin\Journal;
7
use Http\Message\CookieJar;
8
use Http\Message\Formatter;
9
use Http\Message\StreamFactory;
10
use Psr\Cache\CacheItemPoolInterface;
11
use Psr\Log\LoggerInterface;
12
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
13
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
14
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
15
use Symfony\Component\Config\Definition\ConfigurationInterface;
16
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
17
use Symfony\Component\Config\Definition\Exception\InvalidTypeException;
18
19
/**
20
 * This class contains the configuration information for the bundle.
21
 *
22
 * This information is solely responsible for how the different configuration
23
 * sections are normalized, and merged.
24
 *
25
 * @author David Buchmann <[email protected]>
26
 * @author Tobias Nyholm <[email protected]>
27
 */
28
class Configuration implements ConfigurationInterface
29
{
30
    /**
31
     * Whether to use the debug mode.
32
     *
33
     * @see https://github.com/doctrine/DoctrineBundle/blob/v1.5.2/DependencyInjection/Configuration.php#L31-L41
34
     *
35
     * @var bool
36
     */
37
    private $debug;
38
39
    /**
40
     * @param bool $debug
41
     */
42 26
    public function __construct($debug)
43
    {
44 26
        $this->debug = (bool) $debug;
45 26
    }
46
47
    /**
48
     * {@inheritdoc}
49
     */
50 26
    public function getConfigTreeBuilder()
51
    {
52 26
        $treeBuilder = new TreeBuilder();
53 26
        $rootNode = $treeBuilder->root('httplug');
54
55 26
        $this->configureClients($rootNode);
56 26
        $this->configureSharedPlugins($rootNode);
57
58
        $rootNode
59 26
            ->validate()
60 26
                ->ifTrue(function ($v) {
61 20
                    return !empty($v['classes']['client'])
62 17
                        || !empty($v['classes']['message_factory'])
63 17
                        || !empty($v['classes']['uri_factory'])
64 20
                        || !empty($v['classes']['stream_factory']);
65 26
                })
66 26
                ->then(function ($v) {
67 3
                    foreach ($v['classes'] as $key => $class) {
68 3
                        if (null !== $class && !class_exists($class)) {
69 1
                            throw new InvalidConfigurationException(sprintf(
70 1
                                'Class %s specified for httplug.classes.%s does not exist.',
71 1
                                $class,
72 3
                                $key
73
                            ));
74
                        }
75
                    }
76
77 2
                    return $v;
78 26
                })
79 26
            ->end()
80 26
            ->beforeNormalization()
81 26
                ->ifTrue(function ($v) {
82 26
                    return is_array($v) && array_key_exists('toolbar', $v) && is_array($v['toolbar']);
83 26
                })
84 26
                ->then(function ($v) {
85 3
                    if (array_key_exists('profiling', $v)) {
86 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".');
87
                    }
88
89 2
                    @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);
90
91 2
                    if (array_key_exists('enabled', $v['toolbar']) && 'auto' === $v['toolbar']['enabled']) {
92 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);
93 1
                        $v['toolbar']['enabled'] = $this->debug;
94
                    }
95
96 2
                    $v['profiling'] = $v['toolbar'];
97
98 2
                    unset($v['toolbar']);
99
100 2
                    return $v;
101 26
                })
102 26
            ->end()
103 26
            ->fixXmlConfig('client')
104 26
            ->children()
105 26
                ->arrayNode('main_alias')
106 26
                    ->addDefaultsIfNotSet()
107 26
                    ->info('Configure which service the main alias point to.')
108 26
                    ->children()
109 26
                        ->scalarNode('client')->defaultValue('httplug.client.default')->end()
110 26
                        ->scalarNode('message_factory')->defaultValue('httplug.message_factory.default')->end()
111 26
                        ->scalarNode('uri_factory')->defaultValue('httplug.uri_factory.default')->end()
112 26
                        ->scalarNode('stream_factory')->defaultValue('httplug.stream_factory.default')->end()
113 26
                    ->end()
114 26
                ->end()
115 26
                ->arrayNode('classes')
116 26
                    ->addDefaultsIfNotSet()
117 26
                    ->info('Overwrite a service class instead of using the discovery mechanism.')
118 26
                    ->children()
119 26
                        ->scalarNode('client')->defaultNull()->end()
120 26
                        ->scalarNode('message_factory')->defaultNull()->end()
121 26
                        ->scalarNode('uri_factory')->defaultNull()->end()
122 26
                        ->scalarNode('stream_factory')->defaultNull()->end()
123 26
                    ->end()
124 26
                ->end()
125 26
                ->arrayNode('profiling')
126 26
                    ->addDefaultsIfNotSet()
127 26
                    ->treatFalseLike(['enabled' => false])
128 26
                    ->treatTrueLike(['enabled' => true])
129 26
                    ->treatNullLike(['enabled' => $this->debug])
130 26
                    ->info('Extend the debug profiler with information about requests.')
131 26
                    ->children()
132 26
                        ->booleanNode('enabled')
133 26
                            ->info('Turn the toolbar on or off. Defaults to kernel debug mode.')
134 26
                            ->defaultValue($this->debug)
135 26
                        ->end()
136 26
                        ->scalarNode('formatter')->defaultNull()->end()
137 26
                        ->integerNode('captured_body_length')
138 26
                            ->defaultValue(0)
139 26
                            ->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).')
140 26
                        ->end()
141 26
                    ->end()
142 26
                ->end()
143 26
                ->arrayNode('discovery')
144 26
                    ->addDefaultsIfNotSet()
145 26
                    ->info('Control what clients should be found by the discovery.')
146 26
                    ->children()
147 26
                        ->scalarNode('client')
148 26
                            ->defaultValue('auto')
149 26
                            ->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.')
150 26
                        ->end()
151 26
                        ->scalarNode('async_client')
152 26
                            ->defaultNull()
153 26
                            ->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.')
154 26
                        ->end()
155 26
                    ->end()
156 26
                ->end()
157 26
            ->end();
158
159 26
        return $treeBuilder;
160
    }
161
162 26
    private function configureClients(ArrayNodeDefinition $root)
163
    {
164 26
        $root->children()
165 26
            ->arrayNode('clients')
166 26
                ->useAttributeAsKey('name')
167 26
                ->prototype('array')
168 26
                ->fixXmlConfig('plugin')
169 26
                ->validate()
170 26
                    ->ifTrue(function ($config) {
171
                        // Make sure we only allow one of these to be true
172 8
                        return (bool) $config['flexible_client'] + (bool) $config['http_methods_client'] + (bool) $config['batch_client'] >= 2;
173 26
                    })
174 26
                    ->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")')
175 26
                ->end()
176 26
                ->validate()
177 26
                    ->ifTrue(function ($config) {
178 8
                        return 'httplug.factory.auto' === $config['factory'] && !empty($config['config']);
179 26
                    })
180 26
                    ->thenInvalid('If you want to use the "config" key you must also specify a valid "factory".')
181 26
                ->end()
182 26
                ->validate()
183 26
                    ->ifTrue(function ($config) {
184 8
                        return !empty($config['service']) && ('httplug.factory.auto' !== $config['factory'] || !empty($config['config']));
185 26
                    })
186 26
                    ->thenInvalid('If you want to use the "service" key you cannot specify "factory" or "config".')
187 26
                ->end()
188 26
                ->children()
189 26
                    ->scalarNode('factory')
190 26
                        ->defaultValue('httplug.factory.auto')
191 26
                        ->cannotBeEmpty()
192 26
                        ->info('The service id of a factory to use when creating the adapter.')
193 26
                    ->end()
194 26
                    ->scalarNode('service')
195 26
                        ->defaultNull()
196 26
                        ->info('The service id of the client to use.')
197 26
                    ->end()
198 26
                    ->booleanNode('flexible_client')
199 26
                        ->defaultFalse()
200 26
                        ->info('Set to true to get the client wrapped in a FlexibleHttpClient which emulates async or sync behavior.')
201 26
                    ->end()
202 26
                    ->booleanNode('http_methods_client')
203 26
                        ->defaultFalse()
204 26
                        ->info('Set to true to get the client wrapped in a HttpMethodsClient which emulates provides functions for HTTP verbs.')
205 26
                    ->end()
206 26
                    ->booleanNode('batch_client')
207 26
                        ->defaultFalse()
208 26
                        ->info('Set to true to get the client wrapped in a BatchClient which allows you to send multiple request at the same time.')
209 26
                    ->end()
210 26
                    ->variableNode('config')->defaultValue([])->end()
211 26
                    ->append($this->createClientPluginNode())
212 26
                ->end()
213 26
            ->end()
214 26
        ->end();
215 26
    }
216
217
    /**
218
     * @param ArrayNodeDefinition $root
219
     */
220 26
    private function configureSharedPlugins(ArrayNodeDefinition $root)
221
    {
222
        $pluginsNode = $root
223 26
            ->children()
224 26
                ->arrayNode('plugins')
225 26
                ->info('Global plugin configuration. Plugins need to be explicitly added to clients.')
226 26
                ->addDefaultsIfNotSet()
227
            // don't call end to get the plugins node
228
        ;
229 26
        $this->addSharedPluginNodes($pluginsNode);
230 26
    }
231
232
    /**
233
     * Createplugins node of a client.
234
     *
235
     * @return ArrayNodeDefinition The plugin node
236
     */
237 26
    private function createClientPluginNode()
238
    {
239 26
        $builder = new TreeBuilder();
240 26
        $node = $builder->root('plugins');
241
242
        /** @var ArrayNodeDefinition $pluginList */
243
        $pluginList = $node
244 26
            ->info('A list of plugin service ids and client specific plugin definitions. The order is important.')
245 26
            ->prototype('array')
246
        ;
247
        $pluginList
248
            // support having just a service id in the list
249 26
            ->beforeNormalization()
250 26
                ->always(function ($plugin) {
251 9
                    if (is_string($plugin)) {
252
                        return [
253 7
                            'reference' => [
254
                                'enabled' => true,
255 7
                                'id' => $plugin,
256
                            ],
257
                        ];
258
                    }
259
260 8
                    return $plugin;
261 26
                })
262 26
            ->end()
263
264 26
            ->validate()
265 26
                ->always(function ($plugins) {
266 7
                    foreach ($plugins as $name => $definition) {
267 7
                        if ('authentication' === $name) {
268 7
                            if (!count($definition)) {
269 7
                                unset($plugins['authentication']);
270
                            }
271 7
                        } elseif (!$definition['enabled']) {
272 7
                            unset($plugins[$name]);
273
                        }
274
                    }
275
276 7
                    return $plugins;
277 26
                })
278 26
            ->end()
279
        ;
280 26
        $this->addSharedPluginNodes($pluginList, true);
281
282
        $pluginList
283 26
            ->children()
284 26
                ->arrayNode('reference')
285 26
                    ->canBeEnabled()
286 26
                    ->info('Reference to a plugin service')
287 26
                    ->children()
288 26
                        ->scalarNode('id')
289 26
                            ->info('Service id of a plugin')
290 26
                            ->isRequired()
291 26
                            ->cannotBeEmpty()
292 26
                        ->end()
293 26
                    ->end()
294 26
                ->end()
295 26
                ->arrayNode('add_host')
296 26
                    ->canBeEnabled()
297 26
                    ->addDefaultsIfNotSet()
298 26
                    ->info('Set scheme, host and port in the request URI.')
299 26
                    ->children()
300 26
                        ->scalarNode('host')
301 26
                            ->info('Host name including protocol and optionally the port number, e.g. https://api.local:8000')
302 26
                            ->isRequired()
303 26
                            ->cannotBeEmpty()
304 26
                        ->end()
305 26
                        ->scalarNode('replace')
306 26
                            ->info('Whether to replace the host if request already specifies one')
307 26
                            ->defaultValue(false)
308 26
                        ->end()
309 26
                    ->end()
310 26
                ->end()
311 26
                ->arrayNode('add_path')
312 26
                    ->canBeEnabled()
313 26
                    ->addDefaultsIfNotSet()
314 26
                    ->info('Add a base path to the request.')
315 26
                    ->children()
316 26
                        ->scalarNode('path')
317 26
                            ->info('Path to be added, e.g. /api/v1')
318 26
                            ->isRequired()
319 26
                            ->cannotBeEmpty()
320 26
                        ->end()
321 26
                    ->end()
322 26
                ->end()
323 26
                ->arrayNode('base_uri')
324 26
                    ->canBeEnabled()
325 26
                    ->addDefaultsIfNotSet()
326 26
                    ->info('Set a base URI to the request.')
327 26
                    ->children()
328 26
                        ->scalarNode('uri')
329 26
                            ->info('Base Uri including protocol, optionally the port number and prepend path, e.g. https://api.local:8000/api')
330 26
                            ->isRequired()
331 26
                            ->cannotBeEmpty()
332 26
                        ->end()
333 26
                        ->scalarNode('replace')
334 26
                            ->info('Whether to replace the host if request already specifies one')
335 26
                            ->defaultValue(false)
336 26
                        ->end()
337 26
                    ->end()
338 26
                ->end()
339 26
                ->arrayNode('header_append')
340 26
                    ->canBeEnabled()
341 26
                    ->info('Append headers to the request. If the header already exists the value will be appended to the current value.')
342 26
                    ->fixXmlConfig('header')
343 26
                    ->children()
344 26
                        ->arrayNode('headers')
345 26
                            ->info('Keys are the header names, values the header values')
346 26
                            ->normalizeKeys(false)
347 26
                            ->useAttributeAsKey('name')
348 26
                            ->prototype('scalar')->end()
349 26
                        ->end()
350 26
                    ->end()
351 26
                ->end()
352 26
                ->arrayNode('header_defaults')
353 26
                    ->canBeEnabled()
354 26
                    ->info('Set header to default value if it does not exist.')
355 26
                    ->fixXmlConfig('header')
356 26
                    ->children()
357 26
                        ->arrayNode('headers')
358 26
                            ->info('Keys are the header names, values the header values')
359 26
                            ->normalizeKeys(false)
360 26
                            ->useAttributeAsKey('name')
361 26
                            ->prototype('scalar')->end()
362 26
                        ->end()
363 26
                    ->end()
364 26
                ->end()
365 26
                ->arrayNode('header_set')
366 26
                    ->canBeEnabled()
367 26
                    ->info('Set headers to requests. If the header does not exist it wil be set, if the header already exists it will be replaced.')
368 26
                    ->fixXmlConfig('header')
369 26
                    ->children()
370 26
                        ->arrayNode('headers')
371 26
                            ->info('Keys are the header names, values the header values')
372 26
                            ->normalizeKeys(false)
373 26
                            ->useAttributeAsKey('name')
374 26
                            ->prototype('scalar')->end()
375 26
                        ->end()
376 26
                    ->end()
377 26
                ->end()
378 26
                ->arrayNode('header_remove')
379 26
                    ->canBeEnabled()
380 26
                    ->info('Remove headers from requests.')
381 26
                    ->fixXmlConfig('header')
382 26
                    ->children()
383 26
                        ->arrayNode('headers')
384 26
                            ->info('List of header names to remove')
385 26
                            ->prototype('scalar')->end()
386 26
                        ->end()
387 26
                    ->end()
388 26
                ->end()
389 26
            ->end()
390 26
        ->end();
391
392 26
        return $node;
393
    }
394
395
    /**
396
     * Add the definitions for shared plugin configurations.
397
     *
398
     * @param ArrayNodeDefinition $pluginNode The node to add to.
399
     * @param bool                $disableAll Some shared plugins are enabled by default. On the client, all are disabled by default.
400
     */
401 26
    private function addSharedPluginNodes(ArrayNodeDefinition $pluginNode, $disableAll = false)
402
    {
403 26
        $children = $pluginNode->children();
404
405 26
        $children->append($this->createAuthenticationPluginNode());
406 26
        $children->append($this->createCachePluginNode());
407
408
        $children
409 26
            ->arrayNode('cookie')
410 26
                ->canBeEnabled()
411 26
                ->children()
412 26
                    ->scalarNode('cookie_jar')
413 26
                        ->info('This must be a service id to a service implementing '.CookieJar::class)
414 26
                        ->isRequired()
415 26
                        ->cannotBeEmpty()
416 26
                    ->end()
417 26
                ->end()
418 26
            ->end();
419
        // End cookie plugin
420
421
        $children
422 26
            ->arrayNode('history')
423 26
                ->canBeEnabled()
424 26
                ->children()
425 26
                    ->scalarNode('journal')
426 26
                        ->info('This must be a service id to a service implementing '.Journal::class)
427 26
                        ->isRequired()
428 26
                        ->cannotBeEmpty()
429 26
                    ->end()
430 26
                ->end()
431 26
            ->end();
432
        // End history plugin
433
434 26
        $decoder = $children->arrayNode('decoder');
435 26
        $disableAll ? $decoder->canBeEnabled() : $decoder->canBeDisabled();
436 26
        $decoder->addDefaultsIfNotSet()
437 26
            ->children()
438 26
                ->scalarNode('use_content_encoding')->defaultTrue()->end()
439 26
            ->end()
440 26
        ->end();
441
        // End decoder plugin
442
443 26
        $logger = $children->arrayNode('logger');
444 26
        $disableAll ? $logger->canBeEnabled() : $logger->canBeDisabled();
445 26
        $logger->addDefaultsIfNotSet()
446 26
            ->children()
447 26
                ->scalarNode('logger')
448 26
                    ->info('This must be a service id to a service implementing '.LoggerInterface::class)
449 26
                    ->defaultValue('logger')
450 26
                    ->cannotBeEmpty()
451 26
                ->end()
452 26
                ->scalarNode('formatter')
453 26
                    ->info('This must be a service id to a service implementing '.Formatter::class)
454 26
                    ->defaultNull()
455 26
                ->end()
456 26
            ->end()
457 26
        ->end();
458
        // End logger plugin
459
460 26
        $redirect = $children->arrayNode('redirect');
461 26
        $disableAll ? $redirect->canBeEnabled() : $redirect->canBeDisabled();
462 26
        $redirect->addDefaultsIfNotSet()
463 26
            ->children()
464 26
                ->scalarNode('preserve_header')->defaultTrue()->end()
465 26
                ->scalarNode('use_default_for_multiple')->defaultTrue()->end()
466 26
            ->end()
467 26
        ->end();
468
        // End redirect plugin
469
470 26
        $retry = $children->arrayNode('retry');
471 26
        $disableAll ? $retry->canBeEnabled() : $retry->canBeDisabled();
472 26
        $retry->addDefaultsIfNotSet()
473 26
            ->children()
474 26
                ->scalarNode('retry')->defaultValue(1)->end() // TODO: should be called retries for consistency with the class
475 26
            ->end()
476 26
        ->end();
477
        // End retry plugin
478
479 26
        $stopwatch = $children->arrayNode('stopwatch');
480 26
        $disableAll ? $stopwatch->canBeEnabled() : $stopwatch->canBeDisabled();
481 26
        $stopwatch->addDefaultsIfNotSet()
482 26
            ->children()
483 26
                ->scalarNode('stopwatch')
484 26
                    ->info('This must be a service id to a service extending Symfony\Component\Stopwatch\Stopwatch')
485 26
                    ->defaultValue('debug.stopwatch')
486 26
                    ->cannotBeEmpty()
487 26
                ->end()
488 26
            ->end()
489 26
        ->end();
490
        // End stopwatch plugin
491 26
    }
492
493
    /**
494
     * Create configuration for authentication plugin.
495
     *
496
     * @return NodeDefinition Definition for the authentication node in the plugins list.
497
     */
498 26
    private function createAuthenticationPluginNode()
499
    {
500 26
        $builder = new TreeBuilder();
501 26
        $node = $builder->root('authentication');
502
        $node
503 26
            ->useAttributeAsKey('name')
504 26
            ->prototype('array')
505 26
                ->validate()
506 26
                    ->always()
507 26
                    ->then(function ($config) {
508 7
                        switch ($config['type']) {
509 7
                            case 'basic':
510 6
                                $this->validateAuthenticationType(['username', 'password'], $config, 'basic');
511
512 6
                                break;
513 2
                            case 'bearer':
514 1
                                $this->validateAuthenticationType(['token'], $config, 'bearer');
515
516 1
                                break;
517 2
                            case 'service':
518 2
                                $this->validateAuthenticationType(['service'], $config, 'service');
519
520 1
                                break;
521 1
                            case 'wsse':
522 1
                                $this->validateAuthenticationType(['username', 'password'], $config, 'wsse');
523
524 1
                                break;
525
                            case 'query_param':
526
                                $this->validateAuthenticationType(['params'], $config, 'query_param');
527
528
                                break;
529
                        }
530
531 6
                        return $config;
532 26
                    })
533 26
                ->end()
534 26
                ->children()
535 26
                    ->enumNode('type')
536 26
                        ->values(['basic', 'bearer', 'wsse', 'service', 'query_param'])
537 26
                        ->isRequired()
538 26
                        ->cannotBeEmpty()
539 26
                    ->end()
540 26
                    ->scalarNode('username')->end()
541 26
                    ->scalarNode('password')->end()
542 26
                    ->scalarNode('token')->end()
543 26
                    ->scalarNode('service')->end()
544 26
                    ->arrayNode('params')->prototype('scalar')->end()
545 26
                    ->end()
546 26
                ->end()
547 26
            ->end(); // End authentication plugin
548
549 26
        return $node;
550
    }
551
552
    /**
553
     * Validate that the configuration fragment has the specified keys and none other.
554
     *
555
     * @param array  $expected Fields that must exist
556
     * @param array  $actual   Actual configuration hashmap
557
     * @param string $authName Name of authentication method for error messages
558
     *
559
     * @throws InvalidConfigurationException If $actual does not have exactly the keys specified in $expected (plus 'type')
560
     */
561 7
    private function validateAuthenticationType(array $expected, array $actual, $authName)
562
    {
563 7
        unset($actual['type']);
564
        // Empty array is always provided, even if the config is not filled.
565 7
        if (empty($actual['params'])) {
566 7
            unset($actual['params']);
567
        }
568 7
        $actual = array_keys($actual);
569 7
        sort($actual);
570 7
        sort($expected);
571
572 7
        if ($expected === $actual) {
573 6
            return;
574
        }
575
576 1
        throw new InvalidConfigurationException(sprintf(
577 1
            'Authentication "%s" requires %s but got %s',
578 1
            $authName,
579 1
            implode(', ', $expected),
580 1
            implode(', ', $actual)
581
        ));
582
    }
583
584
    /**
585
     * Create configuration for cache plugin.
586
     *
587
     * @return NodeDefinition Definition for the cache node in the plugins list.
588
     */
589 26
    private function createCachePluginNode()
590
    {
591 26
        $builder = new TreeBuilder();
592
593 26
        $config = $builder->root('config');
594
        $config
595 26
            ->fixXmlConfig('method')
596 26
            ->fixXmlConfig('respect_response_cache_directive')
597 26
            ->addDefaultsIfNotSet()
598 26
            ->validate()
599 26
                ->ifTrue(function ($config) {
600
                    // Cannot set both respect_cache_headers and respect_response_cache_directives
601 5
                    return isset($config['respect_cache_headers'], $config['respect_response_cache_directives']);
602 26
                })
603 26
                ->thenInvalid('You can\'t provide config option "respect_cache_headers" and "respect_response_cache_directives" simultaniously. Use "respect_response_cache_directives" instead.')
604 26
            ->end()
605 26
            ->children()
606 26
                ->scalarNode('cache_key_generator')
607 26
                    ->info('This must be a service id to a service implementing '.CacheKeyGenerator::class)
608 26
                ->end()
609 26
                ->integerNode('cache_lifetime')
610 26
                    ->info('The minimum time we should store a cache item')
611 26
                ->end()
612 26
                ->integerNode('default_ttl')
613 26
                    ->info('The default max age of a Response')
614 26
                ->end()
615 26
                ->enumNode('hash_algo')
616 26
                    ->info('Hashing algorithm to use')
617 26
                    ->values(hash_algos())
618 26
                    ->cannotBeEmpty()
619 26
                ->end()
620 26
                ->arrayNode('methods')
621 26
                    ->info('Which request methods to cache')
622 26
                    ->defaultValue(['GET', 'HEAD'])
623 26
                    ->prototype('scalar')
624 26
                        ->validate()
625 26
                            ->ifTrue(function ($v) {
626
                                /* RFC7230 sections 3.1.1 and 3.2.6 except limited to uppercase characters. */
627 1
                                return preg_match('/[^A-Z0-9!#$%&\'*+\-.^_`|~]+/', $v);
628 26
                            })
629 26
                            ->thenInvalid('Invalid method: %s')
630 26
                        ->end()
631 26
                    ->end()
632 26
                ->end()
633 26
                ->scalarNode('respect_cache_headers')
634 26
                    ->info('Whether we should care about cache headers or not [DEPRECATED]')
635 26
                    ->beforeNormalization()
636 26
                        ->always(function ($v) {
637 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);
638
639 3
                            return $v;
640 26
                        })
641 26
                    ->end()
642 26
                    ->validate()
643 26
                        ->ifNotInArray([null, true, false])
644 26
                        ->thenInvalid('Value for "respect_cache_headers" must be null or boolean')
645 26
                    ->end()
646 26
                ->end()
647 26
                ->variableNode('respect_response_cache_directives')
648 26
                    ->info('A list of cache directives to respect when caching responses')
649 26
                    ->validate()
650 26
                        ->always(function ($v) {
651 2
                            if (is_null($v) || is_array($v)) {
652 2
                                return $v;
653
                            }
654
655
                            throw new InvalidTypeException();
656 26
                        })
657 26
                    ->end()
658 26
                ->end()
659 26
            ->end()
660
        ;
661
662 26
        $cache = $builder->root('cache');
663
        $cache
664 26
            ->canBeEnabled()
665 26
            ->addDefaultsIfNotSet()
666 26
            ->children()
667 26
                ->scalarNode('cache_pool')
668 26
                    ->info('This must be a service id to a service implementing '.CacheItemPoolInterface::class)
669 26
                    ->isRequired()
670 26
                    ->cannotBeEmpty()
671 26
                ->end()
672 26
                ->scalarNode('stream_factory')
673 26
                    ->info('This must be a service id to a service implementing '.StreamFactory::class)
674 26
                    ->defaultValue('httplug.stream_factory')
675 26
                    ->cannotBeEmpty()
676 26
                ->end()
677 26
            ->end()
678 26
            ->append($config)
679
        ;
680
681 26
        return $cache;
682
    }
683
}
684