Completed
Pull Request — master (#111)
by David
06:05
created

Configuration::configureClients()   A

Complexity

Conditions 3
Paths 1

Size

Total Lines 58
Code Lines 51

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 51
CRAP Score 3

Importance

Changes 5
Bugs 0 Features 4
Metric Value
c 5
b 0
f 4
dl 0
loc 58
ccs 51
cts 51
cp 1
rs 9.639
cc 3
eloc 51
nc 1
nop 1
crap 3

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 Symfony\Component\Config\Definition\ArrayNode;
6
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
7
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
8
use Symfony\Component\Config\Definition\ConfigurationInterface;
9
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
10
11
/**
12
 * This class contains the configuration information for the bundle.
13
 *
14
 * This information is solely responsible for how the different configuration
15
 * sections are normalized, and merged.
16
 *
17
 * @author David Buchmann <[email protected]>
18
 * @author Tobias Nyholm <[email protected]>
19
 */
20
class Configuration implements ConfigurationInterface
21
{
22
    /**
23
     * Whether to use the debug mode.
24
     *
25
     * @see https://github.com/doctrine/DoctrineBundle/blob/v1.5.2/DependencyInjection/Configuration.php#L31-L41
26
     *
27
     * @var bool
28
     */
29
    private $debug;
30
31
    /**
32
     * @param bool $debug
33
     */
34 12
    public function __construct($debug)
35
    {
36 12
        $this->debug = (bool) $debug;
37 12
    }
38
39
    /**
40
     * {@inheritdoc}
41
     */
42 12
    public function getConfigTreeBuilder()
43
    {
44 12
        $treeBuilder = new TreeBuilder();
45 12
        $rootNode = $treeBuilder->root('httplug');
46
47 12
        $this->configureClients($rootNode);
48 12
        $this->configurePlugins($rootNode);
49
50
        $rootNode
51 12
            ->validate()
52
                ->ifTrue(function ($v) {
53 11
                    return !empty($v['classes']['client'])
54 11
                        || !empty($v['classes']['message_factory'])
55 8
                        || !empty($v['classes']['uri_factory'])
56 11
                        || !empty($v['classes']['stream_factory']);
57 12
                })
58
                ->then(function ($v) {
59 3
                    foreach ($v['classes'] as $key => $class) {
60 3
                        if (null !== $class && !class_exists($class)) {
61 1
                            throw new InvalidConfigurationException(sprintf(
62 1
                                'Class %s specified for httplug.classes.%s does not exist.',
63 1
                                $class,
64
                                $key
65 1
                            ));
66
                        }
67 2
                    }
68
69 2
                    return $v;
70 12
                })
71 12
            ->end()
72 12
            ->beforeNormalization()
73 12
                ->ifTrue(function ($v) {
74 12
                    return is_array($v) && array_key_exists('toolbar', $v) && is_array($v['toolbar']);
75 12
                })
76 12
                ->then(function ($v) {
77 12
                    if (array_key_exists('profiling', $v)) {
78 12
                        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".');
79 12
                    }
80 12
81 12
                    @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);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
82 12
83 12
                    if (array_key_exists('enabled', $v['toolbar']) && 'auto' === $v['toolbar']['enabled']) {
84 12
                        @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);
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
85 12
                        $v['toolbar']['enabled'] = $this->debug;
86 12
                    }
87 12
88 12
                    $v['profiling'] = $v['toolbar'];
89 12
90 12
                    unset($v['toolbar']);
91 12
92 12
                    return $v;
93 12
                })
94 12
            ->end()
95 12
            ->fixXmlConfig('client')
96 12
            ->children()
97 12
                ->arrayNode('main_alias')
98 12
                    ->addDefaultsIfNotSet()
99 12
                    ->info('Configure which service the main alias point to.')
100
                    ->children()
101 1
                        ->scalarNode('client')->defaultValue('httplug.client.default')->end()
102 12
                        ->scalarNode('message_factory')->defaultValue('httplug.message_factory.default')->end()
103 12
                        ->scalarNode('uri_factory')->defaultValue('httplug.uri_factory.default')->end()
104 12
                        ->scalarNode('stream_factory')->defaultValue('httplug.stream_factory.default')->end()
105 12
                    ->end()
106 12
                ->end()
107 12
                ->arrayNode('classes')
108 12
                    ->addDefaultsIfNotSet()
109 12
                    ->info('Overwrite a service class instead of using the discovery mechanism.')
110 12
                    ->children()
111 12
                        ->scalarNode('client')->defaultNull()->end()
112 12
                        ->scalarNode('message_factory')->defaultNull()->end()
113 12
                        ->scalarNode('uri_factory')->defaultNull()->end()
114 12
                        ->scalarNode('stream_factory')->defaultNull()->end()
115 12
                    ->end()
116 12
                ->end()
117 12
                ->arrayNode('profiling')
118 12
                    ->addDefaultsIfNotSet()
119 12
                    ->treatFalseLike(['enabled' => false])
120 12
                    ->treatTrueLike(['enabled' => true])
121 12
                    ->treatNullLike(['enabled' => $this->debug])
122 12
                    ->info('Extend the debug profiler with information about requests.')
123 12
                    ->children()
124 12
                        ->booleanNode('enabled')
125 12
                            ->info('Turn the toolbar on or off. Defaults to kernel debug mode.')
126 12
                            ->defaultValue($this->debug)
127 12
                        ->end()
128 12
                        ->scalarNode('formatter')->defaultNull()->end()
129
                        ->integerNode('captured_body_length')
130 12
                            ->defaultValue(0)
131
                            ->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).')
132
                        ->end()
133 12
                    ->end()
134
                ->end()
135 12
                ->arrayNode('discovery')
136 12
                    ->addDefaultsIfNotSet()
137 12
                    ->info('Control what clients should be found by the discovery.')
138
                    ->children()
139 3
                        ->scalarNode('client')
140
                            ->defaultValue('auto')
141 3
                            ->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.')
142
                        ->end()
143
                        ->scalarNode('async_client')
144
                            ->defaultNull()
145 12
                            ->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.')
146 12
                        ->end()
147 12
                    ->end()
148 12
                ->end()
149 12
            ->end();
150 12
151 12
        return $treeBuilder;
152 12
    }
153 12
154 12
    protected function configureClients(ArrayNodeDefinition $root)
155 12
    {
156 12
        $root->children()
157 12
            ->arrayNode('clients')
158 12
                ->validate()
159 12
                    ->ifTrue(function ($clients) {
160 12
                        foreach ($clients as $name => $config) {
161 12
                            // Make sure we only allow one of these to be true
162 12
                            return (bool) $config['flexible_client'] + (bool) $config['http_methods_client'] + (bool) $config['batch_client'] >= 2;
163 12
                        }
164 12
165 12
                        return false;
166 12
                    })
167 12
                    ->thenInvalid('A http client can\'t be decorated with both FlexibleHttpClient and HttpMethodsClient. Only one of the following options can be true. ("flexible_client", "http_methods_client")')->end()
168 12
                ->useAttributeAsKey('name')
169 12
                ->prototype('array')
170 12
                ->children()
171 12
                    ->scalarNode('factory')
172 12
                        ->isRequired()
173 12
                        ->cannotBeEmpty()
174 12
                        ->info('The service id of a factory to use when creating the adapter.')
175
                    ->end()
176
                    ->booleanNode('flexible_client')
177
                        ->defaultFalse()
178
                        ->info('Set to true to get the client wrapped in a FlexibleHttpClient which emulates async or sync behavior.')
179 12
                    ->end()
180
                    ->booleanNode('http_methods_client')
181 12
                        ->defaultFalse()
182 12
                        ->info('Set to true to get the client wrapped in a HttpMethodsClient which emulates provides functions for HTTP verbs.')
183 12
                    ->end()
184 12
                    ->booleanNode('batch_client')
185 12
                        ->defaultFalse()
186
                        ->info('Set to true to get the client wrapped in a BatchClient which allows you to send multiple request at the same time.')
187 12
                    ->end()
188 12
                    ->arrayNode('options')
189 12
                        ->validate()
190 12
                            ->ifTrue(function ($options) {
191 12
                                return array_key_exists('default_host', $options) && array_key_exists('force_host', $options);
192 12
                            })
193 12
                            ->thenInvalid('You can only set one of default_host and force_host for each client')
194 12
                        ->end()
195 12
                        ->children()
196 12
                            ->scalarNode('default_host')
197 12
                                ->info('Configure the AddHostPlugin for this client. Add host if request is only for a path.')
198 12
                            ->end()
199 12
                            ->scalarNode('force_host')
200 12
                                ->info('Configure the AddHostPlugin for this client. Send all requests to this host regardless of host in request.')
201 12
                            ->end()
202 12
                        ->end()
203 12
                    ->end()
204 12
                    ->arrayNode('plugins')
205 12
                        ->info('A list of service ids of plugins. The order is important.')
206 12
                        ->prototype('scalar')->end()
207 12
                    ->end()
208 12
                    ->variableNode('config')->defaultValue([])->end()
209 12
                ->end()
210
            ->end();
211 12
    }
212 12
213 12
    /**
214 12
     * @param ArrayNodeDefinition $root
215 12
     */
216 12
    protected function configurePlugins(ArrayNodeDefinition $root)
217 12
    {
218 12
        $root->children()
219 12
            ->arrayNode('plugins')
220 12
                ->addDefaultsIfNotSet()
221
                ->children()
222 12
                    ->append($this->addAuthenticationPluginNode())
223 12
224 12
                    ->arrayNode('cache')
225 12
                    ->canBeEnabled()
226 12
                    ->addDefaultsIfNotSet()
227 12
                        ->children()
228 12
                            ->scalarNode('cache_pool')
229
                                ->info('This must be a service id to a service implementing Psr\Cache\CacheItemPoolInterface')
230 12
                                ->isRequired()
231 12
                                ->cannotBeEmpty()
232 12
                            ->end()
233 12
                            ->scalarNode('stream_factory')
234 12
                                ->info('This must be a service id to a service implementing Http\Message\StreamFactory')
235 12
                                ->defaultValue('httplug.stream_factory')
236 12
                                ->cannotBeEmpty()
237 12
                            ->end()
238 12
                            ->arrayNode('config')
239 12
                                ->addDefaultsIfNotSet()
240
                                ->children()
241 12
                                    ->scalarNode('default_ttl')->defaultNull()->end()
242 12
                                    ->scalarNode('respect_cache_headers')->defaultTrue()->end()
243 12
                                ->end()
244 12
                            ->end()
245 12
                        ->end()
246 12
                    ->end() // End cache plugin
247 12
248 12
                    ->arrayNode('cookie')
249 12
                    ->canBeEnabled()
250 12
                        ->children()
251 12
                            ->scalarNode('cookie_jar')
252 12
                                ->info('This must be a service id to a service implementing Http\Message\CookieJar')
253 12
                                ->isRequired()
254 12
                                ->cannotBeEmpty()
255 12
                            ->end()
256
                        ->end()
257 12
                    ->end() // End cookie plugin
258 12
259 12
                    ->arrayNode('decoder')
260 12
                    ->canBeDisabled()
261 12
                    ->addDefaultsIfNotSet()
262 12
                        ->children()
263 12
                            ->scalarNode('use_content_encoding')->defaultTrue()->end()
264 12
                        ->end()
265
                    ->end() // End decoder plugin
266 12
267 12
                    ->arrayNode('history')
268 12
                    ->canBeEnabled()
269 12
                        ->children()
270 12
                            ->scalarNode('journal')
271 12
                                ->info('This must be a service id to a service implementing Http\Client\Plugin\Journal')
272 12
                                ->isRequired()
273
                                ->cannotBeEmpty()
274 12
                            ->end()
275 12
                        ->end()
276 12
                    ->end() // End history plugin
277 12
278 12
                    ->arrayNode('logger')
279 12
                    ->canBeDisabled()
280 12
                    ->addDefaultsIfNotSet()
281 12
                        ->children()
282 12
                            ->scalarNode('logger')
283 12
                                ->info('This must be a service id to a service implementing Psr\Log\LoggerInterface')
284 12
                                ->defaultValue('logger')
285
                                ->cannotBeEmpty()
286 12
                            ->end()
287 12
                            ->scalarNode('formatter')
288 12
                                ->info('This must be a service id to a service implementing Http\Message\Formatter')
289 12
                                ->defaultNull()
290
                            ->end()
291
                        ->end()
292
                    ->end() // End logger plugin
293
294
                    ->arrayNode('redirect')
295
                    ->canBeDisabled()
296 12
                    ->addDefaultsIfNotSet()
297
                        ->children()
298 12
                            ->scalarNode('preserve_header')->defaultTrue()->end()
299 12
                            ->scalarNode('use_default_for_multiple')->defaultTrue()->end()
300
                        ->end()
301 12
                    ->end() // End redirect plugin
302 12
303 12
                    ->arrayNode('retry')
304 12
                    ->canBeDisabled()
305 12
                    ->addDefaultsIfNotSet()
306 2
                        ->children()
307 2
                            ->scalarNode('retry')->defaultValue(1)->end()
308 1
                        ->end()
309 1
                    ->end() // End retry plugin
310 2
311 1
                    ->arrayNode('stopwatch')
312 1
                    ->canBeDisabled()
313 2
                    ->addDefaultsIfNotSet()
314 2
                        ->children()
315 1
                            ->scalarNode('stopwatch')
316 1
                                ->info('This must be a service id to a service extending Symfony\Component\Stopwatch\Stopwatch')
317 1
                                ->defaultValue('debug.stopwatch')
318 1
                                ->cannotBeEmpty()
319 1
                            ->end()
320
                        ->end()
321 1
                    ->end() // End stopwatch plugin
322 12
323 12
                ->end()
324 12
            ->end()
325 12
        ->end();
326 12
    }
327 12
328 12
    /**
329 12
     * Add configuration for authentication plugin.
330 12
     *
331 12
     * @return ArrayNodeDefinition|\Symfony\Component\Config\Definition\Builder\NodeDefinition
332 12
     */
333 12
    private function addAuthenticationPluginNode()
334 12
    {
335 12
        $builder = new TreeBuilder();
336 12
        $node = $builder->root('authentication');
337
        $node
338 12
            ->useAttributeAsKey('name')
339
            ->prototype('array')
340
                ->validate()
341
                    ->always()
342
                    ->then(function ($config) {
343
                        switch ($config['type']) {
344
                            case 'basic':
345
                                $this->validateAuthenticationType(['username', 'password'], $config, 'basic');
346
                                break;
347
                            case 'bearer':
348
                                $this->validateAuthenticationType(['token'], $config, 'bearer');
349
                                break;
350 2
                            case 'service':
351
                                $this->validateAuthenticationType(['service'], $config, 'service');
352 2
                                break;
353 2
                            case 'wsse':
354 2
                                $this->validateAuthenticationType(['username', 'password'], $config, 'wsse');
355 2
                                break;
356
                        }
357 2
358 1
                        return $config;
359
                    })
360
                ->end()
361 1
                ->children()
362 1
                    ->enumNode('type')
363 1
                        ->values(['basic', 'bearer', 'wsse', 'service'])
364 1
                        ->isRequired()
365 1
                        ->cannotBeEmpty()
366 1
                    ->end()
367
                    ->scalarNode('username')->end()
368
                    ->scalarNode('password')->end()
369
                    ->scalarNode('token')->end()
370
                    ->scalarNode('service')->end()
371
                    ->end()
372
                ->end()
373
            ->end(); // End authentication plugin
374
375
        return $node;
376
    }
377
378
    /**
379
     * Validate that the configuration fragment has the specified keys and none other.
380
     *
381
     * @param array  $expected Fields that must exist
382
     * @param array  $actual   Actual configuration hashmap
383
     * @param string $authName Name of authentication method for error messages
384
     *
385
     * @throws InvalidConfigurationException If $actual does not have exactly the keys specified in $expected (plus 'type')
386
     */
387
    private function validateAuthenticationType(array $expected, array $actual, $authName)
388
    {
389
        unset($actual['type']);
390
        $actual = array_keys($actual);
391
        sort($actual);
392
        sort($expected);
393
394
        if ($expected === $actual) {
395
            return;
396
        }
397
398
        throw new InvalidConfigurationException(sprintf(
399
            'Authentication "%s" requires %s but got %s',
400
            $authName,
401
            implode(', ', $expected),
402
            implode(', ', $actual)
403
        ));
404
    }
405
}
406