Further Drupal 8.6.4 changes. Some core files were not committed before a commit...
[yaffs-website] / vendor / symfony / dependency-injection / Loader / XmlFileLoader.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\Config\Util\XmlUtils;
15 use Symfony\Component\DependencyInjection\Alias;
16 use Symfony\Component\DependencyInjection\Argument\BoundArgument;
17 use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
18 use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
19 use Symfony\Component\DependencyInjection\ChildDefinition;
20 use Symfony\Component\DependencyInjection\ContainerBuilder;
21 use Symfony\Component\DependencyInjection\ContainerInterface;
22 use Symfony\Component\DependencyInjection\Definition;
23 use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
24 use Symfony\Component\DependencyInjection\Exception\RuntimeException;
25 use Symfony\Component\DependencyInjection\Reference;
26 use Symfony\Component\ExpressionLanguage\Expression;
27
28 /**
29  * XmlFileLoader loads XML files service definitions.
30  *
31  * @author Fabien Potencier <fabien@symfony.com>
32  */
33 class XmlFileLoader extends FileLoader
34 {
35     const NS = 'http://symfony.com/schema/dic/services';
36
37     /**
38      * {@inheritdoc}
39      */
40     public function load($resource, $type = null)
41     {
42         $path = $this->locator->locate($resource);
43
44         $xml = $this->parseFileToDOM($path);
45
46         $this->container->fileExists($path);
47
48         $defaults = $this->getServiceDefaults($xml, $path);
49
50         // anonymous services
51         $this->processAnonymousServices($xml, $path, $defaults);
52
53         // imports
54         $this->parseImports($xml, $path);
55
56         // parameters
57         $this->parseParameters($xml, $path);
58
59         // extensions
60         $this->loadFromExtensions($xml);
61
62         // services
63         try {
64             $this->parseDefinitions($xml, $path, $defaults);
65         } finally {
66             $this->instanceof = array();
67         }
68     }
69
70     /**
71      * {@inheritdoc}
72      */
73     public function supports($resource, $type = null)
74     {
75         if (!\is_string($resource)) {
76             return false;
77         }
78
79         if (null === $type && 'xml' === pathinfo($resource, PATHINFO_EXTENSION)) {
80             return true;
81         }
82
83         return 'xml' === $type;
84     }
85
86     /**
87      * Parses parameters.
88      *
89      * @param \DOMDocument $xml
90      * @param string       $file
91      */
92     private function parseParameters(\DOMDocument $xml, $file)
93     {
94         if ($parameters = $this->getChildren($xml->documentElement, 'parameters')) {
95             $this->container->getParameterBag()->add($this->getArgumentsAsPhp($parameters[0], 'parameter', $file));
96         }
97     }
98
99     /**
100      * Parses imports.
101      *
102      * @param \DOMDocument $xml
103      * @param string       $file
104      */
105     private function parseImports(\DOMDocument $xml, $file)
106     {
107         $xpath = new \DOMXPath($xml);
108         $xpath->registerNamespace('container', self::NS);
109
110         if (false === $imports = $xpath->query('//container:imports/container:import')) {
111             return;
112         }
113
114         $defaultDirectory = \dirname($file);
115         foreach ($imports as $import) {
116             $this->setCurrentDir($defaultDirectory);
117             $this->import($import->getAttribute('resource'), XmlUtils::phpize($import->getAttribute('type')) ?: null, (bool) XmlUtils::phpize($import->getAttribute('ignore-errors')), $file);
118         }
119     }
120
121     /**
122      * Parses multiple definitions.
123      *
124      * @param \DOMDocument $xml
125      * @param string       $file
126      */
127     private function parseDefinitions(\DOMDocument $xml, $file, $defaults)
128     {
129         $xpath = new \DOMXPath($xml);
130         $xpath->registerNamespace('container', self::NS);
131
132         if (false === $services = $xpath->query('//container:services/container:service|//container:services/container:prototype')) {
133             return;
134         }
135         $this->setCurrentDir(\dirname($file));
136
137         $this->instanceof = array();
138         $this->isLoadingInstanceof = true;
139         $instanceof = $xpath->query('//container:services/container:instanceof');
140         foreach ($instanceof as $service) {
141             $this->setDefinition((string) $service->getAttribute('id'), $this->parseDefinition($service, $file, array()));
142         }
143
144         $this->isLoadingInstanceof = false;
145         foreach ($services as $service) {
146             if (null !== $definition = $this->parseDefinition($service, $file, $defaults)) {
147                 if ('prototype' === $service->tagName) {
148                     $this->registerClasses($definition, (string) $service->getAttribute('namespace'), (string) $service->getAttribute('resource'), (string) $service->getAttribute('exclude'));
149                 } else {
150                     $this->setDefinition((string) $service->getAttribute('id'), $definition);
151                 }
152             }
153         }
154     }
155
156     /**
157      * Get service defaults.
158      *
159      * @return array
160      */
161     private function getServiceDefaults(\DOMDocument $xml, $file)
162     {
163         $xpath = new \DOMXPath($xml);
164         $xpath->registerNamespace('container', self::NS);
165
166         if (null === $defaultsNode = $xpath->query('//container:services/container:defaults')->item(0)) {
167             return array();
168         }
169         $defaults = array(
170             'tags' => $this->getChildren($defaultsNode, 'tag'),
171             'bind' => array_map(function ($v) { return new BoundArgument($v); }, $this->getArgumentsAsPhp($defaultsNode, 'bind', $file)),
172         );
173
174         foreach ($defaults['tags'] as $tag) {
175             if ('' === $tag->getAttribute('name')) {
176                 throw new InvalidArgumentException(sprintf('The tag name for tag "<defaults>" in %s must be a non-empty string.', $file));
177             }
178         }
179
180         if ($defaultsNode->hasAttribute('autowire')) {
181             $defaults['autowire'] = XmlUtils::phpize($defaultsNode->getAttribute('autowire'));
182         }
183         if ($defaultsNode->hasAttribute('public')) {
184             $defaults['public'] = XmlUtils::phpize($defaultsNode->getAttribute('public'));
185         }
186         if ($defaultsNode->hasAttribute('autoconfigure')) {
187             $defaults['autoconfigure'] = XmlUtils::phpize($defaultsNode->getAttribute('autoconfigure'));
188         }
189
190         return $defaults;
191     }
192
193     /**
194      * Parses an individual Definition.
195      *
196      * @param \DOMElement $service
197      * @param string      $file
198      * @param array       $defaults
199      *
200      * @return Definition|null
201      */
202     private function parseDefinition(\DOMElement $service, $file, array $defaults)
203     {
204         if ($alias = $service->getAttribute('alias')) {
205             $this->validateAlias($service, $file);
206
207             $this->container->setAlias((string) $service->getAttribute('id'), $alias = new Alias($alias));
208             if ($publicAttr = $service->getAttribute('public')) {
209                 $alias->setPublic(XmlUtils::phpize($publicAttr));
210             } elseif (isset($defaults['public'])) {
211                 $alias->setPublic($defaults['public']);
212             }
213
214             return;
215         }
216
217         if ($this->isLoadingInstanceof) {
218             $definition = new ChildDefinition('');
219         } elseif ($parent = $service->getAttribute('parent')) {
220             if (!empty($this->instanceof)) {
221                 throw new InvalidArgumentException(sprintf('The service "%s" cannot use the "parent" option in the same file where "instanceof" configuration is defined as using both is not supported. Move your child definitions to a separate file.', $service->getAttribute('id')));
222             }
223
224             foreach ($defaults as $k => $v) {
225                 if ('tags' === $k) {
226                     // since tags are never inherited from parents, there is no confusion
227                     // thus we can safely add them as defaults to ChildDefinition
228                     continue;
229                 }
230                 if ('bind' === $k) {
231                     if ($defaults['bind']) {
232                         throw new InvalidArgumentException(sprintf('Bound values on service "%s" cannot be inherited from "defaults" when a "parent" is set. Move your child definitions to a separate file.', $service->getAttribute('id')));
233                     }
234
235                     continue;
236                 }
237                 if (!$service->hasAttribute($k)) {
238                     throw new InvalidArgumentException(sprintf('Attribute "%s" on service "%s" cannot be inherited from "defaults" when a "parent" is set. Move your child definitions to a separate file or define this attribute explicitly.', $k, $service->getAttribute('id')));
239                 }
240             }
241
242             $definition = new ChildDefinition($parent);
243         } else {
244             $definition = new Definition();
245
246             if (isset($defaults['public'])) {
247                 $definition->setPublic($defaults['public']);
248             }
249             if (isset($defaults['autowire'])) {
250                 $definition->setAutowired($defaults['autowire']);
251             }
252             if (isset($defaults['autoconfigure'])) {
253                 $definition->setAutoconfigured($defaults['autoconfigure']);
254             }
255
256             $definition->setChanges(array());
257         }
258
259         foreach (array('class', 'public', 'shared', 'synthetic', 'lazy', 'abstract') as $key) {
260             if ($value = $service->getAttribute($key)) {
261                 $method = 'set'.$key;
262                 $definition->$method(XmlUtils::phpize($value));
263             }
264         }
265
266         if ($value = $service->getAttribute('autowire')) {
267             $definition->setAutowired(XmlUtils::phpize($value));
268         }
269
270         if ($value = $service->getAttribute('autoconfigure')) {
271             if (!$definition instanceof ChildDefinition) {
272                 $definition->setAutoconfigured(XmlUtils::phpize($value));
273             } elseif ($value = XmlUtils::phpize($value)) {
274                 throw new InvalidArgumentException(sprintf('The service "%s" cannot have a "parent" and also have "autoconfigure". Try setting autoconfigure="false" for the service.', $service->getAttribute('id')));
275             }
276         }
277
278         if ($files = $this->getChildren($service, 'file')) {
279             $definition->setFile($files[0]->nodeValue);
280         }
281
282         if ($deprecated = $this->getChildren($service, 'deprecated')) {
283             $definition->setDeprecated(true, $deprecated[0]->nodeValue ?: null);
284         }
285
286         $definition->setArguments($this->getArgumentsAsPhp($service, 'argument', $file, false, $definition instanceof ChildDefinition));
287         $definition->setProperties($this->getArgumentsAsPhp($service, 'property', $file));
288
289         if ($factories = $this->getChildren($service, 'factory')) {
290             $factory = $factories[0];
291             if ($function = $factory->getAttribute('function')) {
292                 $definition->setFactory($function);
293             } else {
294                 if ($childService = $factory->getAttribute('service')) {
295                     $class = new Reference($childService, ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE);
296                 } else {
297                     $class = $factory->hasAttribute('class') ? $factory->getAttribute('class') : null;
298                 }
299
300                 $definition->setFactory(array($class, $factory->getAttribute('method')));
301             }
302         }
303
304         if ($configurators = $this->getChildren($service, 'configurator')) {
305             $configurator = $configurators[0];
306             if ($function = $configurator->getAttribute('function')) {
307                 $definition->setConfigurator($function);
308             } else {
309                 if ($childService = $configurator->getAttribute('service')) {
310                     $class = new Reference($childService, ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE);
311                 } else {
312                     $class = $configurator->getAttribute('class');
313                 }
314
315                 $definition->setConfigurator(array($class, $configurator->getAttribute('method')));
316             }
317         }
318
319         foreach ($this->getChildren($service, 'call') as $call) {
320             $definition->addMethodCall($call->getAttribute('method'), $this->getArgumentsAsPhp($call, 'argument', $file));
321         }
322
323         $tags = $this->getChildren($service, 'tag');
324
325         if (!empty($defaults['tags'])) {
326             $tags = array_merge($tags, $defaults['tags']);
327         }
328
329         foreach ($tags as $tag) {
330             $parameters = array();
331             foreach ($tag->attributes as $name => $node) {
332                 if ('name' === $name) {
333                     continue;
334                 }
335
336                 if (false !== strpos($name, '-') && false === strpos($name, '_') && !array_key_exists($normalizedName = str_replace('-', '_', $name), $parameters)) {
337                     $parameters[$normalizedName] = XmlUtils::phpize($node->nodeValue);
338                 }
339                 // keep not normalized key
340                 $parameters[$name] = XmlUtils::phpize($node->nodeValue);
341             }
342
343             if ('' === $tag->getAttribute('name')) {
344                 throw new InvalidArgumentException(sprintf('The tag name for service "%s" in %s must be a non-empty string.', (string) $service->getAttribute('id'), $file));
345             }
346
347             $definition->addTag($tag->getAttribute('name'), $parameters);
348         }
349
350         foreach ($this->getChildren($service, 'autowiring-type') as $type) {
351             $definition->addAutowiringType($type->textContent);
352         }
353
354         $bindings = $this->getArgumentsAsPhp($service, 'bind', $file);
355         if (isset($defaults['bind'])) {
356             // deep clone, to avoid multiple process of the same instance in the passes
357             $bindings = array_merge(unserialize(serialize($defaults['bind'])), $bindings);
358         }
359         if ($bindings) {
360             $definition->setBindings($bindings);
361         }
362
363         if ($value = $service->getAttribute('decorates')) {
364             $renameId = $service->hasAttribute('decoration-inner-name') ? $service->getAttribute('decoration-inner-name') : null;
365             $priority = $service->hasAttribute('decoration-priority') ? $service->getAttribute('decoration-priority') : 0;
366             $definition->setDecoratedService($value, $renameId, $priority);
367         }
368
369         return $definition;
370     }
371
372     /**
373      * Parses a XML file to a \DOMDocument.
374      *
375      * @param string $file Path to a file
376      *
377      * @return \DOMDocument
378      *
379      * @throws InvalidArgumentException When loading of XML file returns error
380      */
381     private function parseFileToDOM($file)
382     {
383         try {
384             $dom = XmlUtils::loadFile($file, array($this, 'validateSchema'));
385         } catch (\InvalidArgumentException $e) {
386             throw new InvalidArgumentException(sprintf('Unable to parse file "%s".', $file), $e->getCode(), $e);
387         }
388
389         $this->validateExtensions($dom, $file);
390
391         return $dom;
392     }
393
394     /**
395      * Processes anonymous services.
396      *
397      * @param \DOMDocument $xml
398      * @param string       $file
399      * @param array        $defaults
400      */
401     private function processAnonymousServices(\DOMDocument $xml, $file, $defaults)
402     {
403         $definitions = array();
404         $count = 0;
405         $suffix = '~'.ContainerBuilder::hash($file);
406
407         $xpath = new \DOMXPath($xml);
408         $xpath->registerNamespace('container', self::NS);
409
410         // anonymous services as arguments/properties
411         if (false !== $nodes = $xpath->query('//container:argument[@type="service"][not(@id)]|//container:property[@type="service"][not(@id)]|//container:bind[not(@id)]|//container:factory[not(@service)]|//container:configurator[not(@service)]')) {
412             foreach ($nodes as $node) {
413                 if ($services = $this->getChildren($node, 'service')) {
414                     // give it a unique name
415                     $id = sprintf('%d_%s', ++$count, preg_replace('/^.*\\\\/', '', $services[0]->getAttribute('class')).$suffix);
416                     $node->setAttribute('id', $id);
417                     $node->setAttribute('service', $id);
418
419                     $definitions[$id] = array($services[0], $file, false);
420                     $services[0]->setAttribute('id', $id);
421
422                     // anonymous services are always private
423                     // we could not use the constant false here, because of XML parsing
424                     $services[0]->setAttribute('public', 'false');
425                 }
426             }
427         }
428
429         // anonymous services "in the wild"
430         if (false !== $nodes = $xpath->query('//container:services/container:service[not(@id)]')) {
431             foreach ($nodes as $node) {
432                 @trigger_error(sprintf('Top-level anonymous services are deprecated since Symfony 3.4, the "id" attribute will be required in version 4.0 in %s at line %d.', $file, $node->getLineNo()), E_USER_DEPRECATED);
433
434                 // give it a unique name
435                 $id = sprintf('%d_%s', ++$count, preg_replace('/^.*\\\\/', '', $node->getAttribute('class')).$suffix);
436                 $node->setAttribute('id', $id);
437                 $definitions[$id] = array($node, $file, true);
438             }
439         }
440
441         // resolve definitions
442         uksort($definitions, 'strnatcmp');
443         foreach (array_reverse($definitions) as $id => list($domElement, $file, $wild)) {
444             if (null !== $definition = $this->parseDefinition($domElement, $file, $wild ? $defaults : array())) {
445                 $this->setDefinition($id, $definition);
446             }
447
448             if (true === $wild) {
449                 $tmpDomElement = new \DOMElement('_services', null, self::NS);
450                 $domElement->parentNode->replaceChild($tmpDomElement, $domElement);
451                 $tmpDomElement->setAttribute('id', $id);
452             }
453         }
454     }
455
456     /**
457      * Returns arguments as valid php types.
458      *
459      * @param \DOMElement $node
460      * @param string      $name
461      * @param string      $file
462      * @param bool        $lowercase
463      *
464      * @return mixed
465      */
466     private function getArgumentsAsPhp(\DOMElement $node, $name, $file, $lowercase = true, $isChildDefinition = false)
467     {
468         $arguments = array();
469         foreach ($this->getChildren($node, $name) as $arg) {
470             if ($arg->hasAttribute('name')) {
471                 $arg->setAttribute('key', $arg->getAttribute('name'));
472             }
473
474             // this is used by ChildDefinition to overwrite a specific
475             // argument of the parent definition
476             if ($arg->hasAttribute('index')) {
477                 $key = ($isChildDefinition ? 'index_' : '').$arg->getAttribute('index');
478             } elseif (!$arg->hasAttribute('key')) {
479                 // Append an empty argument, then fetch its key to overwrite it later
480                 $arguments[] = null;
481                 $keys = array_keys($arguments);
482                 $key = array_pop($keys);
483             } else {
484                 $key = $arg->getAttribute('key');
485             }
486
487             $onInvalid = $arg->getAttribute('on-invalid');
488             $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
489             if ('ignore' == $onInvalid) {
490                 $invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE;
491             } elseif ('ignore_uninitialized' == $onInvalid) {
492                 $invalidBehavior = ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE;
493             } elseif ('null' == $onInvalid) {
494                 $invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE;
495             }
496
497             switch ($arg->getAttribute('type')) {
498                 case 'service':
499                     if (!$arg->getAttribute('id')) {
500                         throw new InvalidArgumentException(sprintf('Tag "<%s>" with type="service" has no or empty "id" attribute in "%s".', $name, $file));
501                     }
502                     if ($arg->hasAttribute('strict')) {
503                         @trigger_error(sprintf('The "strict" attribute used when referencing the "%s" service is deprecated since Symfony 3.3 and will be removed in 4.0.', $arg->getAttribute('id')), E_USER_DEPRECATED);
504                     }
505
506                     $arguments[$key] = new Reference($arg->getAttribute('id'), $invalidBehavior);
507                     break;
508                 case 'expression':
509                     if (!class_exists(Expression::class)) {
510                         throw new \LogicException(sprintf('The type="expression" attribute cannot be used without the ExpressionLanguage component. Try running "composer require symfony/expression-language".'));
511                     }
512
513                     $arguments[$key] = new Expression($arg->nodeValue);
514                     break;
515                 case 'collection':
516                     $arguments[$key] = $this->getArgumentsAsPhp($arg, $name, $file, false);
517                     break;
518                 case 'iterator':
519                     $arg = $this->getArgumentsAsPhp($arg, $name, $file, false);
520                     try {
521                         $arguments[$key] = new IteratorArgument($arg);
522                     } catch (InvalidArgumentException $e) {
523                         throw new InvalidArgumentException(sprintf('Tag "<%s>" with type="iterator" only accepts collections of type="service" references in "%s".', $name, $file));
524                     }
525                     break;
526                 case 'tagged':
527                     if (!$arg->getAttribute('tag')) {
528                         throw new InvalidArgumentException(sprintf('Tag "<%s>" with type="tagged" has no or empty "tag" attribute in "%s".', $name, $file));
529                     }
530                     $arguments[$key] = new TaggedIteratorArgument($arg->getAttribute('tag'));
531                     break;
532                 case 'string':
533                     $arguments[$key] = $arg->nodeValue;
534                     break;
535                 case 'constant':
536                     $arguments[$key] = \constant(trim($arg->nodeValue));
537                     break;
538                 default:
539                     $arguments[$key] = XmlUtils::phpize($arg->nodeValue);
540             }
541         }
542
543         return $arguments;
544     }
545
546     /**
547      * Get child elements by name.
548      *
549      * @param \DOMNode $node
550      * @param mixed    $name
551      *
552      * @return array
553      */
554     private function getChildren(\DOMNode $node, $name)
555     {
556         $children = array();
557         foreach ($node->childNodes as $child) {
558             if ($child instanceof \DOMElement && $child->localName === $name && self::NS === $child->namespaceURI) {
559                 $children[] = $child;
560             }
561         }
562
563         return $children;
564     }
565
566     /**
567      * Validates a documents XML schema.
568      *
569      * @param \DOMDocument $dom
570      *
571      * @return bool
572      *
573      * @throws RuntimeException When extension references a non-existent XSD file
574      */
575     public function validateSchema(\DOMDocument $dom)
576     {
577         $schemaLocations = array('http://symfony.com/schema/dic/services' => str_replace('\\', '/', __DIR__.'/schema/dic/services/services-1.0.xsd'));
578
579         if ($element = $dom->documentElement->getAttributeNS('http://www.w3.org/2001/XMLSchema-instance', 'schemaLocation')) {
580             $items = preg_split('/\s+/', $element);
581             for ($i = 0, $nb = \count($items); $i < $nb; $i += 2) {
582                 if (!$this->container->hasExtension($items[$i])) {
583                     continue;
584                 }
585
586                 if (($extension = $this->container->getExtension($items[$i])) && false !== $extension->getXsdValidationBasePath()) {
587                     $path = str_replace($extension->getNamespace(), str_replace('\\', '/', $extension->getXsdValidationBasePath()).'/', $items[$i + 1]);
588
589                     if (!is_file($path)) {
590                         throw new RuntimeException(sprintf('Extension "%s" references a non-existent XSD file "%s"', \get_class($extension), $path));
591                     }
592
593                     $schemaLocations[$items[$i]] = $path;
594                 }
595             }
596         }
597
598         $tmpfiles = array();
599         $imports = '';
600         foreach ($schemaLocations as $namespace => $location) {
601             $parts = explode('/', $location);
602             $locationstart = 'file:///';
603             if (0 === stripos($location, 'phar://')) {
604                 $tmpfile = tempnam(sys_get_temp_dir(), 'symfony');
605                 if ($tmpfile) {
606                     copy($location, $tmpfile);
607                     $tmpfiles[] = $tmpfile;
608                     $parts = explode('/', str_replace('\\', '/', $tmpfile));
609                 } else {
610                     array_shift($parts);
611                     $locationstart = 'phar:///';
612                 }
613             }
614             $drive = '\\' === \DIRECTORY_SEPARATOR ? array_shift($parts).'/' : '';
615             $location = $locationstart.$drive.implode('/', array_map('rawurlencode', $parts));
616
617             $imports .= sprintf('  <xsd:import namespace="%s" schemaLocation="%s" />'."\n", $namespace, $location);
618         }
619
620         $source = <<<EOF
621 <?xml version="1.0" encoding="utf-8" ?>
622 <xsd:schema xmlns="http://symfony.com/schema"
623     xmlns:xsd="http://www.w3.org/2001/XMLSchema"
624     targetNamespace="http://symfony.com/schema"
625     elementFormDefault="qualified">
626
627     <xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
628 $imports
629 </xsd:schema>
630 EOF
631         ;
632
633         $disableEntities = libxml_disable_entity_loader(false);
634         $valid = @$dom->schemaValidateSource($source);
635         libxml_disable_entity_loader($disableEntities);
636
637         foreach ($tmpfiles as $tmpfile) {
638             @unlink($tmpfile);
639         }
640
641         return $valid;
642     }
643
644     /**
645      * Validates an alias.
646      *
647      * @param \DOMElement $alias
648      * @param string      $file
649      */
650     private function validateAlias(\DOMElement $alias, $file)
651     {
652         foreach ($alias->attributes as $name => $node) {
653             if (!\in_array($name, array('alias', 'id', 'public'))) {
654                 @trigger_error(sprintf('Using the attribute "%s" is deprecated for the service "%s" which is defined as an alias in "%s". Allowed attributes for service aliases are "alias", "id" and "public". The XmlFileLoader will raise an exception in Symfony 4.0, instead of silently ignoring unsupported attributes.', $name, $alias->getAttribute('id'), $file), E_USER_DEPRECATED);
655             }
656         }
657
658         foreach ($alias->childNodes as $child) {
659             if ($child instanceof \DOMElement && self::NS === $child->namespaceURI) {
660                 @trigger_error(sprintf('Using the element "%s" is deprecated for the service "%s" which is defined as an alias in "%s". The XmlFileLoader will raise an exception in Symfony 4.0, instead of silently ignoring unsupported elements.', $child->localName, $alias->getAttribute('id'), $file), E_USER_DEPRECATED);
661             }
662         }
663     }
664
665     /**
666      * Validates an extension.
667      *
668      * @param \DOMDocument $dom
669      * @param string       $file
670      *
671      * @throws InvalidArgumentException When no extension is found corresponding to a tag
672      */
673     private function validateExtensions(\DOMDocument $dom, $file)
674     {
675         foreach ($dom->documentElement->childNodes as $node) {
676             if (!$node instanceof \DOMElement || 'http://symfony.com/schema/dic/services' === $node->namespaceURI) {
677                 continue;
678             }
679
680             // can it be handled by an extension?
681             if (!$this->container->hasExtension($node->namespaceURI)) {
682                 $extensionNamespaces = array_filter(array_map(function ($ext) { return $ext->getNamespace(); }, $this->container->getExtensions()));
683                 throw new InvalidArgumentException(sprintf('There is no extension able to load the configuration for "%s" (in %s). Looked for namespace "%s", found %s', $node->tagName, $file, $node->namespaceURI, $extensionNamespaces ? sprintf('"%s"', implode('", "', $extensionNamespaces)) : 'none'));
684             }
685         }
686     }
687
688     /**
689      * Loads from an extension.
690      *
691      * @param \DOMDocument $xml
692      */
693     private function loadFromExtensions(\DOMDocument $xml)
694     {
695         foreach ($xml->documentElement->childNodes as $node) {
696             if (!$node instanceof \DOMElement || self::NS === $node->namespaceURI) {
697                 continue;
698             }
699
700             $values = static::convertDomElementToArray($node);
701             if (!\is_array($values)) {
702                 $values = array();
703             }
704
705             $this->container->loadFromExtension($node->namespaceURI, $values);
706         }
707     }
708
709     /**
710      * Converts a \DOMElement object to a PHP array.
711      *
712      * The following rules applies during the conversion:
713      *
714      *  * Each tag is converted to a key value or an array
715      *    if there is more than one "value"
716      *
717      *  * The content of a tag is set under a "value" key (<foo>bar</foo>)
718      *    if the tag also has some nested tags
719      *
720      *  * The attributes are converted to keys (<foo foo="bar"/>)
721      *
722      *  * The nested-tags are converted to keys (<foo><foo>bar</foo></foo>)
723      *
724      * @param \DOMElement $element A \DOMElement instance
725      *
726      * @return array A PHP array
727      */
728     public static function convertDomElementToArray(\DOMElement $element)
729     {
730         return XmlUtils::convertDomElementToArray($element);
731     }
732 }