Completed
Push — master ( c88cc4...bbe830 )
by David
15:29 queued 11s
created

Configuration::createCachePluginNode()   B

Complexity

Conditions 5
Paths 2

Size

Total Lines 106

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 87
CRAP Score 5.0002

Importance

Changes 0
Metric Value
dl 0
loc 106
ccs 87
cts 89
cp 0.9775
rs 7.6888
c 0
b 0
f 0
cc 5
nc 2
nop 0
crap 5.0002

How to fix   Long Method   

Long Method

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

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

Commonly applied refactorings include:

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