Completed
Push — master ( 0c70e2...060b9b )
by David
05:33 queued 10s
created

Configuration::addSharedPluginNodes()   B

Complexity

Conditions 6
Paths 32

Size

Total Lines 91

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 73
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 91
ccs 73
cts 73
cp 1
rs 7.5741
c 0
b 0
f 0
cc 6
nc 32
nop 2
crap 6

How to fix   Long Method   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
namespace Http\HttplugBundle\DependencyInjection;
4
5
use 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('psr18_client')->defaultValue('httplug.psr18_client.default')->end()
124 50
                        ->scalarNode('message_factory')->defaultValue('httplug.message_factory.default')->end()
125 50
                        ->scalarNode('uri_factory')->defaultValue('httplug.uri_factory.default')->end()
126 50
                        ->scalarNode('stream_factory')->defaultValue('httplug.stream_factory.default')->end()
127 50
                        ->scalarNode('psr17_request_factory')->defaultValue('httplug.psr17_request_factory.default')->end()
128 50
                        ->scalarNode('psr17_response_factory')->defaultValue('httplug.psr17_response_factory.default')->end()
129 50
                        ->scalarNode('psr17_stream_factory')->defaultValue('httplug.psr17_stream_factory.default')->end()
130 50
                        ->scalarNode('psr17_uri_factory')->defaultValue('httplug.psr17_uri_factory.default')->end()
131 50
                        ->scalarNode('psr17_uploaded_file_factory')->defaultValue('httplug.psr17_uploaded_file_factory.default')->end()
132 50
                        ->scalarNode('psr17_server_request_factory')->defaultValue('httplug.psr17_server_request_factory.default')->end()
133 50
                    ->end()
134 50
                ->end()
135 50
                ->arrayNode('classes')
136 50
                    ->addDefaultsIfNotSet()
137 50
                    ->info('Overwrite a service class instead of using the discovery mechanism.')
138 50
                    ->children()
139 50
                        ->scalarNode('client')->defaultNull()->end()
140 50
                        ->scalarNode('psr18_client')->defaultNull()->end()
141 50
                        ->scalarNode('message_factory')->defaultNull()->end()
142 50
                        ->scalarNode('uri_factory')->defaultNull()->end()
143 50
                        ->scalarNode('stream_factory')->defaultNull()->end()
144 50
                        ->scalarNode('psr17_request_factory')->defaultNull()->end()
145 50
                        ->scalarNode('psr17_response_factory')->defaultNull()->end()
146 50
                        ->scalarNode('psr17_stream_factory')->defaultNull()->end()
147 50
                        ->scalarNode('psr17_uri_factory')->defaultNull()->end()
148 50
                        ->scalarNode('psr17_uploaded_file_factory')->defaultNull()->end()
149 50
                        ->scalarNode('psr17_server_request_factory')->defaultNull()->end()
150 50
                    ->end()
151 50
                ->end()
152 50
                ->arrayNode('profiling')
153 50
                    ->addDefaultsIfNotSet()
154 50
                    ->treatFalseLike(['enabled' => false])
155 50
                    ->treatTrueLike(['enabled' => true])
156 50
                    ->treatNullLike(['enabled' => $this->debug])
157 50
                    ->info('Extend the debug profiler with information about requests.')
158 50
                    ->children()
159 50
                        ->booleanNode('enabled')
160 50
                            ->info('Turn the toolbar on or off. Defaults to kernel debug mode.')
161 50
                            ->defaultValue($this->debug)
162 50
                        ->end()
163 50
                        ->scalarNode('formatter')->defaultNull()->end()
164 50
                        ->scalarNode('captured_body_length')
165 50
                            ->validate()
166
                                ->ifTrue(function ($v) {
167 4
                                    return null !== $v && !is_int($v);
168 50
                                })
169 50
                                ->thenInvalid('The child node "captured_body_length" at path "httplug.profiling" must be an integer or null ("%s" given).')
170 50
                            ->end()
171 50
                            ->defaultValue(0)
172 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).')
173 50
                        ->end()
174 50
                    ->end()
175 50
                ->end()
176 50
                ->arrayNode('discovery')
177 50
                    ->addDefaultsIfNotSet()
178 50
                    ->info('Control what clients should be found by the discovery.')
179 50
                    ->children()
180 50
                        ->scalarNode('client')
181 50
                            ->defaultValue('auto')
182 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.')
183 50
                        ->end()
184 50
                        ->scalarNode('async_client')
185 50
                            ->defaultNull()
186 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.')
187 50
                        ->end()
188 50
                    ->end()
189 50
                ->end()
190 50
            ->end();
191
192 50
        return $treeBuilder;
193
    }
194
195 50
    private function configureClients(ArrayNodeDefinition $root)
196
    {
197 50
        $root->children()
198 50
            ->arrayNode('clients')
199 50
                ->useAttributeAsKey('name')
200 50
                ->prototype('array')
201 50
                ->fixXmlConfig('plugin')
202 50
                ->validate()
203
                    ->ifTrue(function ($config) {
204
                        // Make sure we only allow one of these to be true
205 27
                        return (bool) $config['flexible_client'] + (bool) $config['http_methods_client'] + (bool) $config['batch_client'] >= 2;
206 50
                    })
207 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")')
208 50
                ->end()
209 50
                ->validate()
210
                    ->ifTrue(function ($config) {
211 27
                        return 'httplug.factory.auto' === $config['factory'] && !empty($config['config']);
212 50
                    })
213 50
                    ->thenInvalid('If you want to use the "config" key you must also specify a valid "factory".')
214 50
                ->end()
215 50
                ->validate()
216
                    ->ifTrue(function ($config) {
217 27
                        return !empty($config['service']) && ('httplug.factory.auto' !== $config['factory'] || !empty($config['config']));
218 50
                    })
219 50
                    ->thenInvalid('If you want to use the "service" key you cannot specify "factory" or "config".')
220 50
                ->end()
221 50
                ->children()
222 50
                    ->scalarNode('factory')
223 50
                        ->defaultValue('httplug.factory.auto')
224 50
                        ->cannotBeEmpty()
225 50
                        ->info('The service id of a factory to use when creating the adapter.')
226 50
                    ->end()
227 50
                    ->scalarNode('service')
228 50
                        ->defaultNull()
229 50
                        ->info('The service id of the client to use.')
230 50
                    ->end()
231 50
                    ->booleanNode('public')
232 50
                        ->defaultNull()
233 50
                        ->info('Set to true if you really cannot use dependency injection and need to make the client service public.')
234 50
                    ->end()
235 50
                    ->booleanNode('flexible_client')
236 50
                        ->defaultFalse()
237 50
                        ->info('Set to true to get the client wrapped in a FlexibleHttpClient which emulates async or sync behavior.')
238 50
                    ->end()
239 50
                    ->booleanNode('http_methods_client')
240 50
                        ->defaultFalse()
241 50
                        ->info('Set to true to get the client wrapped in a HttpMethodsClient which emulates provides functions for HTTP verbs.')
242 50
                    ->end()
243 50
                    ->booleanNode('batch_client')
244 50
                        ->defaultFalse()
245 50
                        ->info('Set to true to get the client wrapped in a BatchClient which allows you to send multiple request at the same time.')
246 50
                    ->end()
247 50
                    ->variableNode('config')->defaultValue([])->end()
248 50
                    ->append($this->createClientPluginNode())
249 50
                ->end()
250 50
            ->end()
251 50
        ->end();
252 50
    }
253
254
    /**
255
     * @param ArrayNodeDefinition $root
256
     */
257 50
    private function configureSharedPlugins(ArrayNodeDefinition $root)
258
    {
259
        $pluginsNode = $root
260 50
            ->children()
261 50
                ->arrayNode('plugins')
262 50
                ->info('Global plugin configuration. Plugins need to be explicitly added to clients.')
263 50
                ->addDefaultsIfNotSet()
264
            // don't call end to get the plugins node
265
        ;
266 50
        $this->addSharedPluginNodes($pluginsNode);
267 50
    }
268
269
    /**
270
     * Createplugins node of a client.
271
     *
272
     * @return ArrayNodeDefinition The plugin node
273
     */
274 50
    private function createClientPluginNode()
275
    {
276 50
        $treeBuilder = new TreeBuilder('plugins');
277
        // Keep compatibility with symfony/config < 4.2
278 50
        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 50
            $node = $treeBuilder->getRootNode();
282
        }
283
284
        /** @var ArrayNodeDefinition $pluginList */
285
        $pluginList = $node
286 50
            ->info('A list of plugin service ids and client specific plugin definitions. The order is important.')
287 50
            ->prototype('array')
288
        ;
289
        $pluginList
290
            // support having just a service id in the list
291 50
            ->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 50
                })
304 50
            ->end()
305
306 50
            ->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 50
                })
320 50
            ->end()
321
        ;
322 50
        $this->addSharedPluginNodes($pluginList, true);
323
324
        $pluginList
325 50
            ->children()
326 50
                ->arrayNode('reference')
327 50
                    ->canBeEnabled()
328 50
                    ->info('Reference to a plugin service')
329 50
                    ->children()
330 50
                        ->scalarNode('id')
331 50
                            ->info('Service id of a plugin')
332 50
                            ->isRequired()
333 50
                            ->cannotBeEmpty()
334 50
                        ->end()
335 50
                    ->end()
336 50
                ->end()
337 50
                ->arrayNode('add_host')
338 50
                    ->canBeEnabled()
339 50
                    ->addDefaultsIfNotSet()
340 50
                    ->info('Set scheme, host and port in the request URI.')
341 50
                    ->children()
342 50
                        ->scalarNode('host')
343 50
                            ->info('Host name including protocol and optionally the port number, e.g. https://api.local:8000')
344 50
                            ->isRequired()
345 50
                            ->cannotBeEmpty()
346 50
                        ->end()
347 50
                        ->scalarNode('replace')
348 50
                            ->info('Whether to replace the host if request already specifies one')
349 50
                            ->defaultValue(false)
350 50
                        ->end()
351 50
                    ->end()
352 50
                ->end()
353 50
                ->arrayNode('add_path')
354 50
                    ->canBeEnabled()
355 50
                    ->addDefaultsIfNotSet()
356 50
                    ->info('Add a base path to the request.')
357 50
                    ->children()
358 50
                        ->scalarNode('path')
359 50
                            ->info('Path to be added, e.g. /api/v1')
360 50
                            ->isRequired()
361 50
                            ->cannotBeEmpty()
362 50
                        ->end()
363 50
                    ->end()
364 50
                ->end()
365 50
                ->arrayNode('base_uri')
366 50
                    ->canBeEnabled()
367 50
                    ->addDefaultsIfNotSet()
368 50
                    ->info('Set a base URI to the request.')
369 50
                    ->children()
370 50
                        ->scalarNode('uri')
371 50
                            ->info('Base Uri including protocol, optionally the port number and prepend path, e.g. https://api.local:8000/api')
372 50
                            ->isRequired()
373 50
                            ->cannotBeEmpty()
374 50
                        ->end()
375 50
                        ->scalarNode('replace')
376 50
                            ->info('Whether to replace the host if request already specifies one')
377 50
                            ->defaultValue(false)
378 50
                        ->end()
379 50
                    ->end()
380 50
                ->end()
381 50
                ->arrayNode('content_type')
382 50
                    ->canBeEnabled()
383 50
                    ->info('Detect the content type of a request body and set the Content-Type header if it is not already set.')
384 50
                    ->children()
385 50
                        ->booleanNode('skip_detection')
386 50
                            ->info('Whether to skip detection when request body is larger than size_limit')
387 50
                            ->defaultFalse()
388 50
                        ->end()
389 50
                        ->scalarNode('size_limit')
390 50
                            ->info('Skip content type detection if request body is larger than size_limit bytes')
391 50
                        ->end()
392 50
                    ->end()
393 50
                ->end()
394 50
                ->arrayNode('header_append')
395 50
                    ->canBeEnabled()
396 50
                    ->info('Append headers to the request. If the header already exists the value will be appended to the current value.')
397 50
                    ->fixXmlConfig('header')
398 50
                    ->children()
399 50
                        ->arrayNode('headers')
400 50
                            ->info('Keys are the header names, values the header values')
401 50
                            ->normalizeKeys(false)
402 50
                            ->useAttributeAsKey('name')
403 50
                            ->prototype('scalar')->end()
404 50
                        ->end()
405 50
                    ->end()
406 50
                ->end()
407 50
                ->arrayNode('header_defaults')
408 50
                    ->canBeEnabled()
409 50
                    ->info('Set header to default value if it does not exist.')
410 50
                    ->fixXmlConfig('header')
411 50
                    ->children()
412 50
                        ->arrayNode('headers')
413 50
                            ->info('Keys are the header names, values the header values')
414 50
                            ->normalizeKeys(false)
415 50
                            ->useAttributeAsKey('name')
416 50
                            ->prototype('scalar')->end()
417 50
                        ->end()
418 50
                    ->end()
419 50
                ->end()
420 50
                ->arrayNode('header_set')
421 50
                    ->canBeEnabled()
422 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.')
423 50
                    ->fixXmlConfig('header')
424 50
                    ->children()
425 50
                        ->arrayNode('headers')
426 50
                            ->info('Keys are the header names, values the header values')
427 50
                            ->normalizeKeys(false)
428 50
                            ->useAttributeAsKey('name')
429 50
                            ->prototype('scalar')->end()
430 50
                        ->end()
431 50
                    ->end()
432 50
                ->end()
433 50
                ->arrayNode('header_remove')
434 50
                    ->canBeEnabled()
435 50
                    ->info('Remove headers from requests.')
436 50
                    ->fixXmlConfig('header')
437 50
                    ->children()
438 50
                        ->arrayNode('headers')
439 50
                            ->info('List of header names to remove')
440 50
                            ->prototype('scalar')->end()
441 50
                        ->end()
442 50
                    ->end()
443 50
                ->end()
444 50
                ->arrayNode('query_defaults')
445 50
                    ->canBeEnabled()
446 50
                    ->info('Sets query parameters to default value if they are not present in the request.')
447 50
                    ->fixXmlConfig('parameter')
448 50
                    ->children()
449 50
                        ->arrayNode('parameters')
450 50
                            ->info('List of query parameters. Names and values must not be url encoded as the plugin will encode them.')
451 50
                            ->normalizeKeys(false)
452 50
                            ->useAttributeAsKey('name')
453 50
                            ->prototype('scalar')->end()
454 50
                        ->end()
455 50
                    ->end()
456 50
                ->end()
457 50
                ->arrayNode('vcr')
458 50
                    ->canBeEnabled()
459 50
                    ->addDefaultsIfNotSet()
460 50
                    ->info('Record response to be replayed during tests or development cycle.')
461 50
                    ->validate()
462
                        ->ifTrue(function ($config) {
463 5
                            return 'filesystem' === $config['recorder'] && empty($config['fixtures_directory']);
464 50
                        })
465 50
                        ->thenInvalid('If you want to use the "filesystem" recorder you must also specify a "fixtures_directory".')
466 50
                    ->end()
467 50
                    ->children()
468 50
                        ->enumNode('mode')
469 50
                        ->info('What should be the behavior of the plugin?')
470 50
                        ->values(['record', 'replay', 'replay_or_record'])
471 50
                        ->isRequired()
472 50
                        ->cannotBeEmpty()
473 50
                    ->end()
474 50
                    ->scalarNode('recorder')
475 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))
476 50
                        ->defaultValue('filesystem')
477 50
                        ->cannotBeEmpty()
478 50
                    ->end()
479 50
                    ->scalarNode('naming_strategy')
480 50
                        ->info(sprintf('Which naming strategy to use. Add the ID of your service implementing %s to override the default one.', NamingStrategyInterface::class))
481 50
                        ->defaultValue('default')
482 50
                        ->cannotBeEmpty()
483 50
                    ->end()
484 50
                    ->arrayNode('naming_strategy_options')
485 50
                        ->info('See http://docs.php-http.org/en/latest/plugins/vcr.html#the-naming-strategy for more details')
486 50
                        ->children()
487 50
                            ->arrayNode('hash_headers')
488 50
                                ->info('List of header(s) that make the request unique (Ex: ‘Authorization’)')
489 50
                                ->prototype('scalar')->end()
490 50
                            ->end()
491 50
                            ->arrayNode('hash_body_methods')
492 50
                                ->info('for which request methods the body makes requests distinct.')
493 50
                                ->prototype('scalar')->end()
494 50
                            ->end()
495 50
                        ->end()
496 50
                    ->end() // End naming_strategy_options
497 50
                    ->scalarNode('fixtures_directory')
498 50
                        ->info('Where the responses will be stored and replay from when using the filesystem recorder. Should be accessible to your VCS.')
499 50
                    ->end()
500 50
                ->end()
501 50
            ->end()
502 50
        ->end();
503
504 50
        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 50
    private function addSharedPluginNodes(ArrayNodeDefinition $pluginNode, $disableAll = false)
514
    {
515 50
        $children = $pluginNode->children();
516
517 50
        $children->append($this->createAuthenticationPluginNode());
518 50
        $children->append($this->createCachePluginNode());
519
520
        $children
521 50
            ->arrayNode('cookie')
522 50
                ->canBeEnabled()
523 50
                ->children()
524 50
                    ->scalarNode('cookie_jar')
525 50
                        ->info('This must be a service id to a service implementing '.CookieJar::class)
526 50
                        ->isRequired()
527 50
                        ->cannotBeEmpty()
528 50
                    ->end()
529 50
                ->end()
530 50
            ->end();
531
        // End cookie plugin
532
533
        $children
534 50
            ->arrayNode('history')
535 50
                ->canBeEnabled()
536 50
                ->children()
537 50
                    ->scalarNode('journal')
538 50
                        ->info('This must be a service id to a service implementing '.Journal::class)
539 50
                        ->isRequired()
540 50
                        ->cannotBeEmpty()
541 50
                    ->end()
542 50
                ->end()
543 50
            ->end();
544
        // End history plugin
545
546 50
        $decoder = $children->arrayNode('decoder');
547 50
        $disableAll ? $decoder->canBeEnabled() : $decoder->canBeDisabled();
548 50
        $decoder->addDefaultsIfNotSet()
549 50
            ->children()
550 50
                ->scalarNode('use_content_encoding')->defaultTrue()->end()
551 50
            ->end()
552 50
        ->end();
553
        // End decoder plugin
554
555 50
        $logger = $children->arrayNode('logger');
556 50
        $disableAll ? $logger->canBeEnabled() : $logger->canBeDisabled();
557 50
        $logger->addDefaultsIfNotSet()
558 50
            ->children()
559 50
                ->scalarNode('logger')
560 50
                    ->info('This must be a service id to a service implementing '.LoggerInterface::class)
561 50
                    ->defaultValue('logger')
562 50
                    ->cannotBeEmpty()
563 50
                ->end()
564 50
                ->scalarNode('formatter')
565 50
                    ->info('This must be a service id to a service implementing '.Formatter::class)
566 50
                    ->defaultNull()
567 50
                ->end()
568 50
            ->end()
569 50
        ->end();
570
        // End logger plugin
571
572 50
        $redirect = $children->arrayNode('redirect');
573 50
        $disableAll ? $redirect->canBeEnabled() : $redirect->canBeDisabled();
574 50
        $redirect->addDefaultsIfNotSet()
575 50
            ->children()
576 50
                ->scalarNode('preserve_header')->defaultTrue()->end()
577 50
                ->scalarNode('use_default_for_multiple')->defaultTrue()->end()
578 50
            ->end()
579 50
        ->end();
580
        // End redirect plugin
581
582 50
        $retry = $children->arrayNode('retry');
583 50
        $disableAll ? $retry->canBeEnabled() : $retry->canBeDisabled();
584 50
        $retry->addDefaultsIfNotSet()
585 50
            ->children()
586 50
                ->scalarNode('retry')->defaultValue(1)->end() // TODO: should be called retries for consistency with the class
587 50
            ->end()
588 50
        ->end();
589
        // End retry plugin
590
591 50
        $stopwatch = $children->arrayNode('stopwatch');
592 50
        $disableAll ? $stopwatch->canBeEnabled() : $stopwatch->canBeDisabled();
593 50
        $stopwatch->addDefaultsIfNotSet()
594 50
            ->children()
595 50
                ->scalarNode('stopwatch')
596 50
                    ->info('This must be a service id to a service extending Symfony\Component\Stopwatch\Stopwatch')
597 50
                    ->defaultValue('debug.stopwatch')
598 50
                    ->cannotBeEmpty()
599 50
                ->end()
600 50
            ->end()
601 50
        ->end();
602
        // End stopwatch plugin
603 50
    }
604
605
    /**
606
     * Create configuration for authentication plugin.
607
     *
608
     * @return NodeDefinition definition for the authentication node in the plugins list
609
     */
610 50
    private function createAuthenticationPluginNode()
611
    {
612 50
        $treeBuilder = new TreeBuilder('authentication');
613
        // Keep compatibility with symfony/config < 4.2
614 50
        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 50
            $node = $treeBuilder->getRootNode();
618
        }
619
620
        $node
621 50
            ->useAttributeAsKey('name')
622 50
            ->prototype('array')
623 50
                ->validate()
624 50
                    ->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 50
                    })
651 50
                ->end()
652 50
                ->children()
653 50
                    ->enumNode('type')
654 50
                        ->values(['basic', 'bearer', 'wsse', 'service', 'query_param'])
655 50
                        ->isRequired()
656 50
                        ->cannotBeEmpty()
657 50
                    ->end()
658 50
                    ->scalarNode('username')->end()
659 50
                    ->scalarNode('password')->end()
660 50
                    ->scalarNode('token')->end()
661 50
                    ->scalarNode('service')->end()
662 50
                    ->arrayNode('params')->prototype('scalar')->end()
663 50
                    ->end()
664 50
                ->end()
665 50
            ->end(); // End authentication plugin
666
667 50
        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 50
    private function createCachePluginNode()
708
    {
709 50
        $builder = new TreeBuilder('config');
710
        // Keep compatibility with symfony/config < 4.2
711 50
        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 50
            $config = $builder->getRootNode();
715
        }
716
717
        $config
718 50
            ->fixXmlConfig('method')
719 50
            ->fixXmlConfig('respect_response_cache_directive')
720 50
            ->addDefaultsIfNotSet()
721 50
            ->validate()
722
                ->ifTrue(function ($config) {
723
                    // Cannot set both respect_cache_headers and respect_response_cache_directives
724 5
                    return isset($config['respect_cache_headers'], $config['respect_response_cache_directives']);
725 50
                })
726 50
                ->thenInvalid('You can\'t provide config option "respect_cache_headers" and "respect_response_cache_directives" simultaniously. Use "respect_response_cache_directives" instead.')
727 50
            ->end()
728 50
            ->children()
729 50
                ->scalarNode('cache_key_generator')
730 50
                    ->info('This must be a service id to a service implementing '.CacheKeyGenerator::class)
731 50
                ->end()
732 50
                ->integerNode('cache_lifetime')
733 50
                    ->info('The minimum time we should store a cache item')
734 50
                ->end()
735 50
                ->integerNode('default_ttl')
736 50
                    ->info('The default max age of a Response')
737 50
                ->end()
738 50
                ->arrayNode('blacklisted_paths')
739 50
                    ->info('An array of regular expression patterns for paths not to be cached. Defaults to an empty array.')
740 50
                    ->defaultValue([])
741 50
                    ->beforeNormalization()
742 50
                        ->castToArray()
743 50
                    ->end()
744 50
                    ->prototype('scalar')
745 50
                        ->validate()
746
                            ->ifTrue(function ($v) {
747 1
                                return false === @preg_match($v, '');
748 50
                            })
749 50
                            ->thenInvalid('Invalid regular expression for a blacklisted path: %s')
750 50
                        ->end()
751 50
                    ->end()
752 50
                ->end()
753 50
                ->enumNode('hash_algo')
754 50
                    ->info('Hashing algorithm to use')
755 50
                    ->values(hash_algos())
756 50
                    ->cannotBeEmpty()
757 50
                ->end()
758 50
                ->arrayNode('methods')
759 50
                    ->info('Which request methods to cache')
760 50
                    ->defaultValue(['GET', 'HEAD'])
761 50
                    ->prototype('scalar')
762 50
                        ->validate()
763
                            ->ifTrue(function ($v) {
764
                                /* RFC7230 sections 3.1.1 and 3.2.6 except limited to uppercase characters. */
765 1
                                return preg_match('/[^A-Z0-9!#$%&\'*+\-.^_`|~]+/', $v);
766 50
                            })
767 50
                            ->thenInvalid('Invalid method: %s')
768 50
                        ->end()
769 50
                    ->end()
770 50
                ->end()
771 50
                ->scalarNode('respect_cache_headers')
772 50
                    ->info('Whether we should care about cache headers or not [DEPRECATED]')
773 50
                    ->beforeNormalization()
774
                        ->always(function ($v) {
775 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);
776
777 3
                            return $v;
778 50
                        })
779 50
                    ->end()
780 50
                    ->validate()
781 50
                        ->ifNotInArray([null, true, false])
782 50
                        ->thenInvalid('Value for "respect_cache_headers" must be null or boolean')
783 50
                    ->end()
784 50
                ->end()
785 50
                ->variableNode('respect_response_cache_directives')
786 50
                    ->info('A list of cache directives to respect when caching responses')
787 50
                    ->validate()
788
                        ->always(function ($v) {
789 2
                            if (is_null($v) || is_array($v)) {
790 2
                                return $v;
791
                            }
792
793
                            throw new InvalidTypeException();
794 50
                        })
795 50
                    ->end()
796 50
                ->end()
797 50
            ->end()
798
        ;
799
800 50
        $treeBuilder = new TreeBuilder('cache');
801
        // Keep compatibility with symfony/config < 4.2
802 50
        if (!method_exists($treeBuilder, 'getRootNode')) {
803
            $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...
804
        } else {
805 50
            $cache = $treeBuilder->getRootNode();
806
        }
807
808
        $cache
809 50
            ->canBeEnabled()
810 50
            ->info('Configure HTTP caching, requires the php-http/cache-plugin package')
811 50
            ->addDefaultsIfNotSet()
812 50
            ->validate()
813
                ->ifTrue(function ($v) {
814 5
                    return !empty($v['enabled']) && !class_exists(CachePlugin::class);
815 50
                })
816 50
                ->thenInvalid('To use the cache plugin, you need to require php-http/cache-plugin in your project')
817 50
            ->end()
818 50
            ->children()
819 50
                ->scalarNode('cache_pool')
820 50
                    ->info('This must be a service id to a service implementing '.CacheItemPoolInterface::class)
821 50
                    ->isRequired()
822 50
                    ->cannotBeEmpty()
823 50
                ->end()
824 50
                ->scalarNode('stream_factory')
825 50
                    ->info('This must be a service id to a service implementing '.StreamFactory::class)
826 50
                    ->defaultValue('httplug.stream_factory')
827 50
                    ->cannotBeEmpty()
828 50
                ->end()
829 50
            ->end()
830 50
            ->append($config)
831
        ;
832
833 50
        return $cache;
834
    }
835
}
836