Configuration   B
last analyzed

Complexity

Total Complexity 50

Size/Duplication

Total Lines 808
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 8

Test Coverage

Coverage 98.54%

Importance

Changes 0
Metric Value
wmc 50
lcom 1
cbo 8
dl 0
loc 808
ccs 607
cts 616
cp 0.9854
rs 8.192
c 0
b 0
f 0

9 Methods

Rating   Name   Duplication   Size   Complexity  
A configureSharedPlugins() 0 11 1
C createClientPluginNode() 0 232 8
B addSharedPluginNodes() 0 91 6
B createAuthenticationPluginNode() 0 59 7
A validateAuthenticationType() 0 22 3
A __construct() 0 4 1
C getConfigTreeBuilder() 0 140 14
B configureClients() 0 58 4
B createCachePluginNode() 0 135 6

How to fix   Complexity   

Complex Class

Complex classes like Configuration often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Configuration, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Http\HttplugBundle\DependencyInjection;
6
7
use Http\Client\Common\Plugin\Cache\Generator\CacheKeyGenerator;
8
use Http\Client\Common\Plugin\Cache\Listener\CacheListener;
9
use Http\Client\Common\Plugin\CachePlugin;
10
use Http\Client\Common\Plugin\Journal;
11
use Http\Client\Plugin\Vcr\NamingStrategy\NamingStrategyInterface;
12
use Http\Client\Plugin\Vcr\Recorder\PlayerInterface;
13
use Http\Client\Plugin\Vcr\Recorder\RecorderInterface;
14
use Http\Message\CookieJar;
15
use Http\Message\Formatter;
16
use Http\Message\StreamFactory;
17
use Psr\Cache\CacheItemPoolInterface;
18
use Psr\Log\LoggerInterface;
19
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
20
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
21
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
22
use Symfony\Component\Config\Definition\ConfigurationInterface;
23
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
24
use Symfony\Component\Config\Definition\Exception\InvalidTypeException;
25
26
/**
27
 * This class contains the configuration information for the bundle.
28
 *
29
 * This information is solely responsible for how the different configuration
30
 * sections are normalized, and merged.
31
 *
32
 * @author David Buchmann <[email protected]>
33
 * @author Tobias Nyholm <[email protected]>
34
 */
35
class Configuration implements ConfigurationInterface
36
{
37
    /**
38
     * Whether to use the debug mode.
39
     *
40
     * @see https://github.com/doctrine/DoctrineBundle/blob/v1.5.2/DependencyInjection/Configuration.php#L31-L41
41
     *
42
     * @var bool
43
     */
44
    private $debug;
45
46
    /**
47
     * @param bool $debug
48
     */
49 51
    public function __construct($debug)
50
    {
51 51
        $this->debug = (bool) $debug;
52 51
    }
53
54
    /**
55
     * {@inheritdoc}
56
     */
57 51
    public function getConfigTreeBuilder()
58
    {
59 51
        $treeBuilder = new TreeBuilder('httplug');
60
        // Keep compatibility with symfony/config < 4.2
61 51
        if (!method_exists($treeBuilder, 'getRootNode')) {
62
            $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...
63
        } else {
64 51
            $rootNode = $treeBuilder->getRootNode();
65
        }
66
67 51
        $this->configureClients($rootNode);
68 51
        $this->configureSharedPlugins($rootNode);
69
70
        $rootNode
71 51
            ->validate()
72
                ->ifTrue(function ($v) {
73 44
                    return !empty($v['classes']['client'])
74 41
                        || !empty($v['classes']['message_factory'])
75 41
                        || !empty($v['classes']['uri_factory'])
76 44
                        || !empty($v['classes']['stream_factory']);
77 51
                })
78
                ->then(function ($v) {
79 3
                    foreach ($v['classes'] as $key => $class) {
80 3
                        if (null !== $class && !class_exists($class)) {
81 1
                            throw new InvalidConfigurationException(sprintf(
82 1
                                'Class %s specified for httplug.classes.%s does not exist.',
83
                                $class,
84
                                $key
85
                            ));
86
                        }
87
                    }
88
89 2
                    return $v;
90 51
                })
91 51
            ->end()
92 51
            ->beforeNormalization()
93
                ->ifTrue(function ($v) {
94 51
                    return is_array($v) && array_key_exists('toolbar', $v) && is_array($v['toolbar']);
95 51
                })
96
                ->then(function ($v) {
97 4
                    if (array_key_exists('profiling', $v)) {
98 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".');
99
                    }
100
101 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);
102
103 3
                    if (array_key_exists('enabled', $v['toolbar']) && 'auto' === $v['toolbar']['enabled']) {
104 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);
105 1
                        $v['toolbar']['enabled'] = $this->debug;
106
                    }
107
108 3
                    $v['profiling'] = $v['toolbar'];
109
110 3
                    unset($v['toolbar']);
111
112 3
                    return $v;
113 51
                })
114 51
            ->end()
115 51
            ->fixXmlConfig('client')
116 51
            ->children()
117 51
                ->booleanNode('default_client_autowiring')
118 51
                    ->defaultTrue()
119 51
                    ->info('Set to false to not autowire HttpClient and HttpAsyncClient.')
120 51
                ->end()
121 51
                ->arrayNode('main_alias')
122 51
                    ->addDefaultsIfNotSet()
123 51
                    ->info('Configure which service the main alias point to.')
124 51
                    ->children()
125 51
                        ->scalarNode('client')->defaultValue('httplug.client.default')->end()
126 51
                        ->scalarNode('psr18_client')->defaultValue('httplug.psr18_client.default')->end()
127 51
                        ->scalarNode('message_factory')->defaultValue('httplug.message_factory.default')->end()
128 51
                        ->scalarNode('uri_factory')->defaultValue('httplug.uri_factory.default')->end()
129 51
                        ->scalarNode('stream_factory')->defaultValue('httplug.stream_factory.default')->end()
130 51
                        ->scalarNode('psr17_request_factory')->defaultValue('httplug.psr17_request_factory.default')->end()
131 51
                        ->scalarNode('psr17_response_factory')->defaultValue('httplug.psr17_response_factory.default')->end()
132 51
                        ->scalarNode('psr17_stream_factory')->defaultValue('httplug.psr17_stream_factory.default')->end()
133 51
                        ->scalarNode('psr17_uri_factory')->defaultValue('httplug.psr17_uri_factory.default')->end()
134 51
                        ->scalarNode('psr17_uploaded_file_factory')->defaultValue('httplug.psr17_uploaded_file_factory.default')->end()
135 51
                        ->scalarNode('psr17_server_request_factory')->defaultValue('httplug.psr17_server_request_factory.default')->end()
136 51
                    ->end()
137 51
                ->end()
138 51
                ->arrayNode('classes')
139 51
                    ->addDefaultsIfNotSet()
140 51
                    ->info('Overwrite a service class instead of using the discovery mechanism.')
141 51
                    ->children()
142 51
                        ->scalarNode('client')->defaultNull()->end()
143 51
                        ->scalarNode('psr18_client')->defaultNull()->end()
144 51
                        ->scalarNode('message_factory')->defaultNull()->end()
145 51
                        ->scalarNode('uri_factory')->defaultNull()->end()
146 51
                        ->scalarNode('stream_factory')->defaultNull()->end()
147 51
                        ->scalarNode('psr17_request_factory')->defaultNull()->end()
148 51
                        ->scalarNode('psr17_response_factory')->defaultNull()->end()
149 51
                        ->scalarNode('psr17_stream_factory')->defaultNull()->end()
150 51
                        ->scalarNode('psr17_uri_factory')->defaultNull()->end()
151 51
                        ->scalarNode('psr17_uploaded_file_factory')->defaultNull()->end()
152 51
                        ->scalarNode('psr17_server_request_factory')->defaultNull()->end()
153 51
                    ->end()
154 51
                ->end()
155 51
                ->arrayNode('profiling')
156 51
                    ->addDefaultsIfNotSet()
157 51
                    ->treatFalseLike(['enabled' => false])
158 51
                    ->treatTrueLike(['enabled' => true])
159 51
                    ->treatNullLike(['enabled' => $this->debug])
160 51
                    ->info('Extend the debug profiler with information about requests.')
161 51
                    ->children()
162 51
                        ->booleanNode('enabled')
163 51
                            ->info('Turn the toolbar on or off. Defaults to kernel debug mode.')
164 51
                            ->defaultValue($this->debug)
165 51
                        ->end()
166 51
                        ->scalarNode('formatter')->defaultNull()->end()
167 51
                        ->scalarNode('captured_body_length')
168 51
                            ->validate()
169
                                ->ifTrue(function ($v) {
170 4
                                    return null !== $v && !is_int($v);
171 51
                                })
172 51
                                ->thenInvalid('The child node "captured_body_length" at path "httplug.profiling" must be an integer or null ("%s" given).')
173 51
                            ->end()
174 51
                            ->defaultValue(0)
175 51
                            ->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).')
176 51
                        ->end()
177 51
                    ->end()
178 51
                ->end()
179 51
                ->arrayNode('discovery')
180 51
                    ->addDefaultsIfNotSet()
181 51
                    ->info('Control what clients should be found by the discovery.')
182 51
                    ->children()
183 51
                        ->scalarNode('client')
184 51
                            ->defaultValue('auto')
185 51
                            ->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.')
186 51
                        ->end()
187 51
                        ->scalarNode('async_client')
188 51
                            ->defaultNull()
189 51
                            ->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.')
190 51
                        ->end()
191 51
                    ->end()
192 51
                ->end()
193 51
            ->end();
194
195 51
        return $treeBuilder;
196
    }
197
198 51
    private function configureClients(ArrayNodeDefinition $root)
199
    {
200 51
        $root->children()
201 51
            ->arrayNode('clients')
202 51
                ->useAttributeAsKey('name')
203 51
                ->prototype('array')
204 51
                ->fixXmlConfig('plugin')
205 51
                ->validate()
206
                    ->ifTrue(function ($config) {
207
                        // Make sure we only allow one of these to be true
208 27
                        return (bool) $config['flexible_client'] + (bool) $config['http_methods_client'] + (bool) $config['batch_client'] >= 2;
209 51
                    })
210 51
                    ->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")')
211 51
                ->end()
212 51
                ->validate()
213
                    ->ifTrue(function ($config) {
214 27
                        return 'httplug.factory.auto' === $config['factory'] && !empty($config['config']);
215 51
                    })
216 51
                    ->thenInvalid('If you want to use the "config" key you must also specify a valid "factory".')
217 51
                ->end()
218 51
                ->validate()
219
                    ->ifTrue(function ($config) {
220 27
                        return !empty($config['service']) && ('httplug.factory.auto' !== $config['factory'] || !empty($config['config']));
221 51
                    })
222 51
                    ->thenInvalid('If you want to use the "service" key you cannot specify "factory" or "config".')
223 51
                ->end()
224 51
                ->children()
225 51
                    ->scalarNode('factory')
226 51
                        ->defaultValue('httplug.factory.auto')
227 51
                        ->cannotBeEmpty()
228 51
                        ->info('The service id of a factory to use when creating the adapter.')
229 51
                    ->end()
230 51
                    ->scalarNode('service')
231 51
                        ->defaultNull()
232 51
                        ->info('The service id of the client to use.')
233 51
                    ->end()
234 51
                    ->booleanNode('public')
235 51
                        ->defaultNull()
236 51
                        ->info('Set to true if you really cannot use dependency injection and need to make the client service public.')
237 51
                    ->end()
238 51
                    ->booleanNode('flexible_client')
239 51
                        ->defaultFalse()
240 51
                        ->info('Set to true to get the client wrapped in a FlexibleHttpClient which emulates async or sync behavior.')
241 51
                    ->end()
242 51
                    ->booleanNode('http_methods_client')
243 51
                        ->defaultFalse()
244 51
                        ->info('Set to true to get the client wrapped in a HttpMethodsClient which emulates provides functions for HTTP verbs.')
245 51
                    ->end()
246 51
                    ->booleanNode('batch_client')
247 51
                        ->defaultFalse()
248 51
                        ->info('Set to true to get the client wrapped in a BatchClient which allows you to send multiple request at the same time.')
249 51
                    ->end()
250 51
                    ->variableNode('config')->defaultValue([])->end()
251 51
                    ->append($this->createClientPluginNode())
252 51
                ->end()
253 51
            ->end()
254 51
        ->end();
255 51
    }
256
257 51
    private function configureSharedPlugins(ArrayNodeDefinition $root)
258
    {
259
        $pluginsNode = $root
260 51
            ->children()
261 51
                ->arrayNode('plugins')
262 51
                ->info('Global plugin configuration. Plugins need to be explicitly added to clients.')
263 51
                ->addDefaultsIfNotSet()
264
            // don't call end to get the plugins node
265
        ;
266 51
        $this->addSharedPluginNodes($pluginsNode);
267 51
    }
268
269
    /**
270
     * Createplugins node of a client.
271
     *
272
     * @return ArrayNodeDefinition The plugin node
273
     */
274 51
    private function createClientPluginNode()
275
    {
276 51
        $treeBuilder = new TreeBuilder('plugins');
277
        // Keep compatibility with symfony/config < 4.2
278 51
        if (!method_exists($treeBuilder, 'getRootNode')) {
279
            $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...
280
        } else {
281 51
            $node = $treeBuilder->getRootNode();
282
        }
283
284
        /** @var ArrayNodeDefinition $pluginList */
285
        $pluginList = $node
286 51
            ->info('A list of plugin service ids and client specific plugin definitions. The order is important.')
287 51
            ->prototype('array')
288
        ;
289
        $pluginList
290
            // support having just a service id in the list
291 51
            ->beforeNormalization()
292
                ->always(function ($plugin) {
293 18
                    if (is_string($plugin)) {
294
                        return [
295
                            'reference' => [
296 10
                                'enabled' => true,
297 10
                                'id' => $plugin,
298
                            ],
299
                        ];
300
                    }
301
302 15
                    return $plugin;
303 51
                })
304 51
            ->end()
305
306 51
            ->validate()
307
                ->always(function ($plugins) {
308 16
                    foreach ($plugins as $name => $definition) {
309 16
                        if ('authentication' === $name) {
310 16
                            if (!count($definition)) {
311 16
                                unset($plugins['authentication']);
312
                            }
313 16
                        } elseif (!$definition['enabled']) {
314 16
                            unset($plugins[$name]);
315
                        }
316
                    }
317
318 16
                    return $plugins;
319 51
                })
320 51
            ->end()
321
        ;
322 51
        $this->addSharedPluginNodes($pluginList, true);
323
324
        $pluginList
325 51
            ->children()
326 51
                ->arrayNode('reference')
327 51
                    ->canBeEnabled()
328 51
                    ->info('Reference to a plugin service')
329 51
                    ->children()
330 51
                        ->scalarNode('id')
331 51
                            ->info('Service id of a plugin')
332 51
                            ->isRequired()
333 51
                            ->cannotBeEmpty()
334 51
                        ->end()
335 51
                    ->end()
336 51
                ->end()
337 51
                ->arrayNode('add_host')
338 51
                    ->canBeEnabled()
339 51
                    ->addDefaultsIfNotSet()
340 51
                    ->info('Set scheme, host and port in the request URI.')
341 51
                    ->children()
342 51
                        ->scalarNode('host')
343 51
                            ->info('Host name including protocol and optionally the port number, e.g. https://api.local:8000')
344 51
                            ->isRequired()
345 51
                            ->cannotBeEmpty()
346 51
                        ->end()
347 51
                        ->scalarNode('replace')
348 51
                            ->info('Whether to replace the host if request already specifies one')
349 51
                            ->defaultValue(false)
350 51
                        ->end()
351 51
                    ->end()
352 51
                ->end()
353 51
                ->arrayNode('add_path')
354 51
                    ->canBeEnabled()
355 51
                    ->addDefaultsIfNotSet()
356 51
                    ->info('Add a base path to the request.')
357 51
                    ->children()
358 51
                        ->scalarNode('path')
359 51
                            ->info('Path to be added, e.g. /api/v1')
360 51
                            ->isRequired()
361 51
                            ->cannotBeEmpty()
362 51
                        ->end()
363 51
                    ->end()
364 51
                ->end()
365 51
                ->arrayNode('base_uri')
366 51
                    ->canBeEnabled()
367 51
                    ->addDefaultsIfNotSet()
368 51
                    ->info('Set a base URI to the request.')
369 51
                    ->children()
370 51
                        ->scalarNode('uri')
371 51
                            ->info('Base Uri including protocol, optionally the port number and prepend path, e.g. https://api.local:8000/api')
372 51
                            ->isRequired()
373 51
                            ->cannotBeEmpty()
374 51
                        ->end()
375 51
                        ->scalarNode('replace')
376 51
                            ->info('Whether to replace the host if request already specifies one')
377 51
                            ->defaultValue(false)
378 51
                        ->end()
379 51
                    ->end()
380 51
                ->end()
381 51
                ->arrayNode('content_type')
382 51
                    ->canBeEnabled()
383 51
                    ->info('Detect the content type of a request body and set the Content-Type header if it is not already set.')
384 51
                    ->children()
385 51
                        ->booleanNode('skip_detection')
386 51
                            ->info('Whether to skip detection when request body is larger than size_limit')
387 51
                            ->defaultFalse()
388 51
                        ->end()
389 51
                        ->scalarNode('size_limit')
390 51
                            ->info('Skip content type detection if request body is larger than size_limit bytes')
391 51
                        ->end()
392 51
                    ->end()
393 51
                ->end()
394 51
                ->arrayNode('header_append')
395 51
                    ->canBeEnabled()
396 51
                    ->info('Append headers to the request. If the header already exists the value will be appended to the current value.')
397 51
                    ->fixXmlConfig('header')
398 51
                    ->children()
399 51
                        ->arrayNode('headers')
400 51
                            ->info('Keys are the header names, values the header values')
401 51
                            ->normalizeKeys(false)
402 51
                            ->useAttributeAsKey('name')
403 51
                            ->prototype('scalar')->end()
404 51
                        ->end()
405 51
                    ->end()
406 51
                ->end()
407 51
                ->arrayNode('header_defaults')
408 51
                    ->canBeEnabled()
409 51
                    ->info('Set header to default value if it does not exist.')
410 51
                    ->fixXmlConfig('header')
411 51
                    ->children()
412 51
                        ->arrayNode('headers')
413 51
                            ->info('Keys are the header names, values the header values')
414 51
                            ->normalizeKeys(false)
415 51
                            ->useAttributeAsKey('name')
416 51
                            ->prototype('scalar')->end()
417 51
                        ->end()
418 51
                    ->end()
419 51
                ->end()
420 51
                ->arrayNode('header_set')
421 51
                    ->canBeEnabled()
422 51
                    ->info('Set headers to requests. If the header does not exist it wil be set, if the header already exists it will be replaced.')
423 51
                    ->fixXmlConfig('header')
424 51
                    ->children()
425 51
                        ->arrayNode('headers')
426 51
                            ->info('Keys are the header names, values the header values')
427 51
                            ->normalizeKeys(false)
428 51
                            ->useAttributeAsKey('name')
429 51
                            ->prototype('scalar')->end()
430 51
                        ->end()
431 51
                    ->end()
432 51
                ->end()
433 51
                ->arrayNode('header_remove')
434 51
                    ->canBeEnabled()
435 51
                    ->info('Remove headers from requests.')
436 51
                    ->fixXmlConfig('header')
437 51
                    ->children()
438 51
                        ->arrayNode('headers')
439 51
                            ->info('List of header names to remove')
440 51
                            ->prototype('scalar')->end()
441 51
                        ->end()
442 51
                    ->end()
443 51
                ->end()
444 51
                ->arrayNode('query_defaults')
445 51
                    ->canBeEnabled()
446 51
                    ->info('Sets query parameters to default value if they are not present in the request.')
447 51
                    ->fixXmlConfig('parameter')
448 51
                    ->children()
449 51
                        ->arrayNode('parameters')
450 51
                            ->info('List of query parameters. Names and values must not be url encoded as the plugin will encode them.')
451 51
                            ->normalizeKeys(false)
452 51
                            ->useAttributeAsKey('name')
453 51
                            ->prototype('scalar')->end()
454 51
                        ->end()
455 51
                    ->end()
456 51
                ->end()
457 51
                ->arrayNode('vcr')
458 51
                    ->canBeEnabled()
459 51
                    ->addDefaultsIfNotSet()
460 51
                    ->info('Record response to be replayed during tests or development cycle.')
461 51
                    ->validate()
462
                        ->ifTrue(function ($config) {
463 5
                            return 'filesystem' === $config['recorder'] && empty($config['fixtures_directory']);
464 51
                        })
465 51
                        ->thenInvalid('If you want to use the "filesystem" recorder you must also specify a "fixtures_directory".')
466 51
                    ->end()
467 51
                    ->children()
468 51
                        ->enumNode('mode')
469 51
                        ->info('What should be the behavior of the plugin?')
470 51
                        ->values(['record', 'replay', 'replay_or_record'])
471 51
                        ->isRequired()
472 51
                        ->cannotBeEmpty()
473 51
                    ->end()
474 51
                    ->scalarNode('recorder')
475 51
                        ->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))
476 51
                        ->defaultValue('filesystem')
477 51
                        ->cannotBeEmpty()
478 51
                    ->end()
479 51
                    ->scalarNode('naming_strategy')
480 51
                        ->info(sprintf('Which naming strategy to use. Add the ID of your service implementing %s to override the default one.', NamingStrategyInterface::class))
481 51
                        ->defaultValue('default')
482 51
                        ->cannotBeEmpty()
483 51
                    ->end()
484 51
                    ->arrayNode('naming_strategy_options')
485 51
                        ->info('See http://docs.php-http.org/en/latest/plugins/vcr.html#the-naming-strategy for more details')
486 51
                        ->children()
487 51
                            ->arrayNode('hash_headers')
488 51
                                ->info('List of header(s) that make the request unique (Ex: ‘Authorization’)')
489 51
                                ->prototype('scalar')->end()
490 51
                            ->end()
491 51
                            ->arrayNode('hash_body_methods')
492 51
                                ->info('for which request methods the body makes requests distinct.')
493 51
                                ->prototype('scalar')->end()
494 51
                            ->end()
495 51
                        ->end()
496 51
                    ->end() // End naming_strategy_options
497 51
                    ->scalarNode('fixtures_directory')
498 51
                        ->info('Where the responses will be stored and replay from when using the filesystem recorder. Should be accessible to your VCS.')
499 51
                    ->end()
500 51
                ->end()
501 51
            ->end()
502 51
        ->end();
503
504 51
        return $node;
505
    }
506
507
    /**
508
     * Add the definitions for shared plugin configurations.
509
     *
510
     * @param ArrayNodeDefinition $pluginNode the node to add to
511
     * @param bool                $disableAll Some shared plugins are enabled by default. On the client, all are disabled by default.
512
     */
513 51
    private function addSharedPluginNodes(ArrayNodeDefinition $pluginNode, $disableAll = false)
514
    {
515 51
        $children = $pluginNode->children();
516
517 51
        $children->append($this->createAuthenticationPluginNode());
518 51
        $children->append($this->createCachePluginNode());
519
520
        $children
521 51
            ->arrayNode('cookie')
522 51
                ->canBeEnabled()
523 51
                ->children()
524 51
                    ->scalarNode('cookie_jar')
525 51
                        ->info('This must be a service id to a service implementing '.CookieJar::class)
526 51
                        ->isRequired()
527 51
                        ->cannotBeEmpty()
528 51
                    ->end()
529 51
                ->end()
530 51
            ->end();
531
        // End cookie plugin
532
533
        $children
534 51
            ->arrayNode('history')
535 51
                ->canBeEnabled()
536 51
                ->children()
537 51
                    ->scalarNode('journal')
538 51
                        ->info('This must be a service id to a service implementing '.Journal::class)
539 51
                        ->isRequired()
540 51
                        ->cannotBeEmpty()
541 51
                    ->end()
542 51
                ->end()
543 51
            ->end();
544
        // End history plugin
545
546 51
        $decoder = $children->arrayNode('decoder');
547 51
        $disableAll ? $decoder->canBeEnabled() : $decoder->canBeDisabled();
548 51
        $decoder->addDefaultsIfNotSet()
549 51
            ->children()
550 51
                ->scalarNode('use_content_encoding')->defaultTrue()->end()
551 51
            ->end()
552 51
        ->end();
553
        // End decoder plugin
554
555 51
        $logger = $children->arrayNode('logger');
556 51
        $disableAll ? $logger->canBeEnabled() : $logger->canBeDisabled();
557 51
        $logger->addDefaultsIfNotSet()
558 51
            ->children()
559 51
                ->scalarNode('logger')
560 51
                    ->info('This must be a service id to a service implementing '.LoggerInterface::class)
561 51
                    ->defaultValue('logger')
562 51
                    ->cannotBeEmpty()
563 51
                ->end()
564 51
                ->scalarNode('formatter')
565 51
                    ->info('This must be a service id to a service implementing '.Formatter::class)
566 51
                    ->defaultNull()
567 51
                ->end()
568 51
            ->end()
569 51
        ->end();
570
        // End logger plugin
571
572 51
        $redirect = $children->arrayNode('redirect');
573 51
        $disableAll ? $redirect->canBeEnabled() : $redirect->canBeDisabled();
574 51
        $redirect->addDefaultsIfNotSet()
575 51
            ->children()
576 51
                ->scalarNode('preserve_header')->defaultTrue()->end()
577 51
                ->scalarNode('use_default_for_multiple')->defaultTrue()->end()
578 51
            ->end()
579 51
        ->end();
580
        // End redirect plugin
581
582 51
        $retry = $children->arrayNode('retry');
583 51
        $disableAll ? $retry->canBeEnabled() : $retry->canBeDisabled();
584 51
        $retry->addDefaultsIfNotSet()
585 51
            ->children()
586 51
                ->scalarNode('retry')->defaultValue(1)->end() // TODO: should be called retries for consistency with the class
587 51
            ->end()
588 51
        ->end();
589
        // End retry plugin
590
591 51
        $stopwatch = $children->arrayNode('stopwatch');
592 51
        $disableAll ? $stopwatch->canBeEnabled() : $stopwatch->canBeDisabled();
593 51
        $stopwatch->addDefaultsIfNotSet()
594 51
            ->children()
595 51
                ->scalarNode('stopwatch')
596 51
                    ->info('This must be a service id to a service extending Symfony\Component\Stopwatch\Stopwatch')
597 51
                    ->defaultValue('debug.stopwatch')
598 51
                    ->cannotBeEmpty()
599 51
                ->end()
600 51
            ->end()
601 51
        ->end();
602
        // End stopwatch plugin
603 51
    }
604
605
    /**
606
     * Create configuration for authentication plugin.
607
     *
608
     * @return NodeDefinition definition for the authentication node in the plugins list
609
     */
610 51
    private function createAuthenticationPluginNode()
611
    {
612 51
        $treeBuilder = new TreeBuilder('authentication');
613
        // Keep compatibility with symfony/config < 4.2
614 51
        if (!method_exists($treeBuilder, 'getRootNode')) {
615
            $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...
616
        } else {
617 51
            $node = $treeBuilder->getRootNode();
618
        }
619
620
        $node
621 51
            ->useAttributeAsKey('name')
622 51
            ->prototype('array')
623 51
                ->validate()
624 51
                    ->always()
625
                    ->then(function ($config) {
626 8
                        switch ($config['type']) {
627 8
                            case 'basic':
628 7
                                $this->validateAuthenticationType(['username', 'password'], $config, 'basic');
629
630 7
                                break;
631 2
                            case 'bearer':
632 1
                                $this->validateAuthenticationType(['token'], $config, 'bearer');
633
634 1
                                break;
635 2
                            case 'service':
636 2
                                $this->validateAuthenticationType(['service'], $config, 'service');
637
638 1
                                break;
639 1
                            case 'wsse':
640 1
                                $this->validateAuthenticationType(['username', 'password'], $config, 'wsse');
641
642 1
                                break;
643
                            case 'query_param':
644
                                $this->validateAuthenticationType(['params'], $config, 'query_param');
645
646
                                break;
647
                        }
648
649 7
                        return $config;
650 51
                    })
651 51
                ->end()
652 51
                ->children()
653 51
                    ->enumNode('type')
654 51
                        ->values(['basic', 'bearer', 'wsse', 'service', 'query_param'])
655 51
                        ->isRequired()
656 51
                        ->cannotBeEmpty()
657 51
                    ->end()
658 51
                    ->scalarNode('username')->end()
659 51
                    ->scalarNode('password')->end()
660 51
                    ->scalarNode('token')->end()
661 51
                    ->scalarNode('service')->end()
662 51
                    ->arrayNode('params')->prototype('scalar')->end()
663 51
                    ->end()
664 51
                ->end()
665 51
            ->end(); // End authentication plugin
666
667 51
        return $node;
668
    }
669
670
    /**
671
     * Validate that the configuration fragment has the specified keys and none other.
672
     *
673
     * @param array  $expected Fields that must exist
674
     * @param array  $actual   Actual configuration hashmap
675
     * @param string $authName Name of authentication method for error messages
676
     *
677
     * @throws InvalidConfigurationException If $actual does not have exactly the keys specified in $expected (plus 'type')
678
     */
679 8
    private function validateAuthenticationType(array $expected, array $actual, $authName)
680
    {
681 8
        unset($actual['type']);
682
        // Empty array is always provided, even if the config is not filled.
683 8
        if (empty($actual['params'])) {
684 8
            unset($actual['params']);
685
        }
686 8
        $actual = array_keys($actual);
687 8
        sort($actual);
688 8
        sort($expected);
689
690 8
        if ($expected === $actual) {
691 7
            return;
692
        }
693
694 1
        throw new InvalidConfigurationException(sprintf(
695 1
            'Authentication "%s" requires %s but got %s',
696
            $authName,
697 1
            implode(', ', $expected),
698 1
            implode(', ', $actual)
699
        ));
700
    }
701
702
    /**
703
     * Create configuration for cache plugin.
704
     *
705
     * @return NodeDefinition definition for the cache node in the plugins list
706
     */
707 51
    private function createCachePluginNode()
708
    {
709 51
        $builder = new TreeBuilder('config');
710
        // Keep compatibility with symfony/config < 4.2
711 51
        if (!method_exists($builder, 'getRootNode')) {
712
            $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...
713
        } else {
714 51
            $config = $builder->getRootNode();
715
        }
716
717
        $config
718 51
            ->fixXmlConfig('method')
719 51
            ->fixXmlConfig('respect_response_cache_directive')
720 51
            ->fixXmlConfig('cache_listener')
721 51
            ->addDefaultsIfNotSet()
722 51
            ->validate()
723
                ->ifTrue(function ($config) {
724
                    // Cannot set both respect_cache_headers and respect_response_cache_directives
725 6
                    return isset($config['respect_cache_headers'], $config['respect_response_cache_directives']);
726 51
                })
727 51
                ->thenInvalid('You can\'t provide config option "respect_cache_headers" and "respect_response_cache_directives" simultaniously. Use "respect_response_cache_directives" instead.')
728 51
            ->end()
729 51
            ->children()
730 51
                ->scalarNode('cache_key_generator')
731 51
                    ->info('This must be a service id to a service implementing '.CacheKeyGenerator::class)
732 51
                ->end()
733 51
                ->integerNode('cache_lifetime')
734 51
                    ->info('The minimum time we should store a cache item')
735 51
                ->end()
736 51
                ->integerNode('default_ttl')
737 51
                    ->info('The default max age of a Response')
738 51
                ->end()
739 51
                ->arrayNode('blacklisted_paths')
740 51
                    ->info('An array of regular expression patterns for paths not to be cached. Defaults to an empty array.')
741 51
                    ->defaultValue([])
742 51
                    ->beforeNormalization()
743 51
                        ->castToArray()
744 51
                    ->end()
745 51
                    ->prototype('scalar')
746 51
                        ->validate()
747
                            ->ifTrue(function ($v) {
748 1
                                return false === @preg_match($v, '');
749 51
                            })
750 51
                            ->thenInvalid('Invalid regular expression for a blacklisted path: %s')
751 51
                        ->end()
752 51
                    ->end()
753 51
                ->end()
754 51
                ->enumNode('hash_algo')
755 51
                    ->info('Hashing algorithm to use')
756 51
                    ->values(hash_algos())
757 51
                    ->cannotBeEmpty()
758 51
                ->end()
759 51
                ->arrayNode('methods')
760 51
                    ->info('Which request methods to cache')
761 51
                    ->defaultValue(['GET', 'HEAD'])
762 51
                    ->prototype('scalar')
763 51
                        ->validate()
764
                            ->ifTrue(function ($v) {
765
                                /* RFC7230 sections 3.1.1 and 3.2.6 except limited to uppercase characters. */
766 1
                                return preg_match('/[^A-Z0-9!#$%&\'*+\-.^_`|~]+/', $v);
767 51
                            })
768 51
                            ->thenInvalid('Invalid method: %s')
769 51
                        ->end()
770 51
                    ->end()
771 51
                ->end()
772 51
                ->arrayNode('cache_listeners')
773 51
                    ->info('A list of service ids to act on the response based on the results of the cache check. Must implement '.CacheListener::class.'. Defaults to an empty array.')
774 51
                    ->beforeNormalization()->castToArray()->end()
775 51
                    ->prototype('scalar')
776 51
                    ->end()
777 51
                ->end()
778 51
                ->scalarNode('respect_cache_headers')
779 51
                    ->info('Whether we should care about cache headers or not [DEPRECATED]')
780 51
                    ->beforeNormalization()
781
                        ->always(function ($v) {
782 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);
783
784 3
                            return $v;
785 51
                        })
786 51
                    ->end()
787 51
                    ->validate()
788 51
                        ->ifNotInArray([null, true, false])
789 51
                        ->thenInvalid('Value for "respect_cache_headers" must be null or boolean')
790 51
                    ->end()
791 51
                ->end()
792 51
                ->variableNode('respect_response_cache_directives')
793 51
                    ->info('A list of cache directives to respect when caching responses')
794 51
                    ->validate()
795
                        ->always(function ($v) {
796 2
                            if (is_null($v) || is_array($v)) {
797 2
                                return $v;
798
                            }
799
800
                            throw new InvalidTypeException();
801 51
                        })
802 51
                    ->end()
803 51
                ->end()
804 51
            ->end()
805
        ;
806
807 51
        $treeBuilder = new TreeBuilder('cache');
808
        // Keep compatibility with symfony/config < 4.2
809 51
        if (!method_exists($treeBuilder, 'getRootNode')) {
810
            $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...
811
        } else {
812 51
            $cache = $treeBuilder->getRootNode();
813
        }
814
815
        $cache
816 51
            ->canBeEnabled()
817 51
            ->info('Configure HTTP caching, requires the php-http/cache-plugin package')
818 51
            ->addDefaultsIfNotSet()
819 51
            ->validate()
820
                ->ifTrue(function ($v) {
821 6
                    return !empty($v['enabled']) && !class_exists(CachePlugin::class);
822 51
                })
823 51
                ->thenInvalid('To use the cache plugin, you need to require php-http/cache-plugin in your project')
824 51
            ->end()
825 51
            ->children()
826 51
                ->scalarNode('cache_pool')
827 51
                    ->info('This must be a service id to a service implementing '.CacheItemPoolInterface::class)
828 51
                    ->isRequired()
829 51
                    ->cannotBeEmpty()
830 51
                ->end()
831 51
                ->scalarNode('stream_factory')
832 51
                    ->info('This must be a service id to a service implementing '.StreamFactory::class)
833 51
                    ->defaultValue('httplug.stream_factory')
834 51
                    ->cannotBeEmpty()
835 51
                ->end()
836 51
            ->end()
837 51
            ->append($config)
838
        ;
839
840 51
        return $cache;
841
    }
842
}
843