Completed
Push — master ( 57db8a...f718e5 )
by Tobias
06:48
created

Configuration::getConfigTreeBuilder()   C

Complexity

Conditions 13
Paths 2

Size

Total Lines 116

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 93
CRAP Score 13.0002

Importance

Changes 0
Metric Value
dl 0
loc 116
ccs 93
cts 94
cp 0.9894
rs 5.2933
c 0
b 0
f 0
cc 13
nc 2
nop 0
crap 13.0002

How to fix   Long Method    Complexity   

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

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
61 37
        $this->configureSharedPlugins($rootNode);
0 ignored issues
show
Compatibility introduced by
$rootNode of type object<Symfony\Component...Builder\NodeDefinition> is not a sub-type of object<Symfony\Component...er\ArrayNodeDefinition>. It seems like you assume a child class of the class Symfony\Component\Config...\Builder\NodeDefinition to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

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

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
258 37
            ->info('A list of plugin service ids and client specific plugin definitions. The order is important.')
259 37
            ->prototype('array')
260
        ;
261
        $pluginList
262
            // support having just a service id in the list
263 37
            ->beforeNormalization()
264 37
                ->always(function ($plugin) {
265 11
                    if (is_string($plugin)) {
266
                        return [
267
                            'reference' => [
268 9
                                'enabled' => true,
269 9
                                'id' => $plugin,
270
                            ],
271
                        ];
272
                    }
273
274 8
                    return $plugin;
275 37
                })
276 37
            ->end()
277
278 37
            ->validate()
279 37
                ->always(function ($plugins) {
280 9
                    foreach ($plugins as $name => $definition) {
281 9
                        if ('authentication' === $name) {
282 9
                            if (!count($definition)) {
283 9
                                unset($plugins['authentication']);
284
                            }
285 9
                        } elseif (!$definition['enabled']) {
286 9
                            unset($plugins[$name]);
287
                        }
288
                    }
289
290 9
                    return $plugins;
291 37
                })
292 37
            ->end()
293
        ;
294 37
        $this->addSharedPluginNodes($pluginList, true);
295
296
        $pluginList
297 37
            ->children()
298 37
                ->arrayNode('reference')
299 37
                    ->canBeEnabled()
300 37
                    ->info('Reference to a plugin service')
301 37
                    ->children()
302 37
                        ->scalarNode('id')
303 37
                            ->info('Service id of a plugin')
304 37
                            ->isRequired()
305 37
                            ->cannotBeEmpty()
306 37
                        ->end()
307 37
                    ->end()
308 37
                ->end()
309 37
                ->arrayNode('add_host')
310 37
                    ->canBeEnabled()
311 37
                    ->addDefaultsIfNotSet()
312 37
                    ->info('Set scheme, host and port in the request URI.')
313 37
                    ->children()
314 37
                        ->scalarNode('host')
315 37
                            ->info('Host name including protocol and optionally the port number, e.g. https://api.local:8000')
316 37
                            ->isRequired()
317 37
                            ->cannotBeEmpty()
318 37
                        ->end()
319 37
                        ->scalarNode('replace')
320 37
                            ->info('Whether to replace the host if request already specifies one')
321 37
                            ->defaultValue(false)
322 37
                        ->end()
323 37
                    ->end()
324 37
                ->end()
325 37
                ->arrayNode('add_path')
326 37
                    ->canBeEnabled()
327 37
                    ->addDefaultsIfNotSet()
328 37
                    ->info('Add a base path to the request.')
329 37
                    ->children()
330 37
                        ->scalarNode('path')
331 37
                            ->info('Path to be added, e.g. /api/v1')
332 37
                            ->isRequired()
333 37
                            ->cannotBeEmpty()
334 37
                        ->end()
335 37
                    ->end()
336 37
                ->end()
337 37
                ->arrayNode('base_uri')
338 37
                    ->canBeEnabled()
339 37
                    ->addDefaultsIfNotSet()
340 37
                    ->info('Set a base URI to the request.')
341 37
                    ->children()
342 37
                        ->scalarNode('uri')
343 37
                            ->info('Base Uri including protocol, optionally the port number and prepend path, e.g. https://api.local:8000/api')
344 37
                            ->isRequired()
345 37
                            ->cannotBeEmpty()
346 37
                        ->end()
347 37
                        ->scalarNode('replace')
348 37
                            ->info('Whether to replace the host if request already specifies one')
349 37
                            ->defaultValue(false)
350 37
                        ->end()
351 37
                    ->end()
352 37
                ->end()
353 37
                ->arrayNode('header_append')
354 37
                    ->canBeEnabled()
355 37
                    ->info('Append headers to the request. If the header already exists the value will be appended to the current value.')
356 37
                    ->fixXmlConfig('header')
357 37
                    ->children()
358 37
                        ->arrayNode('headers')
359 37
                            ->info('Keys are the header names, values the header values')
360 37
                            ->normalizeKeys(false)
361 37
                            ->useAttributeAsKey('name')
362 37
                            ->prototype('scalar')->end()
363 37
                        ->end()
364 37
                    ->end()
365 37
                ->end()
366 37
                ->arrayNode('header_defaults')
367 37
                    ->canBeEnabled()
368 37
                    ->info('Set header to default value if it does not exist.')
369 37
                    ->fixXmlConfig('header')
370 37
                    ->children()
371 37
                        ->arrayNode('headers')
372 37
                            ->info('Keys are the header names, values the header values')
373 37
                            ->normalizeKeys(false)
374 37
                            ->useAttributeAsKey('name')
375 37
                            ->prototype('scalar')->end()
376 37
                        ->end()
377 37
                    ->end()
378 37
                ->end()
379 37
                ->arrayNode('header_set')
380 37
                    ->canBeEnabled()
381 37
                    ->info('Set headers to requests. If the header does not exist it wil be set, if the header already exists it will be replaced.')
382 37
                    ->fixXmlConfig('header')
383 37
                    ->children()
384 37
                        ->arrayNode('headers')
385 37
                            ->info('Keys are the header names, values the header values')
386 37
                            ->normalizeKeys(false)
387 37
                            ->useAttributeAsKey('name')
388 37
                            ->prototype('scalar')->end()
389 37
                        ->end()
390 37
                    ->end()
391 37
                ->end()
392 37
                ->arrayNode('header_remove')
393 37
                    ->canBeEnabled()
394 37
                    ->info('Remove headers from requests.')
395 37
                    ->fixXmlConfig('header')
396 37
                    ->children()
397 37
                        ->arrayNode('headers')
398 37
                            ->info('List of header names to remove')
399 37
                            ->prototype('scalar')->end()
400 37
                        ->end()
401 37
                    ->end()
402 37
                ->end()
403 37
                ->arrayNode('query_defaults')
404 37
                    ->canBeEnabled()
405 37
                    ->info('Sets query parameters to default value if they are not present in the request.')
406 37
                    ->fixXmlConfig('parameter')
407 37
                    ->children()
408 37
                        ->arrayNode('parameters')
409 37
                            ->info('List of query parameters. Names and values must not be url encoded as the plugin will encode them.')
410 37
                            ->normalizeKeys(false)
411 37
                            ->useAttributeAsKey('name')
412 37
                            ->prototype('scalar')->end()
413 37
                        ->end()
414 37
                    ->end()
415 37
                ->end()
416 37
            ->end()
417 37
        ->end();
418
419 37
        return $node;
420
    }
421
422
    /**
423
     * Add the definitions for shared plugin configurations.
424
     *
425
     * @param ArrayNodeDefinition $pluginNode the node to add to
426
     * @param bool                $disableAll Some shared plugins are enabled by default. On the client, all are disabled by default.
427
     */
428 37
    private function addSharedPluginNodes(ArrayNodeDefinition $pluginNode, $disableAll = false)
429
    {
430 37
        $children = $pluginNode->children();
431
432 37
        $children->append($this->createAuthenticationPluginNode());
433 37
        $children->append($this->createCachePluginNode());
434
435
        $children
436 37
            ->arrayNode('cookie')
437 37
                ->canBeEnabled()
438 37
                ->children()
439 37
                    ->scalarNode('cookie_jar')
440 37
                        ->info('This must be a service id to a service implementing '.CookieJar::class)
441 37
                        ->isRequired()
442 37
                        ->cannotBeEmpty()
443 37
                    ->end()
444 37
                ->end()
445 37
            ->end();
446
        // End cookie plugin
447
448
        $children
449 37
            ->arrayNode('history')
450 37
                ->canBeEnabled()
451 37
                ->children()
452 37
                    ->scalarNode('journal')
453 37
                        ->info('This must be a service id to a service implementing '.Journal::class)
454 37
                        ->isRequired()
455 37
                        ->cannotBeEmpty()
456 37
                    ->end()
457 37
                ->end()
458 37
            ->end();
459
        // End history plugin
460
461 37
        $decoder = $children->arrayNode('decoder');
462 37
        $disableAll ? $decoder->canBeEnabled() : $decoder->canBeDisabled();
463 37
        $decoder->addDefaultsIfNotSet()
464 37
            ->children()
465 37
                ->scalarNode('use_content_encoding')->defaultTrue()->end()
466 37
            ->end()
467 37
        ->end();
468
        // End decoder plugin
469
470 37
        $logger = $children->arrayNode('logger');
471 37
        $disableAll ? $logger->canBeEnabled() : $logger->canBeDisabled();
472 37
        $logger->addDefaultsIfNotSet()
473 37
            ->children()
474 37
                ->scalarNode('logger')
475 37
                    ->info('This must be a service id to a service implementing '.LoggerInterface::class)
476 37
                    ->defaultValue('logger')
477 37
                    ->cannotBeEmpty()
478 37
                ->end()
479 37
                ->scalarNode('formatter')
480 37
                    ->info('This must be a service id to a service implementing '.Formatter::class)
481 37
                    ->defaultNull()
482 37
                ->end()
483 37
            ->end()
484 37
        ->end();
485
        // End logger plugin
486
487 37
        $redirect = $children->arrayNode('redirect');
488 37
        $disableAll ? $redirect->canBeEnabled() : $redirect->canBeDisabled();
489 37
        $redirect->addDefaultsIfNotSet()
490 37
            ->children()
491 37
                ->scalarNode('preserve_header')->defaultTrue()->end()
492 37
                ->scalarNode('use_default_for_multiple')->defaultTrue()->end()
493 37
            ->end()
494 37
        ->end();
495
        // End redirect plugin
496
497 37
        $retry = $children->arrayNode('retry');
498 37
        $disableAll ? $retry->canBeEnabled() : $retry->canBeDisabled();
499 37
        $retry->addDefaultsIfNotSet()
500 37
            ->children()
501 37
                ->scalarNode('retry')->defaultValue(1)->end() // TODO: should be called retries for consistency with the class
502 37
            ->end()
503 37
        ->end();
504
        // End retry plugin
505
506 37
        $stopwatch = $children->arrayNode('stopwatch');
507 37
        $disableAll ? $stopwatch->canBeEnabled() : $stopwatch->canBeDisabled();
508 37
        $stopwatch->addDefaultsIfNotSet()
509 37
            ->children()
510 37
                ->scalarNode('stopwatch')
511 37
                    ->info('This must be a service id to a service extending Symfony\Component\Stopwatch\Stopwatch')
512 37
                    ->defaultValue('debug.stopwatch')
513 37
                    ->cannotBeEmpty()
514 37
                ->end()
515 37
            ->end()
516 37
        ->end();
517
        // End stopwatch plugin
518 37
    }
519
520
    /**
521
     * Create configuration for authentication plugin.
522
     *
523
     * @return NodeDefinition definition for the authentication node in the plugins list
524
     */
525 37
    private function createAuthenticationPluginNode()
526
    {
527 37
        $treeBuilder = new TreeBuilder('authentication');
528
        // Keep compatibility with symfony/config < 4.2
529 37
        if (!method_exists($treeBuilder, 'getRootNode')) {
530
            $node = $treeBuilder->root('authentication');
531
        } else {
532 37
            $node = $treeBuilder->getRootNode();
533
        }
534
535
        $node
0 ignored issues
show
Bug introduced by
The method useAttributeAsKey() does not exist on Symfony\Component\Config...\Builder\NodeDefinition. Did you maybe mean attribute()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
536 37
            ->useAttributeAsKey('name')
537 37
            ->prototype('array')
538 37
                ->validate()
539 37
                    ->always()
540 37
                    ->then(function ($config) {
541 7
                        switch ($config['type']) {
542 7
                            case 'basic':
543 6
                                $this->validateAuthenticationType(['username', 'password'], $config, 'basic');
544
545 6
                                break;
546 2
                            case 'bearer':
547 1
                                $this->validateAuthenticationType(['token'], $config, 'bearer');
548
549 1
                                break;
550 2
                            case 'service':
551 2
                                $this->validateAuthenticationType(['service'], $config, 'service');
552
553 1
                                break;
554 1
                            case 'wsse':
555 1
                                $this->validateAuthenticationType(['username', 'password'], $config, 'wsse');
556
557 1
                                break;
558
                            case 'query_param':
559
                                $this->validateAuthenticationType(['params'], $config, 'query_param');
560
561
                                break;
562
                        }
563
564 6
                        return $config;
565 37
                    })
566 37
                ->end()
567 37
                ->children()
568 37
                    ->enumNode('type')
569 37
                        ->values(['basic', 'bearer', 'wsse', 'service', 'query_param'])
570 37
                        ->isRequired()
571 37
                        ->cannotBeEmpty()
572 37
                    ->end()
573 37
                    ->scalarNode('username')->end()
574 37
                    ->scalarNode('password')->end()
575 37
                    ->scalarNode('token')->end()
576 37
                    ->scalarNode('service')->end()
577 37
                    ->arrayNode('params')->prototype('scalar')->end()
578 37
                    ->end()
579 37
                ->end()
580 37
            ->end(); // End authentication plugin
581
582 37
        return $node;
583
    }
584
585
    /**
586
     * Validate that the configuration fragment has the specified keys and none other.
587
     *
588
     * @param array  $expected Fields that must exist
589
     * @param array  $actual   Actual configuration hashmap
590
     * @param string $authName Name of authentication method for error messages
591
     *
592
     * @throws InvalidConfigurationException If $actual does not have exactly the keys specified in $expected (plus 'type')
593
     */
594 7
    private function validateAuthenticationType(array $expected, array $actual, $authName)
595
    {
596 7
        unset($actual['type']);
597
        // Empty array is always provided, even if the config is not filled.
598 7
        if (empty($actual['params'])) {
599 7
            unset($actual['params']);
600
        }
601 7
        $actual = array_keys($actual);
602 7
        sort($actual);
603 7
        sort($expected);
604
605 7
        if ($expected === $actual) {
606 6
            return;
607
        }
608
609 1
        throw new InvalidConfigurationException(sprintf(
610 1
            'Authentication "%s" requires %s but got %s',
611
            $authName,
612 1
            implode(', ', $expected),
613 1
            implode(', ', $actual)
614
        ));
615
    }
616
617
    /**
618
     * Create configuration for cache plugin.
619
     *
620
     * @return NodeDefinition definition for the cache node in the plugins list
621
     */
622 37
    private function createCachePluginNode()
623
    {
624 37
        $builder = new TreeBuilder('config');
625
        // Keep compatibility with symfony/config < 4.2
626 37
        if (!method_exists($builder, 'getRootNode')) {
627
            $config = $builder->root('config');
628
        } else {
629 37
            $config = $builder->getRootNode();
630
        }
631
632
        $config
633 37
            ->fixXmlConfig('method')
634 37
            ->fixXmlConfig('respect_response_cache_directive')
635 37
            ->addDefaultsIfNotSet()
636 37
            ->validate()
637 37
                ->ifTrue(function ($config) {
638
                    // Cannot set both respect_cache_headers and respect_response_cache_directives
639 5
                    return isset($config['respect_cache_headers'], $config['respect_response_cache_directives']);
640 37
                })
641 37
                ->thenInvalid('You can\'t provide config option "respect_cache_headers" and "respect_response_cache_directives" simultaniously. Use "respect_response_cache_directives" instead.')
642 37
            ->end()
643 37
            ->children()
644 37
                ->scalarNode('cache_key_generator')
645 37
                    ->info('This must be a service id to a service implementing '.CacheKeyGenerator::class)
646 37
                ->end()
647 37
                ->integerNode('cache_lifetime')
648 37
                    ->info('The minimum time we should store a cache item')
649 37
                ->end()
650 37
                ->integerNode('default_ttl')
651 37
                    ->info('The default max age of a Response')
652 37
                ->end()
653 37
                ->enumNode('hash_algo')
654 37
                    ->info('Hashing algorithm to use')
655 37
                    ->values(hash_algos())
656 37
                    ->cannotBeEmpty()
657 37
                ->end()
658 37
                ->arrayNode('methods')
659 37
                    ->info('Which request methods to cache')
660 37
                    ->defaultValue(['GET', 'HEAD'])
661 37
                    ->prototype('scalar')
662 37
                        ->validate()
663 37
                            ->ifTrue(function ($v) {
664
                                /* RFC7230 sections 3.1.1 and 3.2.6 except limited to uppercase characters. */
665 1
                                return preg_match('/[^A-Z0-9!#$%&\'*+\-.^_`|~]+/', $v);
666 37
                            })
667 37
                            ->thenInvalid('Invalid method: %s')
668 37
                        ->end()
669 37
                    ->end()
670 37
                ->end()
671 37
                ->scalarNode('respect_cache_headers')
672 37
                    ->info('Whether we should care about cache headers or not [DEPRECATED]')
673 37
                    ->beforeNormalization()
674 37
                        ->always(function ($v) {
675 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);
676
677 3
                            return $v;
678 37
                        })
679 37
                    ->end()
680 37
                    ->validate()
681 37
                        ->ifNotInArray([null, true, false])
682 37
                        ->thenInvalid('Value for "respect_cache_headers" must be null or boolean')
683 37
                    ->end()
684 37
                ->end()
685 37
                ->variableNode('respect_response_cache_directives')
686 37
                    ->info('A list of cache directives to respect when caching responses')
687 37
                    ->validate()
688 37
                        ->always(function ($v) {
689 2
                            if (is_null($v) || is_array($v)) {
690 2
                                return $v;
691
                            }
692
693
                            throw new InvalidTypeException();
694 37
                        })
695 37
                    ->end()
696 37
                ->end()
697 37
            ->end()
698
        ;
699
700 37
        $cache = $builder->root('cache');
701
        $cache
702 37
            ->canBeEnabled()
703 37
            ->addDefaultsIfNotSet()
704 37
            ->children()
705 37
                ->scalarNode('cache_pool')
706 37
                    ->info('This must be a service id to a service implementing '.CacheItemPoolInterface::class)
707 37
                    ->isRequired()
708 37
                    ->cannotBeEmpty()
709 37
                ->end()
710 37
                ->scalarNode('stream_factory')
711 37
                    ->info('This must be a service id to a service implementing '.StreamFactory::class)
712 37
                    ->defaultValue('httplug.stream_factory')
713 37
                    ->cannotBeEmpty()
714 37
                ->end()
715 37
            ->end()
716 37
            ->append($config)
717
        ;
718
719 37
        return $cache;
720
    }
721
}
722