Completed
Push — master ( 6a74d8...f3d2ef )
by David
05:54 queued 10s
created

Configuration::getConfigTreeBuilder()   C

Complexity

Conditions 14
Paths 2

Size

Total Lines 122

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 99
CRAP Score 14.0001

Importance

Changes 0
Metric Value
dl 0
loc 122
ccs 99
cts 100
cp 0.99
rs 5.0133
c 0
b 0
f 0
cc 14
nc 2
nop 0
crap 14.0001

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