cd9bcbba100876a45086b3f427fbe34942285f83
[yaffs-website] / vendor / symfony / dependency-injection / Loader / YamlFileLoader.php
1 <?php
2
3 /*
4  * This file is part of the Symfony package.
5  *
6  * (c) Fabien Potencier <fabien@symfony.com>
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\Exception\ParseException;
23 use Symfony\Component\Yaml\Parser as YamlParser;
24 use Symfony\Component\Yaml\Yaml;
25 use Symfony\Component\ExpressionLanguage\Expression;
26
27 /**
28  * YamlFileLoader loads YAML files service definitions.
29  *
30  * The YAML format does not support anonymous services (cf. the XML loader).
31  *
32  * @author Fabien Potencier <fabien@symfony.com>
33  */
34 class YamlFileLoader extends FileLoader
35 {
36     private static $keywords = array(
37         'alias' => 'alias',
38         'parent' => 'parent',
39         'class' => 'class',
40         'shared' => 'shared',
41         'synthetic' => 'synthetic',
42         'lazy' => 'lazy',
43         'public' => 'public',
44         'abstract' => 'abstract',
45         'deprecated' => 'deprecated',
46         'factory' => 'factory',
47         'file' => 'file',
48         'arguments' => 'arguments',
49         'properties' => 'properties',
50         'configurator' => 'configurator',
51         'calls' => 'calls',
52         'tags' => 'tags',
53         'decorates' => 'decorates',
54         'decoration_inner_name' => 'decoration_inner_name',
55         'decoration_priority' => 'decoration_priority',
56         'autowire' => 'autowire',
57         'autowiring_types' => 'autowiring_types',
58     );
59
60     private $yamlParser;
61
62     /**
63      * {@inheritdoc}
64      */
65     public function load($resource, $type = null)
66     {
67         $path = $this->locator->locate($resource);
68
69         $content = $this->loadFile($path);
70
71         $this->container->addResource(new FileResource($path));
72
73         // empty file
74         if (null === $content) {
75             return;
76         }
77
78         // imports
79         $this->parseImports($content, $path);
80
81         // parameters
82         if (isset($content['parameters'])) {
83             if (!is_array($content['parameters'])) {
84                 throw new InvalidArgumentException(sprintf('The "parameters" key should contain an array in %s. Check your YAML syntax.', $resource));
85             }
86
87             foreach ($content['parameters'] as $key => $value) {
88                 $this->container->setParameter($key, $this->resolveServices($value));
89             }
90         }
91
92         // extensions
93         $this->loadFromExtensions($content);
94
95         // services
96         $this->parseDefinitions($content, $resource);
97     }
98
99     /**
100      * {@inheritdoc}
101      */
102     public function supports($resource, $type = null)
103     {
104         return is_string($resource) && in_array(pathinfo($resource, PATHINFO_EXTENSION), array('yml', 'yaml'), true);
105     }
106
107     /**
108      * Parses all imports.
109      *
110      * @param array  $content
111      * @param string $file
112      */
113     private function parseImports(array $content, $file)
114     {
115         if (!isset($content['imports'])) {
116             return;
117         }
118
119         if (!is_array($content['imports'])) {
120             throw new InvalidArgumentException(sprintf('The "imports" key should contain an array in %s. Check your YAML syntax.', $file));
121         }
122
123         $defaultDirectory = dirname($file);
124         foreach ($content['imports'] as $import) {
125             if (!is_array($import)) {
126                 throw new InvalidArgumentException(sprintf('The values in the "imports" key should be arrays in %s. Check your YAML syntax.', $file));
127             }
128
129             $this->setCurrentDir($defaultDirectory);
130             $this->import($import['resource'], null, isset($import['ignore_errors']) ? (bool) $import['ignore_errors'] : false, $file);
131         }
132     }
133
134     /**
135      * Parses definitions.
136      *
137      * @param array  $content
138      * @param string $file
139      */
140     private function parseDefinitions(array $content, $file)
141     {
142         if (!isset($content['services'])) {
143             return;
144         }
145
146         if (!is_array($content['services'])) {
147             throw new InvalidArgumentException(sprintf('The "services" key should contain an array in %s. Check your YAML syntax.', $file));
148         }
149
150         foreach ($content['services'] as $id => $service) {
151             $this->parseDefinition($id, $service, $file);
152         }
153     }
154
155     /**
156      * Parses a definition.
157      *
158      * @param string       $id
159      * @param array|string $service
160      * @param string       $file
161      *
162      * @throws InvalidArgumentException When tags are invalid
163      */
164     private function parseDefinition($id, $service, $file)
165     {
166         if (is_string($service) && 0 === strpos($service, '@')) {
167             $this->container->setAlias($id, substr($service, 1));
168
169             return;
170         }
171
172         if (!is_array($service)) {
173             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));
174         }
175
176         static::checkDefinition($id, $service, $file);
177
178         if (isset($service['alias'])) {
179             $public = !array_key_exists('public', $service) || (bool) $service['public'];
180             $this->container->setAlias($id, new Alias($service['alias'], $public));
181
182             foreach ($service as $key => $value) {
183                 if (!in_array($key, array('alias', 'public'))) {
184                     @trigger_error(sprintf('The configuration key "%s" is unsupported for the service "%s" which is defined as an alias in "%s". Allowed configuration keys for service aliases are "alias" and "public". The YamlFileLoader will raise an exception in Symfony 4.0, instead of silently ignoring unsupported attributes.', $key, $id, $file), E_USER_DEPRECATED);
185                 }
186             }
187
188             return;
189         }
190
191         if (isset($service['parent'])) {
192             $definition = new DefinitionDecorator($service['parent']);
193         } else {
194             $definition = new Definition();
195         }
196
197         if (isset($service['class'])) {
198             $definition->setClass($service['class']);
199         }
200
201         if (isset($service['shared'])) {
202             $definition->setShared($service['shared']);
203         }
204
205         if (isset($service['synthetic'])) {
206             $definition->setSynthetic($service['synthetic']);
207         }
208
209         if (isset($service['lazy'])) {
210             $definition->setLazy($service['lazy']);
211         }
212
213         if (isset($service['public'])) {
214             $definition->setPublic($service['public']);
215         }
216
217         if (isset($service['abstract'])) {
218             $definition->setAbstract($service['abstract']);
219         }
220
221         if (array_key_exists('deprecated', $service)) {
222             $definition->setDeprecated(true, $service['deprecated']);
223         }
224
225         if (isset($service['factory'])) {
226             $definition->setFactory($this->parseCallable($service['factory'], 'factory', $id, $file));
227         }
228
229         if (isset($service['file'])) {
230             $definition->setFile($service['file']);
231         }
232
233         if (isset($service['arguments'])) {
234             $definition->setArguments($this->resolveServices($service['arguments']));
235         }
236
237         if (isset($service['properties'])) {
238             $definition->setProperties($this->resolveServices($service['properties']));
239         }
240
241         if (isset($service['configurator'])) {
242             $definition->setConfigurator($this->parseCallable($service['configurator'], 'configurator', $id, $file));
243         }
244
245         if (isset($service['calls'])) {
246             if (!is_array($service['calls'])) {
247                 throw new InvalidArgumentException(sprintf('Parameter "calls" must be an array for service "%s" in %s. Check your YAML syntax.', $id, $file));
248             }
249
250             foreach ($service['calls'] as $call) {
251                 if (isset($call['method'])) {
252                     $method = $call['method'];
253                     $args = isset($call['arguments']) ? $this->resolveServices($call['arguments']) : array();
254                 } else {
255                     $method = $call[0];
256                     $args = isset($call[1]) ? $this->resolveServices($call[1]) : array();
257                 }
258
259                 $definition->addMethodCall($method, $args);
260             }
261         }
262
263         if (isset($service['tags'])) {
264             if (!is_array($service['tags'])) {
265                 throw new InvalidArgumentException(sprintf('Parameter "tags" must be an array for service "%s" in %s. Check your YAML syntax.', $id, $file));
266             }
267
268             foreach ($service['tags'] as $tag) {
269                 if (!is_array($tag)) {
270                     throw new InvalidArgumentException(sprintf('A "tags" entry must be an array for service "%s" in %s. Check your YAML syntax.', $id, $file));
271                 }
272
273                 if (!isset($tag['name'])) {
274                     throw new InvalidArgumentException(sprintf('A "tags" entry is missing a "name" key for service "%s" in %s.', $id, $file));
275                 }
276
277                 if (!is_string($tag['name']) || '' === $tag['name']) {
278                     throw new InvalidArgumentException(sprintf('The tag name for service "%s" in %s must be a non-empty string.', $id, $file));
279                 }
280
281                 $name = $tag['name'];
282                 unset($tag['name']);
283
284                 foreach ($tag as $attribute => $value) {
285                     if (!is_scalar($value) && null !== $value) {
286                         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));
287                     }
288                 }
289
290                 $definition->addTag($name, $tag);
291             }
292         }
293
294         if (isset($service['decorates'])) {
295             if ('' !== $service['decorates'] && '@' === $service['decorates'][0]) {
296                 throw new InvalidArgumentException(sprintf('The value of the "decorates" option for the "%s" service must be the id of the service without the "@" prefix (replace "%s" with "%s").', $id, $service['decorates'], substr($service['decorates'], 1)));
297             }
298
299             $renameId = isset($service['decoration_inner_name']) ? $service['decoration_inner_name'] : null;
300             $priority = isset($service['decoration_priority']) ? $service['decoration_priority'] : 0;
301             $definition->setDecoratedService($service['decorates'], $renameId, $priority);
302         }
303
304         if (isset($service['autowire'])) {
305             $definition->setAutowired($service['autowire']);
306         }
307
308         if (isset($service['autowiring_types'])) {
309             if (is_string($service['autowiring_types'])) {
310                 $definition->addAutowiringType($service['autowiring_types']);
311             } else {
312                 if (!is_array($service['autowiring_types'])) {
313                     throw new InvalidArgumentException(sprintf('Parameter "autowiring_types" must be a string or an array for service "%s" in %s. Check your YAML syntax.', $id, $file));
314                 }
315
316                 foreach ($service['autowiring_types'] as $autowiringType) {
317                     if (!is_string($autowiringType)) {
318                         throw new InvalidArgumentException(sprintf('A "autowiring_types" attribute must be of type string for service "%s" in %s. Check your YAML syntax.', $id, $file));
319                     }
320
321                     $definition->addAutowiringType($autowiringType);
322                 }
323             }
324         }
325
326         $this->container->setDefinition($id, $definition);
327     }
328
329     /**
330      * Parses a callable.
331      *
332      * @param string|array $callable  A callable
333      * @param string       $parameter A parameter (e.g. 'factory' or 'configurator')
334      * @param string       $id        A service identifier
335      * @param string       $file      A parsed file
336      *
337      * @throws InvalidArgumentException When errors are occuried
338      *
339      * @return string|array A parsed callable
340      */
341     private function parseCallable($callable, $parameter, $id, $file)
342     {
343         if (is_string($callable)) {
344             if ('' !== $callable && '@' === $callable[0]) {
345                 throw new InvalidArgumentException(sprintf('The value of the "%s" option for the "%s" service must be the id of the service without the "@" prefix (replace "%s" with "%s").', $parameter, $id, $callable, substr($callable, 1)));
346             }
347
348             if (false !== strpos($callable, ':') && false === strpos($callable, '::')) {
349                 $parts = explode(':', $callable);
350
351                 return array($this->resolveServices('@'.$parts[0]), $parts[1]);
352             }
353
354             return $callable;
355         }
356
357         if (is_array($callable)) {
358             if (isset($callable[0]) && isset($callable[1])) {
359                 return array($this->resolveServices($callable[0]), $callable[1]);
360             }
361
362             throw new InvalidArgumentException(sprintf('Parameter "%s" must contain an array with two elements for service "%s" in %s. Check your YAML syntax.', $parameter, $id, $file));
363         }
364
365         throw new InvalidArgumentException(sprintf('Parameter "%s" must be a string or an array for service "%s" in %s. Check your YAML syntax.', $parameter, $id, $file));
366     }
367
368     /**
369      * Loads a YAML file.
370      *
371      * @param string $file
372      *
373      * @return array The file content
374      *
375      * @throws InvalidArgumentException when the given file is not a local file or when it does not exist
376      */
377     protected function loadFile($file)
378     {
379         if (!class_exists('Symfony\Component\Yaml\Parser')) {
380             throw new RuntimeException('Unable to load YAML config files as the Symfony Yaml Component is not installed.');
381         }
382
383         if (!stream_is_local($file)) {
384             throw new InvalidArgumentException(sprintf('This is not a local file "%s".', $file));
385         }
386
387         if (!file_exists($file)) {
388             throw new InvalidArgumentException(sprintf('The file "%s" does not exist.', $file));
389         }
390
391         if (null === $this->yamlParser) {
392             $this->yamlParser = new YamlParser();
393         }
394
395         try {
396             $configuration = $this->yamlParser->parse(file_get_contents($file), Yaml::PARSE_CONSTANT);
397         } catch (ParseException $e) {
398             throw new InvalidArgumentException(sprintf('The file "%s" does not contain valid YAML.', $file), 0, $e);
399         }
400
401         return $this->validate($configuration, $file);
402     }
403
404     /**
405      * Validates a YAML file.
406      *
407      * @param mixed  $content
408      * @param string $file
409      *
410      * @return array
411      *
412      * @throws InvalidArgumentException When service file is not valid
413      */
414     private function validate($content, $file)
415     {
416         if (null === $content) {
417             return $content;
418         }
419
420         if (!is_array($content)) {
421             throw new InvalidArgumentException(sprintf('The service file "%s" is not valid. It should contain an array. Check your YAML syntax.', $file));
422         }
423
424         foreach ($content as $namespace => $data) {
425             if (in_array($namespace, array('imports', 'parameters', 'services'))) {
426                 continue;
427             }
428
429             if (!$this->container->hasExtension($namespace)) {
430                 $extensionNamespaces = array_filter(array_map(function ($ext) { return $ext->getAlias(); }, $this->container->getExtensions()));
431                 throw new InvalidArgumentException(sprintf(
432                     'There is no extension able to load the configuration for "%s" (in %s). Looked for namespace "%s", found %s',
433                     $namespace,
434                     $file,
435                     $namespace,
436                     $extensionNamespaces ? sprintf('"%s"', implode('", "', $extensionNamespaces)) : 'none'
437                 ));
438             }
439         }
440
441         return $content;
442     }
443
444     /**
445      * Resolves services.
446      *
447      * @param string|array $value
448      *
449      * @return array|string|Reference
450      */
451     private function resolveServices($value)
452     {
453         if (is_array($value)) {
454             $value = array_map(array($this, 'resolveServices'), $value);
455         } elseif (is_string($value) && 0 === strpos($value, '@=')) {
456             return new Expression(substr($value, 2));
457         } elseif (is_string($value) && 0 === strpos($value, '@')) {
458             if (0 === strpos($value, '@@')) {
459                 $value = substr($value, 1);
460                 $invalidBehavior = null;
461             } elseif (0 === strpos($value, '@?')) {
462                 $value = substr($value, 2);
463                 $invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE;
464             } else {
465                 $value = substr($value, 1);
466                 $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
467             }
468
469             if ('=' === substr($value, -1)) {
470                 $value = substr($value, 0, -1);
471             }
472
473             if (null !== $invalidBehavior) {
474                 $value = new Reference($value, $invalidBehavior);
475             }
476         }
477
478         return $value;
479     }
480
481     /**
482      * Loads from Extensions.
483      *
484      * @param array $content
485      */
486     private function loadFromExtensions(array $content)
487     {
488         foreach ($content as $namespace => $values) {
489             if (in_array($namespace, array('imports', 'parameters', 'services'))) {
490                 continue;
491             }
492
493             if (!is_array($values)) {
494                 $values = array();
495             }
496
497             $this->container->loadFromExtension($namespace, $values);
498         }
499     }
500
501     /**
502      * Checks the keywords used to define a service.
503      *
504      * @param string $id         The service name
505      * @param array  $definition The service definition to check
506      * @param string $file       The loaded YAML file
507      */
508     private static function checkDefinition($id, array $definition, $file)
509     {
510         foreach ($definition as $key => $value) {
511             if (!isset(static::$keywords[$key])) {
512                 @trigger_error(sprintf('The configuration key "%s" is unsupported for service definition "%s" in "%s". Allowed configuration keys are "%s". The YamlFileLoader object will raise an exception instead in Symfony 4.0 when detecting an unsupported service configuration key.', $key, $id, $file, implode('", "', static::$keywords)), E_USER_DEPRECATED);
513                 // @deprecated Uncomment the following statement in Symfony 4.0
514                 // and also update the corresponding unit test to make it expect
515                 // an InvalidArgumentException exception.
516                 //throw new InvalidArgumentException(sprintf('The configuration key "%s" is unsupported for service definition "%s" in "%s". Allowed configuration keys are "%s".', $key, $id, $file, implode('", "', static::$keywords)));
517             }
518         }
519     }
520 }