namespace Symfony\Component\DependencyInjection\Loader;
-use Symfony\Component\Config\Resource\FileResource;
use Symfony\Component\Config\Util\XmlUtils;
-use Symfony\Component\DependencyInjection\DefinitionDecorator;
+use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Alias;
+use Symfony\Component\DependencyInjection\Argument\BoundArgument;
+use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Definition;
+use Symfony\Component\DependencyInjection\ChildDefinition;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
$xml = $this->parseFileToDOM($path);
- $this->container->addResource(new FileResource($path));
+ $this->container->fileExists($path);
+
+ $defaults = $this->getServiceDefaults($xml, $path);
// anonymous services
- $this->processAnonymousServices($xml, $path);
+ $this->processAnonymousServices($xml, $path, $defaults);
// imports
$this->parseImports($xml, $path);
// parameters
- $this->parseParameters($xml);
+ $this->parseParameters($xml, $path);
// extensions
$this->loadFromExtensions($xml);
// services
- $this->parseDefinitions($xml, $path);
+ try {
+ $this->parseDefinitions($xml, $path, $defaults);
+ } finally {
+ $this->instanceof = array();
+ }
}
/**
*/
public function supports($resource, $type = null)
{
- return is_string($resource) && 'xml' === pathinfo($resource, PATHINFO_EXTENSION);
+ if (!is_string($resource)) {
+ return false;
+ }
+
+ if (null === $type && 'xml' === pathinfo($resource, PATHINFO_EXTENSION)) {
+ return true;
+ }
+
+ return 'xml' === $type;
}
/**
* Parses parameters.
*
* @param \DOMDocument $xml
+ * @param string $file
*/
- private function parseParameters(\DOMDocument $xml)
+ private function parseParameters(\DOMDocument $xml, $file)
{
if ($parameters = $this->getChildren($xml->documentElement, 'parameters')) {
- $this->container->getParameterBag()->add($this->getArgumentsAsPhp($parameters[0], 'parameter'));
+ $this->container->getParameterBag()->add($this->getArgumentsAsPhp($parameters[0], 'parameter', $file));
}
}
$defaultDirectory = dirname($file);
foreach ($imports as $import) {
$this->setCurrentDir($defaultDirectory);
- $this->import($import->getAttribute('resource'), null, (bool) XmlUtils::phpize($import->getAttribute('ignore-errors')), $file);
+ $this->import($import->getAttribute('resource'), XmlUtils::phpize($import->getAttribute('type')) ?: null, (bool) XmlUtils::phpize($import->getAttribute('ignore-errors')), $file);
}
}
* @param \DOMDocument $xml
* @param string $file
*/
- private function parseDefinitions(\DOMDocument $xml, $file)
+ private function parseDefinitions(\DOMDocument $xml, $file, $defaults)
{
$xpath = new \DOMXPath($xml);
$xpath->registerNamespace('container', self::NS);
- if (false === $services = $xpath->query('//container:services/container:service')) {
+ if (false === $services = $xpath->query('//container:services/container:service|//container:services/container:prototype')) {
return;
}
+ $this->setCurrentDir(dirname($file));
+
+ $this->instanceof = array();
+ $this->isLoadingInstanceof = true;
+ $instanceof = $xpath->query('//container:services/container:instanceof');
+ foreach ($instanceof as $service) {
+ $this->setDefinition((string) $service->getAttribute('id'), $this->parseDefinition($service, $file, array()));
+ }
+ $this->isLoadingInstanceof = false;
foreach ($services as $service) {
- if (null !== $definition = $this->parseDefinition($service, $file)) {
- $this->container->setDefinition((string) $service->getAttribute('id'), $definition);
+ if (null !== $definition = $this->parseDefinition($service, $file, $defaults)) {
+ if ('prototype' === $service->tagName) {
+ $this->registerClasses($definition, (string) $service->getAttribute('namespace'), (string) $service->getAttribute('resource'), (string) $service->getAttribute('exclude'));
+ } else {
+ $this->setDefinition((string) $service->getAttribute('id'), $definition);
+ }
+ }
+ }
+ }
+
+ /**
+ * Get service defaults.
+ *
+ * @return array
+ */
+ private function getServiceDefaults(\DOMDocument $xml, $file)
+ {
+ $xpath = new \DOMXPath($xml);
+ $xpath->registerNamespace('container', self::NS);
+
+ if (null === $defaultsNode = $xpath->query('//container:services/container:defaults')->item(0)) {
+ return array();
+ }
+ $defaults = array(
+ 'tags' => $this->getChildren($defaultsNode, 'tag'),
+ 'bind' => array_map(function ($v) { return new BoundArgument($v); }, $this->getArgumentsAsPhp($defaultsNode, 'bind', $file)),
+ );
+
+ foreach ($defaults['tags'] as $tag) {
+ if ('' === $tag->getAttribute('name')) {
+ throw new InvalidArgumentException(sprintf('The tag name for tag "<defaults>" in %s must be a non-empty string.', $file));
}
}
+
+ if ($defaultsNode->hasAttribute('autowire')) {
+ $defaults['autowire'] = XmlUtils::phpize($defaultsNode->getAttribute('autowire'));
+ }
+ if ($defaultsNode->hasAttribute('public')) {
+ $defaults['public'] = XmlUtils::phpize($defaultsNode->getAttribute('public'));
+ }
+ if ($defaultsNode->hasAttribute('autoconfigure')) {
+ $defaults['autoconfigure'] = XmlUtils::phpize($defaultsNode->getAttribute('autoconfigure'));
+ }
+
+ return $defaults;
}
/**
*
* @param \DOMElement $service
* @param string $file
+ * @param array $defaults
*
* @return Definition|null
*/
- private function parseDefinition(\DOMElement $service, $file)
+ private function parseDefinition(\DOMElement $service, $file, array $defaults)
{
if ($alias = $service->getAttribute('alias')) {
$this->validateAlias($service, $file);
- $public = true;
+ $this->container->setAlias((string) $service->getAttribute('id'), $alias = new Alias($alias));
if ($publicAttr = $service->getAttribute('public')) {
- $public = XmlUtils::phpize($publicAttr);
+ $alias->setPublic(XmlUtils::phpize($publicAttr));
+ } elseif (isset($defaults['public'])) {
+ $alias->setPublic($defaults['public']);
}
- $this->container->setAlias((string) $service->getAttribute('id'), new Alias($alias, $public));
return;
}
- if ($parent = $service->getAttribute('parent')) {
- $definition = new DefinitionDecorator($parent);
+ if ($this->isLoadingInstanceof) {
+ $definition = new ChildDefinition('');
+ } elseif ($parent = $service->getAttribute('parent')) {
+ if (!empty($this->instanceof)) {
+ 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')));
+ }
+
+ foreach ($defaults as $k => $v) {
+ if ('tags' === $k) {
+ // since tags are never inherited from parents, there is no confusion
+ // thus we can safely add them as defaults to ChildDefinition
+ continue;
+ }
+ if ('bind' === $k) {
+ if ($defaults['bind']) {
+ 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')));
+ }
+
+ continue;
+ }
+ if (!$service->hasAttribute($k)) {
+ 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')));
+ }
+ }
+
+ $definition = new ChildDefinition($parent);
} else {
$definition = new Definition();
+
+ if (isset($defaults['public'])) {
+ $definition->setPublic($defaults['public']);
+ }
+ if (isset($defaults['autowire'])) {
+ $definition->setAutowired($defaults['autowire']);
+ }
+ if (isset($defaults['autoconfigure'])) {
+ $definition->setAutoconfigured($defaults['autoconfigure']);
+ }
+
+ $definition->setChanges(array());
}
- foreach (array('class', 'shared', 'public', 'synthetic', 'lazy', 'abstract') as $key) {
+ foreach (array('class', 'public', 'shared', 'synthetic', 'lazy', 'abstract') as $key) {
if ($value = $service->getAttribute($key)) {
$method = 'set'.$key;
$definition->$method(XmlUtils::phpize($value));
$definition->setAutowired(XmlUtils::phpize($value));
}
+ if ($value = $service->getAttribute('autoconfigure')) {
+ if (!$definition instanceof ChildDefinition) {
+ $definition->setAutoconfigured(XmlUtils::phpize($value));
+ } elseif ($value = XmlUtils::phpize($value)) {
+ 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')));
+ }
+ }
+
if ($files = $this->getChildren($service, 'file')) {
$definition->setFile($files[0]->nodeValue);
}
$definition->setDeprecated(true, $deprecated[0]->nodeValue ?: null);
}
- $definition->setArguments($this->getArgumentsAsPhp($service, 'argument'));
- $definition->setProperties($this->getArgumentsAsPhp($service, 'property'));
+ $definition->setArguments($this->getArgumentsAsPhp($service, 'argument', $file, false, $definition instanceof ChildDefinition));
+ $definition->setProperties($this->getArgumentsAsPhp($service, 'property', $file));
if ($factories = $this->getChildren($service, 'factory')) {
$factory = $factories[0];
if ($function = $factory->getAttribute('function')) {
$definition->setFactory($function);
} else {
- $factoryService = $this->getChildren($factory, 'service');
-
- if (isset($factoryService[0])) {
- $class = $this->parseDefinition($factoryService[0], $file);
- } elseif ($childService = $factory->getAttribute('service')) {
+ if ($childService = $factory->getAttribute('service')) {
$class = new Reference($childService, ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE);
} else {
- $class = $factory->getAttribute('class');
+ $class = $factory->hasAttribute('class') ? $factory->getAttribute('class') : null;
}
$definition->setFactory(array($class, $factory->getAttribute('method')));
if ($function = $configurator->getAttribute('function')) {
$definition->setConfigurator($function);
} else {
- $configuratorService = $this->getChildren($configurator, 'service');
-
- if (isset($configuratorService[0])) {
- $class = $this->parseDefinition($configuratorService[0], $file);
- } elseif ($childService = $configurator->getAttribute('service')) {
+ if ($childService = $configurator->getAttribute('service')) {
$class = new Reference($childService, ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE);
} else {
$class = $configurator->getAttribute('class');
}
foreach ($this->getChildren($service, 'call') as $call) {
- $definition->addMethodCall($call->getAttribute('method'), $this->getArgumentsAsPhp($call, 'argument'));
+ $definition->addMethodCall($call->getAttribute('method'), $this->getArgumentsAsPhp($call, 'argument', $file));
}
- foreach ($this->getChildren($service, 'tag') as $tag) {
+ $tags = $this->getChildren($service, 'tag');
+
+ if (!empty($defaults['tags'])) {
+ $tags = array_merge($tags, $defaults['tags']);
+ }
+
+ foreach ($tags as $tag) {
$parameters = array();
foreach ($tag->attributes as $name => $node) {
if ('name' === $name) {
$definition->addAutowiringType($type->textContent);
}
+ $bindings = $this->getArgumentsAsPhp($service, 'bind', $file);
+ if (isset($defaults['bind'])) {
+ // deep clone, to avoid multiple process of the same instance in the passes
+ $bindings = array_merge(unserialize(serialize($defaults['bind'])), $bindings);
+ }
+ if ($bindings) {
+ $definition->setBindings($bindings);
+ }
+
if ($value = $service->getAttribute('decorates')) {
$renameId = $service->hasAttribute('decoration-inner-name') ? $service->getAttribute('decoration-inner-name') : null;
$priority = $service->hasAttribute('decoration-priority') ? $service->getAttribute('decoration-priority') : 0;
*
* @param \DOMDocument $xml
* @param string $file
+ * @param array $defaults
*/
- private function processAnonymousServices(\DOMDocument $xml, $file)
+ private function processAnonymousServices(\DOMDocument $xml, $file, $defaults)
{
$definitions = array();
$count = 0;
+ $suffix = ContainerBuilder::hash($file);
$xpath = new \DOMXPath($xml);
$xpath->registerNamespace('container', self::NS);
// anonymous services as arguments/properties
- if (false !== $nodes = $xpath->query('//container:argument[@type="service"][not(@id)]|//container:property[@type="service"][not(@id)]')) {
+ 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)]')) {
foreach ($nodes as $node) {
- // give it a unique name
- $id = sprintf('%s_%d', hash('sha256', $file), ++$count);
- $node->setAttribute('id', $id);
-
if ($services = $this->getChildren($node, 'service')) {
+ // give it a unique name
+ $id = sprintf('%d_%s', ++$count, preg_replace('/^.*\\\\/', '', $services[0]->getAttribute('class')).'~'.$suffix);
+ $node->setAttribute('id', $id);
+ $node->setAttribute('service', $id);
+
$definitions[$id] = array($services[0], $file, false);
$services[0]->setAttribute('id', $id);
// anonymous services "in the wild"
if (false !== $nodes = $xpath->query('//container:services/container:service[not(@id)]')) {
foreach ($nodes as $node) {
+ @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);
+
// give it a unique name
- $id = sprintf('%s_%d', hash('sha256', $file), ++$count);
+ $id = sprintf('%d_%s', ++$count, preg_replace('/^.*\\\\/', '', $node->getAttribute('class')).$suffix);
$node->setAttribute('id', $id);
$definitions[$id] = array($node, $file, true);
}
}
// resolve definitions
- krsort($definitions);
- foreach ($definitions as $id => list($domElement, $file, $wild)) {
- if (null !== $definition = $this->parseDefinition($domElement, $file)) {
- $this->container->setDefinition($id, $definition);
+ uksort($definitions, 'strnatcmp');
+ foreach (array_reverse($definitions) as $id => list($domElement, $file, $wild)) {
+ if (null !== $definition = $this->parseDefinition($domElement, $file, $wild ? $defaults : array())) {
+ $this->setDefinition($id, $definition);
}
if (true === $wild) {
$tmpDomElement = new \DOMElement('_services', null, self::NS);
$domElement->parentNode->replaceChild($tmpDomElement, $domElement);
$tmpDomElement->setAttribute('id', $id);
- } else {
- $domElement->parentNode->removeChild($domElement);
}
}
}
*
* @param \DOMElement $node
* @param string $name
+ * @param string $file
* @param bool $lowercase
*
* @return mixed
*/
- private function getArgumentsAsPhp(\DOMElement $node, $name, $lowercase = true)
+ private function getArgumentsAsPhp(\DOMElement $node, $name, $file, $lowercase = true, $isChildDefinition = false)
{
$arguments = array();
foreach ($this->getChildren($node, $name) as $arg) {
$arg->setAttribute('key', $arg->getAttribute('name'));
}
- // this is used by DefinitionDecorator to overwrite a specific
+ // this is used by ChildDefinition to overwrite a specific
// argument of the parent definition
if ($arg->hasAttribute('index')) {
- $key = 'index_'.$arg->getAttribute('index');
+ $key = ($isChildDefinition ? 'index_' : '').$arg->getAttribute('index');
} elseif (!$arg->hasAttribute('key')) {
// Append an empty argument, then fetch its key to overwrite it later
$arguments[] = null;
$key = array_pop($keys);
} else {
$key = $arg->getAttribute('key');
+ }
- // parameter keys are case insensitive
- if ('parameter' == $name && $lowercase) {
- $key = strtolower($key);
- }
+ $onInvalid = $arg->getAttribute('on-invalid');
+ $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
+ if ('ignore' == $onInvalid) {
+ $invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE;
+ } elseif ('ignore_uninitialized' == $onInvalid) {
+ $invalidBehavior = ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE;
+ } elseif ('null' == $onInvalid) {
+ $invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE;
}
switch ($arg->getAttribute('type')) {
case 'service':
- $onInvalid = $arg->getAttribute('on-invalid');
- $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
- if ('ignore' == $onInvalid) {
- $invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE;
- } elseif ('null' == $onInvalid) {
- $invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE;
+ if (!$arg->getAttribute('id')) {
+ throw new InvalidArgumentException(sprintf('Tag "<%s>" with type="service" has no or empty "id" attribute in "%s".', $name, $file));
+ }
+ if ($arg->hasAttribute('strict')) {
+ @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);
}
$arguments[$key] = new Reference($arg->getAttribute('id'), $invalidBehavior);
break;
case 'expression':
+ if (!class_exists(Expression::class)) {
+ throw new \LogicException(sprintf('The type="expression" attribute cannot be used without the ExpressionLanguage component. Try running "composer require symfony/expression-language".'));
+ }
+
$arguments[$key] = new Expression($arg->nodeValue);
break;
case 'collection':
- $arguments[$key] = $this->getArgumentsAsPhp($arg, $name, false);
+ $arguments[$key] = $this->getArgumentsAsPhp($arg, $name, $file, false);
+ break;
+ case 'iterator':
+ $arg = $this->getArgumentsAsPhp($arg, $name, $file, false);
+ try {
+ $arguments[$key] = new IteratorArgument($arg);
+ } catch (InvalidArgumentException $e) {
+ throw new InvalidArgumentException(sprintf('Tag "<%s>" with type="iterator" only accepts collections of type="service" references in "%s".', $name, $file));
+ }
+ break;
+ case 'tagged':
+ if (!$arg->getAttribute('tag')) {
+ throw new InvalidArgumentException(sprintf('Tag "<%s>" with type="tagged" has no or empty "tag" attribute in "%s".', $name, $file));
+ }
+ $arguments[$key] = new TaggedIteratorArgument($arg->getAttribute('tag'));
break;
case 'string':
$arguments[$key] = $arg->nodeValue;
{
$children = array();
foreach ($node->childNodes as $child) {
- if ($child instanceof \DOMElement && $child->localName === $name && $child->namespaceURI === self::NS) {
+ if ($child instanceof \DOMElement && $child->localName === $name && self::NS === $child->namespaceURI) {
$children[] = $child;
}
}
$imports = '';
foreach ($schemaLocations as $namespace => $location) {
$parts = explode('/', $location);
+ $locationstart = 'file:///';
if (0 === stripos($location, 'phar://')) {
- $tmpfile = tempnam(sys_get_temp_dir(), 'sf2');
+ $tmpfile = tempnam(sys_get_temp_dir(), 'symfony');
if ($tmpfile) {
copy($location, $tmpfile);
$tmpfiles[] = $tmpfile;
$parts = explode('/', str_replace('\\', '/', $tmpfile));
+ } else {
+ array_shift($parts);
+ $locationstart = 'phar:///';
}
}
$drive = '\\' === DIRECTORY_SEPARATOR ? array_shift($parts).'/' : '';
- $location = 'file:///'.$drive.implode('/', array_map('rawurlencode', $parts));
+ $location = $locationstart.$drive.implode('/', array_map('rawurlencode', $parts));
$imports .= sprintf(' <xsd:import namespace="%s" schemaLocation="%s" />'."\n", $namespace, $location);
}
}
foreach ($alias->childNodes as $child) {
- if ($child instanceof \DOMElement && $child->namespaceURI === self::NS) {
+ if ($child instanceof \DOMElement && self::NS === $child->namespaceURI) {
@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);
}
}
private function loadFromExtensions(\DOMDocument $xml)
{
foreach ($xml->documentElement->childNodes as $node) {
- if (!$node instanceof \DOMElement || $node->namespaceURI === self::NS) {
+ if (!$node instanceof \DOMElement || self::NS === $node->namespaceURI) {
continue;
}
}
/**
- * Converts a \DomElement object to a PHP array.
+ * Converts a \DOMElement object to a PHP array.
*
* The following rules applies during the conversion:
*
*
* * The nested-tags are converted to keys (<foo><foo>bar</foo></foo>)
*
- * @param \DomElement $element A \DomElement instance
+ * @param \DOMElement $element A \DOMElement instance
*
* @return array A PHP array
*/