Completed
Push — master ( 8b7cb0...fb1018 )
by Tobias
12:08
created

Configuration   B

Complexity

Total Complexity 47

Size/Duplication

Total Lines 701
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 8

Test Coverage

Coverage 98.48%

Importance

Changes 0
Metric Value
wmc 47
lcom 1
cbo 8
dl 0
loc 701
ccs 520
cts 528
cp 0.9848
rs 8.539
c 0
b 0
f 0

9 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
C getConfigTreeBuilder() 0 116 13
B configureClients() 0 58 4
A configureSharedPlugins() 0 11 1
B createClientPluginNode() 0 175 7
B addSharedPluginNodes() 0 91 6
B createAuthenticationPluginNode() 0 59 7
A validateAuthenticationType() 0 22 3
B createCachePluginNode() 0 106 5

How to fix   Complexity   

Complex Class

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

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

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

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