Completed
Push — master ( d05392...6a74d8 )
by David
07:01
created

Configuration::createAuthenticationPluginNode()   B

Complexity

Conditions 7
Paths 2

Size

Total Lines 59

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 40
CRAP Score 7.0368

Importance

Changes 0
Metric Value
dl 0
loc 59
ccs 40
cts 44
cp 0.9091
rs 7.9612
c 0
b 0
f 0
cc 7
nc 2
nop 0
crap 7.0368

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