[DependencyInjection] Use DOM instead of SimpleXML for namespace support
This commit is contained in:
parent
01f21e3c31
commit
33c91f9be8
@ -18,9 +18,9 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\DependencyInjection\Alias;
|
||||
use Symfony\Component\DependencyInjection\Definition;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Component\DependencyInjection\SimpleXMLElement;
|
||||
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
|
||||
use Symfony\Component\ExpressionLanguage\Expression;
|
||||
|
||||
/**
|
||||
* XmlFileLoader loads XML files service definitions.
|
||||
@ -29,6 +29,8 @@ use Symfony\Component\DependencyInjection\Exception\RuntimeException;
|
||||
*/
|
||||
class XmlFileLoader extends FileLoader
|
||||
{
|
||||
const NS = 'http://symfony.com/schema/dic/services';
|
||||
|
||||
/**
|
||||
* Loads an XML file.
|
||||
*
|
||||
@ -39,8 +41,7 @@ class XmlFileLoader extends FileLoader
|
||||
{
|
||||
$path = $this->locator->locate($file);
|
||||
|
||||
$xml = $this->parseFile($path);
|
||||
$xml->registerXPathNamespace('container', 'http://symfony.com/schema/dic/services');
|
||||
$xml = $this->parseFileToDOM($path);
|
||||
|
||||
$this->container->addResource(new FileResource($path));
|
||||
|
||||
@ -76,125 +77,131 @@ class XmlFileLoader extends FileLoader
|
||||
/**
|
||||
* Parses parameters
|
||||
*
|
||||
* @param SimpleXMLElement $xml
|
||||
* @param string $file
|
||||
* @param \DOMDocument $xml
|
||||
* @param string $file
|
||||
*/
|
||||
private function parseParameters(SimpleXMLElement $xml, $file)
|
||||
private function parseParameters(\DOMDocument $xml, $file)
|
||||
{
|
||||
if (!$xml->parameters) {
|
||||
return;
|
||||
if ($parameters = $this->getChildren($xml->documentElement, 'parameters')) {
|
||||
$this->container->getParameterBag()->add($this->getArgumentsAsPhp($parameters[0], 'parameter'));
|
||||
}
|
||||
|
||||
$this->container->getParameterBag()->add($xml->parameters->getArgumentsAsPhp('parameter'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses imports
|
||||
*
|
||||
* @param SimpleXMLElement $xml
|
||||
* @param string $file
|
||||
* @param \DOMDocument $xml
|
||||
* @param string $file
|
||||
*/
|
||||
private function parseImports(SimpleXMLElement $xml, $file)
|
||||
private function parseImports(\DOMDocument $xml, $file)
|
||||
{
|
||||
if (false === $imports = $xml->xpath('//container:imports/container:import')) {
|
||||
$xpath = new \DOMXPath($xml);
|
||||
$xpath->registerNamespace('container', self::NS);
|
||||
|
||||
if (false === $imports = $xpath->query('//container:imports/container:import')) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($imports as $import) {
|
||||
$this->setCurrentDir(dirname($file));
|
||||
$this->import((string) $import['resource'], null, (Boolean) $import->getAttributeAsPhp('ignore-errors'), $file);
|
||||
$this->import($import->getAttribute('resource'), null, (Boolean) XmlUtils::phpize($import->getAttribute('ignore-errors')), $file);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses multiple definitions
|
||||
*
|
||||
* @param SimpleXMLElement $xml
|
||||
* @param string $file
|
||||
* @param \DOMDocument $xml
|
||||
* @param string $file
|
||||
*/
|
||||
private function parseDefinitions(SimpleXMLElement $xml, $file)
|
||||
private function parseDefinitions(\DOMDocument $xml, $file)
|
||||
{
|
||||
if (false === $services = $xml->xpath('//container:services/container:service')) {
|
||||
$xpath = new \DOMXPath($xml);
|
||||
$xpath->registerNamespace('container', self::NS);
|
||||
|
||||
if (false === $services = $xpath->query('//container:services/container:service')) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($services as $service) {
|
||||
$this->parseDefinition((string) $service['id'], $service, $file);
|
||||
$this->parseDefinition((string) $service->getAttribute('id'), $service, $file);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an individual Definition
|
||||
*
|
||||
* @param string $id
|
||||
* @param SimpleXMLElement $service
|
||||
* @param string $file
|
||||
* @param string $id
|
||||
* @param \DOMElement $service
|
||||
* @param string $file
|
||||
*/
|
||||
private function parseDefinition($id, $service, $file)
|
||||
private function parseDefinition($id, \DOMElement $service, $file)
|
||||
{
|
||||
if ((string) $service['alias']) {
|
||||
if ($alias = $service->getAttribute('alias')) {
|
||||
$public = true;
|
||||
if (isset($service['public'])) {
|
||||
$public = $service->getAttributeAsPhp('public');
|
||||
if ($publicAttr = $service->getAttribute('public')) {
|
||||
$public = XmlUtils::phpize($publicAttr);
|
||||
}
|
||||
$this->container->setAlias($id, new Alias((string) $service['alias'], $public));
|
||||
$this->container->setAlias($id, new Alias($alias, $public));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($service['parent'])) {
|
||||
$definition = new DefinitionDecorator((string) $service['parent']);
|
||||
if ($parent = $service->getAttribute('parent')) {
|
||||
$definition = new DefinitionDecorator($parent);
|
||||
} else {
|
||||
$definition = new Definition();
|
||||
}
|
||||
|
||||
foreach (array('class', 'scope', 'public', 'factory-class', 'factory-method', 'factory-service', 'synthetic', 'synchronized', 'lazy', 'abstract') as $key) {
|
||||
if (isset($service[$key])) {
|
||||
if ($value = $service->getAttribute($key)) {
|
||||
$method = 'set'.str_replace('-', '', $key);
|
||||
$definition->$method((string) $service->getAttributeAsPhp($key));
|
||||
$definition->$method(XmlUtils::phpize($value));
|
||||
}
|
||||
}
|
||||
|
||||
if ($service->file) {
|
||||
$definition->setFile((string) $service->file);
|
||||
if ($files = $this->getChildren($service, 'file')) {
|
||||
$definition->setFile($files[0]->nodeValue);
|
||||
}
|
||||
|
||||
$definition->setArguments($service->getArgumentsAsPhp('argument'));
|
||||
$definition->setProperties($service->getArgumentsAsPhp('property'));
|
||||
$definition->setArguments($this->getArgumentsAsPhp($service, 'argument'));
|
||||
$definition->setProperties($this->getArgumentsAsPhp($service, 'property'));
|
||||
|
||||
if (isset($service->configurator)) {
|
||||
if (isset($service->configurator['function'])) {
|
||||
$definition->setConfigurator((string) $service->configurator['function']);
|
||||
if ($configurators = $this->getChildren($service, 'configurator')) {
|
||||
$configurator = $configurators[0];
|
||||
if ($function = $configurator->getAttribute('function')) {
|
||||
$definition->setConfigurator($function);
|
||||
} else {
|
||||
if (isset($service->configurator['service'])) {
|
||||
$class = new Reference((string) $service->configurator['service'], ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, false);
|
||||
if ($childService = $configurator->getAttribute('service')) {
|
||||
$class = new Reference($childService, ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, false);
|
||||
} else {
|
||||
$class = (string) $service->configurator['class'];
|
||||
$class = $configurator->getAttribute('class');
|
||||
}
|
||||
|
||||
$definition->setConfigurator(array($class, (string) $service->configurator['method']));
|
||||
$definition->setConfigurator(array($class, $configurator->getAttribute('method')));
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($service->call as $call) {
|
||||
$definition->addMethodCall((string) $call['method'], $call->getArgumentsAsPhp('argument'));
|
||||
foreach ($this->getChildren($service, 'call') as $call) {
|
||||
$definition->addMethodCall($call->getAttribute('method'), $this->getArgumentsAsPhp($call, 'argument'));
|
||||
}
|
||||
|
||||
foreach ($service->tag as $tag) {
|
||||
foreach ($this->getChildren($service, 'tag') as $tag) {
|
||||
$parameters = array();
|
||||
foreach ($tag->attributes() as $name => $value) {
|
||||
foreach ($tag->attributes as $name => $node) {
|
||||
if ('name' === $name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (false !== strpos($name, '-') && false === strpos($name, '_') && !array_key_exists($normalizedName = str_replace('-', '_', $name), $parameters)) {
|
||||
$parameters[$normalizedName] = SimpleXMLElement::phpize($value);
|
||||
$parameters[$normalizedName] = XmlUtils::phpize($node->nodeValue);
|
||||
}
|
||||
// keep not normalized key for BC too
|
||||
$parameters[$name] = SimpleXMLElement::phpize($value);
|
||||
}
|
||||
|
||||
$definition->addTag((string) $tag['name'], $parameters);
|
||||
// $definition->addTag((string) $tag['name'], $parameters);
|
||||
$definition->addTag($tag->getAttribute('name'), $parameters);
|
||||
}
|
||||
|
||||
if (isset($service['decorates'])) {
|
||||
@ -215,6 +222,22 @@ class XmlFileLoader extends FileLoader
|
||||
* @throws InvalidArgumentException When loading of XML file returns error
|
||||
*/
|
||||
protected function parseFile($file)
|
||||
{
|
||||
$dom = $this->parseFileToDOM($file);
|
||||
|
||||
return simplexml_import_dom($dom, 'Symfony\\Component\\DependencyInjection\\SimpleXMLElement');
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a XML file to a \DOMDocument
|
||||
*
|
||||
* @param string $file Path to a file
|
||||
*
|
||||
* @return \DOMDocument
|
||||
*
|
||||
* @throws InvalidArgumentException When loading of XML file returns error
|
||||
*/
|
||||
protected function parseFileToDOM($file)
|
||||
{
|
||||
try {
|
||||
$dom = XmlUtils::loadFile($file, array($this, 'validateSchema'));
|
||||
@ -224,41 +247,48 @@ class XmlFileLoader extends FileLoader
|
||||
|
||||
$this->validateExtensions($dom, $file);
|
||||
|
||||
return simplexml_import_dom($dom, 'Symfony\\Component\\DependencyInjection\\SimpleXMLElement');
|
||||
return $dom;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes anonymous services
|
||||
*
|
||||
* @param SimpleXMLElement $xml
|
||||
* @param string $file
|
||||
* @param \DOMDocument $xml
|
||||
* @param string $file
|
||||
*/
|
||||
private function processAnonymousServices(SimpleXMLElement $xml, $file)
|
||||
private function processAnonymousServices(\DOMDocument $xml, $file)
|
||||
{
|
||||
$definitions = array();
|
||||
$count = 0;
|
||||
|
||||
$xpath = new \DOMXPath($xml);
|
||||
$xpath->registerNamespace('container', self::NS);
|
||||
|
||||
// anonymous services as arguments/properties
|
||||
if (false !== $nodes = $xml->xpath('//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)]')) {
|
||||
foreach ($nodes as $node) {
|
||||
// give it a unique name
|
||||
$id = sprintf('%s_%d', hash('sha256', $file), ++$count);
|
||||
$node['id'] = $id;
|
||||
$node->setAttribute('id', $id);
|
||||
|
||||
$definitions[$id] = array($node->service, $file, false);
|
||||
$node->service['id'] = $id;
|
||||
if ($services = $this->getChildren($node, 'service')) {
|
||||
$definitions[$id] = array($services[0], $file, false);
|
||||
$services[0]->setAttribute('id', $id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// anonymous services "in the wild"
|
||||
if (false !== $nodes = $xml->xpath('//container:services/container:service[not(@id)]')) {
|
||||
if (false !== $nodes = $xpath->query('//container:services/container:service[not(@id)]')) {
|
||||
foreach ($nodes as $node) {
|
||||
// give it a unique name
|
||||
$id = sprintf('%s_%d', hash('sha256', $file), ++$count);
|
||||
$node['id'] = $id;
|
||||
$node->setAttribute('id', $id);
|
||||
|
||||
$definitions[$id] = array($node, $file, true);
|
||||
$node->service['id'] = $id;
|
||||
if ($services = $this->getChildren($node, 'service')) {
|
||||
$definitions[$id] = array($node, $file, true);
|
||||
$services[0]->setAttribute('id', $id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -266,13 +296,13 @@ class XmlFileLoader extends FileLoader
|
||||
krsort($definitions);
|
||||
foreach ($definitions as $id => $def) {
|
||||
// anonymous services are always private
|
||||
$def[0]['public'] = false;
|
||||
$def[0]->setAttribute('public', false);
|
||||
|
||||
$this->parseDefinition($id, $def[0], $def[1]);
|
||||
|
||||
$oNode = dom_import_simplexml($def[0]);
|
||||
$oNode = $def[0];
|
||||
if (true === $def[2]) {
|
||||
$nNode = new \DOMElement('_services');
|
||||
$nNode = new \DOMElement('_services', null, self::NS);
|
||||
$oNode->parentNode->replaceChild($nNode, $oNode);
|
||||
$nNode->setAttribute('id', $id);
|
||||
} else {
|
||||
@ -281,6 +311,96 @@ class XmlFileLoader extends FileLoader
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns arguments as valid php types.
|
||||
*
|
||||
* @param \DOMElement $node
|
||||
* @param string $name
|
||||
* @param Boolean $lowercase
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private function getArgumentsAsPhp(\DOMElement $node, $name, $lowercase = true)
|
||||
{
|
||||
$arguments = array();
|
||||
foreach ($this->getChildren($node, $name) as $arg) {
|
||||
if ($nameAttr = $arg->getAttribute('name')) {
|
||||
$arg->setAttribute('key', $nameAttr);
|
||||
}
|
||||
|
||||
if (!$key = $arg->getAttribute('key')) {
|
||||
$key = !$arguments ? 0 : max(array_keys($arguments)) + 1;
|
||||
}
|
||||
|
||||
// parameter keys are case insensitive
|
||||
if ('parameter' == $name && $lowercase) {
|
||||
$key = strtolower($key);
|
||||
}
|
||||
|
||||
// this is used by DefinitionDecorator to overwrite a specific
|
||||
// argument of the parent definition
|
||||
if ($index = $arg->getAttribute('index')) {
|
||||
$key = 'index_'.$index;
|
||||
}
|
||||
|
||||
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 ($strict = $arg->getAttribute('strict')) {
|
||||
$strict = XmlUtils::phpize($strict);
|
||||
} else {
|
||||
$strict = true;
|
||||
}
|
||||
|
||||
$arguments[$key] = new Reference($arg->getAttribute('id'), $invalidBehavior, $strict);
|
||||
break;
|
||||
case 'expression':
|
||||
$arguments[$key] = new Expression($arg->nodeValue);
|
||||
break;
|
||||
case 'collection':
|
||||
$arguments[$key] = $this->getArgumentsAsPhp($arg, $name, false);
|
||||
break;
|
||||
case 'string':
|
||||
$arguments[$key] = $arg->nodeValue;
|
||||
break;
|
||||
case 'constant':
|
||||
$arguments[$key] = constant($arg->nodeValue);
|
||||
break;
|
||||
default:
|
||||
$arguments[$key] = XmlUtils::phpize($arg->nodeValue);
|
||||
}
|
||||
}
|
||||
|
||||
return $arguments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get child elements by name
|
||||
*
|
||||
* @param \DOMNode $node
|
||||
* @param mixed $name
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getChildren(\DOMNode $node, $name)
|
||||
{
|
||||
$children = array();
|
||||
foreach ($node->childNodes as $child) {
|
||||
if ($child instanceof \DOMElement && $child->localName === $name && $child->namespaceURI === self::NS) {
|
||||
$children[] = $child;
|
||||
}
|
||||
}
|
||||
|
||||
return $children;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a documents XML schema.
|
||||
*
|
||||
@ -385,12 +505,12 @@ EOF
|
||||
/**
|
||||
* Loads from an extension.
|
||||
*
|
||||
* @param SimpleXMLElement $xml
|
||||
* @param \DOMDocument $xml
|
||||
*/
|
||||
private function loadFromExtensions(SimpleXMLElement $xml)
|
||||
private function loadFromExtensions(\DOMDocument $xml)
|
||||
{
|
||||
foreach (dom_import_simplexml($xml)->childNodes as $node) {
|
||||
if (!$node instanceof \DOMElement || $node->namespaceURI === 'http://symfony.com/schema/dic/services') {
|
||||
foreach ($xml->documentElement->childNodes as $node) {
|
||||
if (!$node instanceof \DOMElement || $node->namespaceURI === self::NS) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" ?>
|
||||
|
||||
<srv:container xmlns="http://symfony.com/schema/dic/doctrine"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:srv="http://symfony.com/schema/dic/services"
|
||||
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd
|
||||
http://symfony.com/schema/dic/doctrine http://symfony.com/schema/dic/doctrine/doctrine-1.0.xsd">
|
||||
|
||||
<srv:services>
|
||||
<srv:service id="foo" class="FooClass">
|
||||
<srv:tag name="foo.tag" />
|
||||
<srv:call method="setBar">
|
||||
<srv:argument>foo</srv:argument>
|
||||
</srv:call>
|
||||
</srv:service>
|
||||
</srv:services>
|
||||
</srv:container>
|
@ -432,4 +432,16 @@ class XmlFileLoaderTest extends \PHPUnit_Framework_TestCase
|
||||
$this->assertSame('Document types are not allowed.', $e->getMessage(), '->load() throws an InvalidArgumentException if the configuration contains a document type');
|
||||
}
|
||||
}
|
||||
|
||||
public function testXmlNamespaces()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
$loader = new XmlFileLoader($container, new FileLocator(self::$fixturesPath.'/xml'));
|
||||
$loader->load('namespaces.xml');
|
||||
$services = $container->getDefinitions();
|
||||
|
||||
$this->assertTrue(isset($services['foo']), '->load() parses <srv:service> elements');
|
||||
$this->assertEquals(1, count($services['foo']->getTag('foo.tag')), '->load parses <srv:tag> elements');
|
||||
$this->assertEquals(array(array('setBar', array('foo'))), $services['foo']->getMethodCalls(), '->load() parses the <srv:call> tag');
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user