1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/* |
4
|
|
|
* This file is part of the Symfony package. |
5
|
|
|
* |
6
|
|
|
* (c) Fabien Potencier <[email protected]> |
7
|
|
|
* |
8
|
|
|
* For the full copyright and license information, please view the LICENSE |
9
|
|
|
* file that was distributed with this source code. |
10
|
|
|
*/ |
11
|
|
|
|
12
|
|
|
namespace Symfony\Component\DependencyInjection\Loader; |
13
|
|
|
|
14
|
|
|
use Symfony\Component\DependencyInjection\DefinitionDecorator; |
15
|
|
|
use Symfony\Component\DependencyInjection\Alias; |
16
|
|
|
use Symfony\Component\DependencyInjection\ContainerInterface; |
17
|
|
|
use Symfony\Component\DependencyInjection\Definition; |
18
|
|
|
use Symfony\Component\DependencyInjection\Reference; |
19
|
|
|
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; |
20
|
|
|
use Symfony\Component\DependencyInjection\Exception\RuntimeException; |
21
|
|
|
use Symfony\Component\Config\Resource\FileResource; |
22
|
|
|
use Symfony\Component\Yaml\Parser as YamlParser; |
23
|
|
|
use Symfony\Component\ExpressionLanguage\Expression; |
24
|
|
|
|
25
|
|
|
/** |
26
|
|
|
* YamlFileLoader loads YAML files service definitions. |
27
|
|
|
* |
28
|
|
|
* The YAML format does not support anonymous services (cf. the XML loader). |
29
|
|
|
* |
30
|
|
|
* @author Fabien Potencier <[email protected]> |
31
|
|
|
*/ |
32
|
|
|
class YamlFileLoader extends FileLoader |
33
|
|
|
{ |
34
|
|
|
private $yamlParser; |
35
|
|
|
|
36
|
|
|
/** |
37
|
|
|
* {@inheritdoc} |
38
|
|
|
*/ |
39
|
|
|
public function load($resource, $type = null) |
40
|
|
|
{ |
41
|
|
|
$path = $this->locator->locate($resource); |
42
|
|
|
|
43
|
|
|
$content = $this->loadFile($path); |
44
|
|
|
|
45
|
|
|
$this->container->addResource(new FileResource($path)); |
46
|
|
|
|
47
|
|
|
// empty file |
48
|
|
|
if (null === $content) { |
49
|
|
|
return; |
50
|
|
|
} |
51
|
|
|
|
52
|
|
|
// imports |
53
|
|
|
$this->parseImports($content, $path); |
54
|
|
|
|
55
|
|
|
// parameters |
56
|
|
View Code Duplication |
if (isset($content['parameters'])) { |
|
|
|
|
57
|
|
|
if (!is_array($content['parameters'])) { |
58
|
|
|
throw new InvalidArgumentException(sprintf('The "parameters" key should contain an array in %s. Check your YAML syntax.', $resource)); |
59
|
|
|
} |
60
|
|
|
|
61
|
|
|
foreach ($content['parameters'] as $key => $value) { |
62
|
|
|
$this->container->setParameter($key, $this->resolveServices($value)); |
63
|
|
|
} |
64
|
|
|
} |
65
|
|
|
|
66
|
|
|
// extensions |
67
|
|
|
$this->loadFromExtensions($content); |
68
|
|
|
|
69
|
|
|
// services |
70
|
|
|
$this->parseDefinitions($content, $resource); |
71
|
|
|
} |
72
|
|
|
|
73
|
|
|
/** |
74
|
|
|
* {@inheritdoc} |
75
|
|
|
*/ |
76
|
|
|
public function supports($resource, $type = null) |
77
|
|
|
{ |
78
|
|
|
return is_string($resource) && in_array(pathinfo($resource, PATHINFO_EXTENSION), array('yml', 'yaml'), true); |
79
|
|
|
} |
80
|
|
|
|
81
|
|
|
/** |
82
|
|
|
* Parses all imports. |
83
|
|
|
* |
84
|
|
|
* @param array $content |
85
|
|
|
* @param string $file |
86
|
|
|
*/ |
87
|
|
|
private function parseImports($content, $file) |
88
|
|
|
{ |
89
|
|
|
if (!isset($content['imports'])) { |
90
|
|
|
return; |
91
|
|
|
} |
92
|
|
|
|
93
|
|
|
if (!is_array($content['imports'])) { |
94
|
|
|
throw new InvalidArgumentException(sprintf('The "imports" key should contain an array in %s. Check your YAML syntax.', $file)); |
95
|
|
|
} |
96
|
|
|
|
97
|
|
|
foreach ($content['imports'] as $import) { |
98
|
|
|
if (!is_array($import)) { |
99
|
|
|
throw new InvalidArgumentException(sprintf('The values in the "imports" key should be arrays in %s. Check your YAML syntax.', $file)); |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
$this->setCurrentDir(dirname($file)); |
103
|
|
|
$this->import($import['resource'], null, isset($import['ignore_errors']) ? (bool) $import['ignore_errors'] : false, $file); |
104
|
|
|
} |
105
|
|
|
} |
106
|
|
|
|
107
|
|
|
/** |
108
|
|
|
* Parses definitions. |
109
|
|
|
* |
110
|
|
|
* @param array $content |
111
|
|
|
* @param string $file |
112
|
|
|
*/ |
113
|
|
View Code Duplication |
private function parseDefinitions($content, $file) |
114
|
|
|
{ |
115
|
|
|
if (!isset($content['services'])) { |
116
|
|
|
return; |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
if (!is_array($content['services'])) { |
120
|
|
|
throw new InvalidArgumentException(sprintf('The "services" key should contain an array in %s. Check your YAML syntax.', $file)); |
121
|
|
|
} |
122
|
|
|
|
123
|
|
|
foreach ($content['services'] as $id => $service) { |
124
|
|
|
$this->parseDefinition($id, $service, $file); |
125
|
|
|
} |
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
/** |
129
|
|
|
* Parses a definition. |
130
|
|
|
* |
131
|
|
|
* @param string $id |
132
|
|
|
* @param array $service |
133
|
|
|
* @param string $file |
134
|
|
|
* |
135
|
|
|
* @throws InvalidArgumentException When tags are invalid |
136
|
|
|
*/ |
137
|
|
|
private function parseDefinition($id, $service, $file) |
138
|
|
|
{ |
139
|
|
View Code Duplication |
if (is_string($service) && 0 === strpos($service, '@')) { |
|
|
|
|
140
|
|
|
$this->container->setAlias($id, substr($service, 1)); |
141
|
|
|
|
142
|
|
|
return; |
143
|
|
|
} |
144
|
|
|
|
145
|
|
View Code Duplication |
if (!is_array($service)) { |
|
|
|
|
146
|
|
|
throw new InvalidArgumentException(sprintf('A service definition must be an array or a string starting with "@" but %s found for service "%s" in %s. Check your YAML syntax.', gettype($service), $id, $file)); |
147
|
|
|
} |
148
|
|
|
|
149
|
|
View Code Duplication |
if (isset($service['alias'])) { |
|
|
|
|
150
|
|
|
$public = !array_key_exists('public', $service) || (bool) $service['public']; |
151
|
|
|
$this->container->setAlias($id, new Alias($service['alias'], $public)); |
152
|
|
|
|
153
|
|
|
return; |
154
|
|
|
} |
155
|
|
|
|
156
|
|
View Code Duplication |
if (isset($service['parent'])) { |
|
|
|
|
157
|
|
|
$definition = new DefinitionDecorator($service['parent']); |
158
|
|
|
} else { |
159
|
|
|
$definition = new Definition(); |
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
if (isset($service['class'])) { |
163
|
|
|
$definition->setClass($service['class']); |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
if (isset($service['scope'])) { |
167
|
|
|
$definition->setScope($service['scope']); |
168
|
|
|
} |
169
|
|
|
|
170
|
|
|
if (isset($service['synthetic'])) { |
171
|
|
|
$definition->setSynthetic($service['synthetic']); |
172
|
|
|
} |
173
|
|
|
|
174
|
|
|
if (isset($service['synchronized'])) { |
175
|
|
|
trigger_error(sprintf('The "synchronized" key in file "%s" is deprecated since version 2.7 and will be removed in 3.0.', $file), E_USER_DEPRECATED); |
176
|
|
|
$definition->setSynchronized($service['synchronized'], 'request' !== $id); |
|
|
|
|
177
|
|
|
} |
178
|
|
|
|
179
|
|
|
if (isset($service['lazy'])) { |
180
|
|
|
$definition->setLazy($service['lazy']); |
181
|
|
|
} |
182
|
|
|
|
183
|
|
|
if (isset($service['public'])) { |
184
|
|
|
$definition->setPublic($service['public']); |
185
|
|
|
} |
186
|
|
|
|
187
|
|
|
if (isset($service['abstract'])) { |
188
|
|
|
$definition->setAbstract($service['abstract']); |
189
|
|
|
} |
190
|
|
|
|
191
|
|
View Code Duplication |
if (isset($service['factory'])) { |
|
|
|
|
192
|
|
|
if (is_string($service['factory'])) { |
193
|
|
|
if (strpos($service['factory'], ':') !== false && strpos($service['factory'], '::') === false) { |
194
|
|
|
$parts = explode(':', $service['factory']); |
195
|
|
|
$definition->setFactory(array($this->resolveServices('@'.$parts[0]), $parts[1])); |
196
|
|
|
} else { |
197
|
|
|
$definition->setFactory($service['factory']); |
198
|
|
|
} |
199
|
|
|
} else { |
200
|
|
|
$definition->setFactory(array($this->resolveServices($service['factory'][0]), $service['factory'][1])); |
201
|
|
|
} |
202
|
|
|
} |
203
|
|
|
|
204
|
|
|
if (isset($service['factory_class'])) { |
205
|
|
|
trigger_error(sprintf('The "factory_class" key in file "%s" is deprecated since version 2.6 and will be removed in 3.0. Use "factory" instead.', $file), E_USER_DEPRECATED); |
206
|
|
|
$definition->setFactoryClass($service['factory_class']); |
|
|
|
|
207
|
|
|
} |
208
|
|
|
|
209
|
|
|
if (isset($service['factory_method'])) { |
210
|
|
|
trigger_error(sprintf('The "factory_method" key in file "%s" is deprecated since version 2.6 and will be removed in 3.0. Use "factory" instead.', $file), E_USER_DEPRECATED); |
211
|
|
|
$definition->setFactoryMethod($service['factory_method']); |
|
|
|
|
212
|
|
|
} |
213
|
|
|
|
214
|
|
|
if (isset($service['factory_service'])) { |
215
|
|
|
trigger_error(sprintf('The "factory_service" key in file "%s" is deprecated since version 2.6 and will be removed in 3.0. Use "factory" instead.', $file), E_USER_DEPRECATED); |
216
|
|
|
$definition->setFactoryService($service['factory_service']); |
|
|
|
|
217
|
|
|
} |
218
|
|
|
|
219
|
|
|
if (isset($service['file'])) { |
220
|
|
|
$definition->setFile($service['file']); |
221
|
|
|
} |
222
|
|
|
|
223
|
|
|
if (isset($service['arguments'])) { |
224
|
|
|
$definition->setArguments($this->resolveServices($service['arguments'])); |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
if (isset($service['properties'])) { |
228
|
|
|
$definition->setProperties($this->resolveServices($service['properties'])); |
229
|
|
|
} |
230
|
|
|
|
231
|
|
View Code Duplication |
if (isset($service['configurator'])) { |
|
|
|
|
232
|
|
|
if (is_string($service['configurator'])) { |
233
|
|
|
$definition->setConfigurator($service['configurator']); |
234
|
|
|
} else { |
235
|
|
|
$definition->setConfigurator(array($this->resolveServices($service['configurator'][0]), $service['configurator'][1])); |
236
|
|
|
} |
237
|
|
|
} |
238
|
|
|
|
239
|
|
View Code Duplication |
if (isset($service['calls'])) { |
|
|
|
|
240
|
|
|
if (!is_array($service['calls'])) { |
241
|
|
|
throw new InvalidArgumentException(sprintf('Parameter "calls" must be an array for service "%s" in %s. Check your YAML syntax.', $id, $file)); |
242
|
|
|
} |
243
|
|
|
|
244
|
|
|
foreach ($service['calls'] as $call) { |
245
|
|
|
if (isset($call['method'])) { |
246
|
|
|
$method = $call['method']; |
247
|
|
|
$args = isset($call['arguments']) ? $this->resolveServices($call['arguments']) : array(); |
248
|
|
|
} else { |
249
|
|
|
$method = $call[0]; |
250
|
|
|
$args = isset($call[1]) ? $this->resolveServices($call[1]) : array(); |
251
|
|
|
} |
252
|
|
|
|
253
|
|
|
$definition->addMethodCall($method, $args); |
254
|
|
|
} |
255
|
|
|
} |
256
|
|
|
|
257
|
|
View Code Duplication |
if (isset($service['tags'])) { |
|
|
|
|
258
|
|
|
if (!is_array($service['tags'])) { |
259
|
|
|
throw new InvalidArgumentException(sprintf('Parameter "tags" must be an array for service "%s" in %s. Check your YAML syntax.', $id, $file)); |
260
|
|
|
} |
261
|
|
|
|
262
|
|
|
foreach ($service['tags'] as $tag) { |
263
|
|
|
if (!is_array($tag)) { |
264
|
|
|
throw new InvalidArgumentException(sprintf('A "tags" entry must be an array for service "%s" in %s. Check your YAML syntax.', $id, $file)); |
265
|
|
|
} |
266
|
|
|
|
267
|
|
|
if (!isset($tag['name'])) { |
268
|
|
|
throw new InvalidArgumentException(sprintf('A "tags" entry is missing a "name" key for service "%s" in %s.', $id, $file)); |
269
|
|
|
} |
270
|
|
|
|
271
|
|
|
$name = $tag['name']; |
272
|
|
|
unset($tag['name']); |
273
|
|
|
|
274
|
|
|
foreach ($tag as $attribute => $value) { |
275
|
|
|
if (!is_scalar($value) && null !== $value) { |
276
|
|
|
throw new InvalidArgumentException(sprintf('A "tags" attribute must be of a scalar-type for service "%s", tag "%s", attribute "%s" in %s. Check your YAML syntax.', $id, $name, $attribute, $file)); |
277
|
|
|
} |
278
|
|
|
} |
279
|
|
|
|
280
|
|
|
$definition->addTag($name, $tag); |
281
|
|
|
} |
282
|
|
|
} |
283
|
|
|
|
284
|
|
View Code Duplication |
if (isset($service['decorates'])) { |
|
|
|
|
285
|
|
|
$renameId = isset($service['decoration_inner_name']) ? $service['decoration_inner_name'] : null; |
286
|
|
|
$definition->setDecoratedService($service['decorates'], $renameId); |
|
|
|
|
287
|
|
|
} |
288
|
|
|
|
289
|
|
|
$this->container->setDefinition($id, $definition); |
290
|
|
|
} |
291
|
|
|
|
292
|
|
|
/** |
293
|
|
|
* Loads a YAML file. |
294
|
|
|
* |
295
|
|
|
* @param string $file |
296
|
|
|
* |
297
|
|
|
* @return array The file content |
298
|
|
|
* |
299
|
|
|
* @throws InvalidArgumentException when the given file is not a local file or when it does not exist |
300
|
|
|
*/ |
301
|
|
|
protected function loadFile($file) |
302
|
|
|
{ |
303
|
|
|
if (!class_exists('Symfony\Component\Yaml\Parser')) { |
304
|
|
|
throw new RuntimeException('Unable to load YAML config files as the Symfony Yaml Component is not installed.'); |
305
|
|
|
} |
306
|
|
|
|
307
|
|
|
if (!stream_is_local($file)) { |
308
|
|
|
throw new InvalidArgumentException(sprintf('This is not a local file "%s".', $file)); |
309
|
|
|
} |
310
|
|
|
|
311
|
|
|
if (!file_exists($file)) { |
312
|
|
|
throw new InvalidArgumentException(sprintf('The service file "%s" is not valid.', $file)); |
313
|
|
|
} |
314
|
|
|
|
315
|
|
|
if (null === $this->yamlParser) { |
316
|
|
|
$this->yamlParser = new YamlParser(); |
317
|
|
|
} |
318
|
|
|
|
319
|
|
|
return $this->validate($this->yamlParser->parse(file_get_contents($file)), $file); |
320
|
|
|
} |
321
|
|
|
|
322
|
|
|
/** |
323
|
|
|
* Validates a YAML file. |
324
|
|
|
* |
325
|
|
|
* @param mixed $content |
326
|
|
|
* @param string $file |
327
|
|
|
* |
328
|
|
|
* @return array |
329
|
|
|
* |
330
|
|
|
* @throws InvalidArgumentException When service file is not valid |
331
|
|
|
*/ |
332
|
|
|
private function validate($content, $file) |
333
|
|
|
{ |
334
|
|
|
if (null === $content) { |
335
|
|
|
return $content; |
336
|
|
|
} |
337
|
|
|
|
338
|
|
|
if (!is_array($content)) { |
339
|
|
|
throw new InvalidArgumentException(sprintf('The service file "%s" is not valid. It should contain an array. Check your YAML syntax.', $file)); |
340
|
|
|
} |
341
|
|
|
|
342
|
|
|
foreach ($content as $namespace => $data) { |
343
|
|
|
if (in_array($namespace, array('imports', 'parameters', 'services'))) { |
344
|
|
|
continue; |
345
|
|
|
} |
346
|
|
|
|
347
|
|
View Code Duplication |
if (!$this->container->hasExtension($namespace)) { |
|
|
|
|
348
|
|
|
$extensionNamespaces = array_filter(array_map(function ($ext) { return $ext->getAlias(); }, $this->container->getExtensions())); |
349
|
|
|
throw new InvalidArgumentException(sprintf( |
350
|
|
|
'There is no extension able to load the configuration for "%s" (in %s). Looked for namespace "%s", found %s', |
351
|
|
|
$namespace, |
352
|
|
|
$file, |
353
|
|
|
$namespace, |
354
|
|
|
$extensionNamespaces ? sprintf('"%s"', implode('", "', $extensionNamespaces)) : 'none' |
355
|
|
|
)); |
356
|
|
|
} |
357
|
|
|
} |
358
|
|
|
|
359
|
|
|
return $content; |
360
|
|
|
} |
361
|
|
|
|
362
|
|
|
/** |
363
|
|
|
* Resolves services. |
364
|
|
|
* |
365
|
|
|
* @param string|array $value |
366
|
|
|
* |
367
|
|
|
* @return array|string|Reference |
368
|
|
|
*/ |
369
|
|
View Code Duplication |
private function resolveServices($value) |
370
|
|
|
{ |
371
|
|
|
if (is_array($value)) { |
372
|
|
|
$value = array_map(array($this, 'resolveServices'), $value); |
373
|
|
|
} elseif (is_string($value) && 0 === strpos($value, '@=')) { |
374
|
|
|
return new Expression(substr($value, 2)); |
375
|
|
|
} elseif (is_string($value) && 0 === strpos($value, '@')) { |
376
|
|
|
if (0 === strpos($value, '@@')) { |
377
|
|
|
$value = substr($value, 1); |
378
|
|
|
$invalidBehavior = null; |
379
|
|
|
} elseif (0 === strpos($value, '@?')) { |
380
|
|
|
$value = substr($value, 2); |
381
|
|
|
$invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE; |
382
|
|
|
} else { |
383
|
|
|
$value = substr($value, 1); |
384
|
|
|
$invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE; |
385
|
|
|
} |
386
|
|
|
|
387
|
|
|
if ('=' === substr($value, -1)) { |
388
|
|
|
$value = substr($value, 0, -1); |
389
|
|
|
$strict = false; |
390
|
|
|
} else { |
391
|
|
|
$strict = true; |
392
|
|
|
} |
393
|
|
|
|
394
|
|
|
if (null !== $invalidBehavior) { |
395
|
|
|
$value = new Reference($value, $invalidBehavior, $strict); |
396
|
|
|
} |
397
|
|
|
} |
398
|
|
|
|
399
|
|
|
return $value; |
400
|
|
|
} |
401
|
|
|
|
402
|
|
|
/** |
403
|
|
|
* Loads from Extensions. |
404
|
|
|
* |
405
|
|
|
* @param array $content |
406
|
|
|
*/ |
407
|
|
|
private function loadFromExtensions($content) |
408
|
|
|
{ |
409
|
|
|
foreach ($content as $namespace => $values) { |
410
|
|
|
if (in_array($namespace, array('imports', 'parameters', 'services'))) { |
411
|
|
|
continue; |
412
|
|
|
} |
413
|
|
|
|
414
|
|
|
if (!is_array($values)) { |
415
|
|
|
$values = array(); |
416
|
|
|
} |
417
|
|
|
|
418
|
|
|
$this->container->loadFromExtension($namespace, $values); |
419
|
|
|
} |
420
|
|
|
} |
421
|
|
|
} |
422
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.