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