[DependencyInjection] made the loader extensions much more reliable and robust

This commit is contained in:
Fabien Potencier 2010-01-11 15:56:49 +01:00
parent 0c2b2bdbbb
commit a0551d525c
19 changed files with 276 additions and 151 deletions

View File

@ -165,7 +165,9 @@ class XmlDumper extends Dumper
return <<<EOF return <<<EOF
<?xml version="1.0" ?> <?xml version="1.0" ?>
<container xmlns="http://symfony-project.org/2.0/container"> <container xmlns="http://www.symfony-project.org/schema/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.symfony-project.org/schema/services http://www.symfony-project.org/schema/services/services-1.0.xsd">
EOF; EOF;
} }

View File

@ -22,11 +22,23 @@ abstract class Loader implements LoaderInterface
{ {
static protected $extensions = array(); static protected $extensions = array();
/**
* Registers an extension.
*
* @param LoaderExtensionInterface $extension An extension instance
*/
static public function registerExtension(LoaderExtensionInterface $extension) static public function registerExtension(LoaderExtensionInterface $extension)
{ {
static::$extensions[$extension->getNamespace()] = $extension; static::$extensions[$extension->getAlias()] = static::$extensions[$extension->getNamespace()] = $extension;
} }
/**
* Returns an extension by alias or namespace.
*
* @param string $name An alias or a namespace
*
* @return LoaderExtensionInterface An extension instance
*/
static public function getExtension($name) static public function getExtension($name)
{ {
return isset(static::$extensions[$name]) ? static::$extensions[$name] : null; return isset(static::$extensions[$name]) ? static::$extensions[$name] : null;

View File

@ -31,9 +31,18 @@ interface LoaderExtensionInterface
public function load($tag, array $config); public function load($tag, array $config);
/** /**
* Returns the namespace to be used for this extension. * Returns the namespace to be used for this extension (XML namespace).
* *
* @return string The namespace * @return string The XML namespace
*/ */
public function getNamespace(); public function getNamespace();
/**
* Returns the recommanded alias to use in XML.
*
* This alias is also the mandatory prefix to use when using YAML.
*
* @return string The alias
*/
public function getAlias();
} }

View File

@ -195,10 +195,12 @@ class XmlFileLoader extends FileLoader
{ {
throw new \InvalidArgumentException(implode("\n", $this->getXmlErrors())); throw new \InvalidArgumentException(implode("\n", $this->getXmlErrors()));
} }
$dom->validateOnParse = true;
$dom->normalizeDocument();
libxml_use_internal_errors(false); libxml_use_internal_errors(false);
$this->validate($dom, $path); $this->validate($dom, $path);
$xmls[$path] = simplexml_import_dom($dom, 'Symfony\Components\DependencyInjection\SimpleXMLElement'); $xmls[$path] = simplexml_import_dom($dom, 'Symfony\\Components\\DependencyInjection\\SimpleXMLElement');
} }
return $xmls; return $xmls;
@ -210,7 +212,7 @@ class XmlFileLoader extends FileLoader
$count = 0; $count = 0;
// find anonymous service definitions // find anonymous service definitions
$xml->registerXPathNamespace('container', 'http://symfony-project.org/2.0/container'); $xml->registerXPathNamespace('container', 'http://www.symfony-project.org/schema/services');
$nodes = $xml->xpath('//container:argument[@type="service"][not(@id)]'); $nodes = $xml->xpath('//container:argument[@type="service"][not(@id)]');
foreach ($nodes as $node) foreach ($nodes as $node)
{ {
@ -236,14 +238,52 @@ class XmlFileLoader extends FileLoader
protected function validate($dom, $file) protected function validate($dom, $file)
{ {
$this->validateSchema($dom, $file);
$this->validateExtensions($dom, $file);
}
protected function validateSchema($dom, $file)
{
$schemaLocations = array('http://www.symfony-project.org/schema/services' => __DIR__.'/schema/services/services-1.0.xsd');
if ($element = $dom->documentElement->getAttributeNS('http://www.w3.org/2001/XMLSchema-instance', 'schemaLocation'))
{
$items = preg_split('/\s+/', $element);
for ($i = 0, $nb = count($items); $i < $nb; $i += 2)
{
$schemaLocations[$items[$i]] = str_replace('http://www.symfony-project.org/', __DIR__.'/', $items[$i + 1]);
}
}
$imports = '';
foreach ($schemaLocations as $namespace => $location)
{
$imports .= sprintf(' <xsd:import namespace="%s" schemaLocation="%s" />'."\n", $namespace, $location);
}
$source = <<<EOF
<?xml version="1.0" encoding="utf-8" ?>
<xsd:schema xmlns="http://www.symfony-project.org/schema"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.symfony-project.org/schema"
elementFormDefault="qualified">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
$imports
</xsd:schema>
EOF
;
libxml_use_internal_errors(true); libxml_use_internal_errors(true);
if (!$dom->schemaValidate(__DIR__.'/services.xsd')) if (!$dom->schemaValidateSource($source))
{ {
throw new \InvalidArgumentException(implode("\n", $this->getXmlErrors())); throw new \InvalidArgumentException(implode("\n", $this->getXmlErrors()));
} }
libxml_use_internal_errors(false); libxml_use_internal_errors(false);
}
// validate extensions protected function validateExtensions($dom, $file)
{
foreach ($dom->documentElement->childNodes as $node) foreach ($dom->documentElement->childNodes as $node)
{ {
if (!$node instanceof \DOMElement || in_array($node->tagName, array('imports', 'parameters', 'services'))) if (!$node instanceof \DOMElement || in_array($node->tagName, array('imports', 'parameters', 'services')))
@ -251,19 +291,16 @@ class XmlFileLoader extends FileLoader
continue; continue;
} }
// can it be handled by an extension? if ($node->namespaceURI === 'http://www.symfony-project.org/schema/services')
if (false !== strpos($node->tagName, ':'))
{ {
list($namespace, $tag) = explode(':', $node->tagName); throw new \InvalidArgumentException(sprintf('The "%s" tag is not valid (in %s).', $node->tagName, $file));
if (!static::getExtension($namespace))
{
throw new \InvalidArgumentException(sprintf('There is no extension able to load the configuration for "%s" (in %s).', $node->tagName, $file));
}
continue;
} }
throw new \InvalidArgumentException(sprintf('The "%s" tag is not valid (in %s).', $node->tagName, $file)); // can it be handled by an extension?
if (!static::getExtension($node->namespaceURI))
{
throw new \InvalidArgumentException(sprintf('There is no extension able to load the configuration for "%s" (in %s).', $node->tagName, $file));
}
} }
} }
@ -289,15 +326,15 @@ class XmlFileLoader extends FileLoader
protected function loadFromExtensions(BuilderConfiguration $configuration, $xml) protected function loadFromExtensions(BuilderConfiguration $configuration, $xml)
{ {
foreach (dom_import_simplexml($xml)->getElementsByTagNameNS('*', '*') as $element) foreach (dom_import_simplexml($xml)->childNodes as $node)
{ {
if (!$element->prefix) if (!$node instanceof \DOMElement || $node->namespaceURI === 'http://www.symfony-project.org/schema/services')
{ {
continue; continue;
} }
$values = static::convertDomElementToArray($element); $values = static::convertDomElementToArray($node);
$config = $this->getExtension($element->prefix)->load($element->localName, is_array($values) ? $values : array($values)); $config = $this->getExtension($node->namespaceURI)->load($node->localName, is_array($values) ? $values : array($values));
$configuration->merge($config); $configuration->merge($config);
} }
@ -345,7 +382,7 @@ class XmlFileLoader extends FileLoader
} }
elseif (!$node instanceof \DOMComment) elseif (!$node instanceof \DOMComment)
{ {
$config[$node->tagName] = static::convertDomElementToArray($node); $config[$node->localName] = static::convertDomElementToArray($node);
$empty = false; $empty = false;
} }
} }

View File

@ -0,0 +1,155 @@
<?xml version="1.0" encoding="UTF-8" ?>
<xsd:schema xmlns="http://www.symfony-project.org/schema/services"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.symfony-project.org/schema/services"
elementFormDefault="qualified">
<xsd:annotation>
<xsd:documentation><![CDATA[
Symfony XML Services Schema, version 1.0
Authors: Fabien Potencier
This defines a way to describe PHP objects (services) and their
dependencies.
]]></xsd:documentation>
</xsd:annotation>
<xsd:element name="container" type="container" />
<xsd:complexType name="container">
<xsd:annotation>
<xsd:documentation><![CDATA[
The root element of a service file.
]]></xsd:documentation>
</xsd:annotation>
<xsd:sequence>
<xsd:any namespace="##any" processContents="lax" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="imports" type="imports" minOccurs="0" maxOccurs="1" />
<xsd:element name="parameters" type="parameters" minOccurs="0" maxOccurs="1" />
<xsd:element name="services" type="services" minOccurs="0" maxOccurs="1" />
<xsd:any namespace="##any" processContents="lax" minOccurs="0" maxOccurs="unbounded" />
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="services">
<xsd:annotation>
<xsd:documentation><![CDATA[
Enclosing element for the definition of all services
]]></xsd:documentation>
</xsd:annotation>
<xsd:sequence>
<xsd:element name="service" type="service" minOccurs="1" maxOccurs="unbounded" />
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="imports">
<xsd:annotation>
<xsd:documentation><![CDATA[
Enclosing element for the import elements
]]></xsd:documentation>
</xsd:annotation>
<xsd:sequence>
<xsd:element name="import" type="import" minOccurs="1" maxOccurs="unbounded" />
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="import">
<xsd:annotation>
<xsd:documentation><![CDATA[
Import an external resource defining other services or parameters
]]></xsd:documentation>
</xsd:annotation>
<xsd:attribute name="resource" type="xsd:string" use="required" />
<xsd:attribute name="class" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The PHP class able to load the resource. If not defined, the loader uses the current loader.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:complexType name="configurator">
<xsd:attribute name="id" type="xsd:string" />
<xsd:attribute name="service" type="xsd:string" />
<xsd:attribute name="class" type="xsd:string" />
<xsd:attribute name="method" type="xsd:string" />
<xsd:attribute name="function" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="service">
<xsd:choice maxOccurs="unbounded">
<xsd:element name="file" type="xsd:string" minOccurs="0" maxOccurs="1" />
<xsd:element name="argument" type="argument" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="configurator" type="configurator" minOccurs="0" maxOccurs="1" />
<xsd:element name="call" type="call" minOccurs="0" maxOccurs="unbounded" />
</xsd:choice>
<xsd:attribute name="id" type="xsd:string" />
<xsd:attribute name="class" type="xsd:string" />
<xsd:attribute name="shared" type="boolean" />
<xsd:attribute name="constructor" type="xsd:string" />
<xsd:attribute name="alias" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="parameters">
<xsd:sequence>
<xsd:element name="parameter" type="parameter" minOccurs="1" maxOccurs="unbounded" />
</xsd:sequence>
<xsd:attribute name="type" type="parameter_type" />
<xsd:attribute name="key" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="parameter" mixed="true">
<xsd:sequence>
<xsd:element name="parameter" type="parameter" minOccurs="0" maxOccurs="unbounded" />
</xsd:sequence>
<xsd:attribute name="type" type="parameter_type" />
<xsd:attribute name="id" type="xsd:string" />
<xsd:attribute name="key" type="xsd:string" />
<xsd:attribute name="on-invalid" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="argument" mixed="true">
<xsd:choice maxOccurs="unbounded">
<xsd:element name="argument" type="argument" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="service" type="service" />
</xsd:choice>
<xsd:attribute name="type" type="argument_type" />
<xsd:attribute name="id" type="xsd:string" />
<xsd:attribute name="key" type="xsd:string" />
<xsd:attribute name="on-invalid" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="call" mixed="true">
<xsd:choice maxOccurs="unbounded">
<xsd:element name="argument" type="argument" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="service" type="service" />
</xsd:choice>
<xsd:attribute name="method" type="xsd:string" />
</xsd:complexType>
<xsd:simpleType name="parameter_type">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="collection" />
<xsd:enumeration value="service" />
<xsd:enumeration value="string" />
<xsd:enumeration value="constant" />
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="argument_type">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="collection" />
<xsd:enumeration value="service" />
<xsd:enumeration value="string" />
<xsd:enumeration value="constant" />
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="boolean">
<xsd:restriction base="xsd:string">
<xsd:pattern value="(%.+%|true|false)" />
</xsd:restriction>
</xsd:simpleType>
</xsd:schema>

View File

@ -1,113 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="http://symfony-project.org/2.0/container" targetNamespace="http://symfony-project.org/2.0/container" elementFormDefault="qualified">
<xs:element name="container" type="container" />
<xs:complexType name="container">
<xs:sequence>
<xs:element name="imports" type="imports" minOccurs="0" maxOccurs="1" />
<xs:element name="parameters" type="parameters" minOccurs="0" maxOccurs="1" />
<xs:element name="services" type="services" minOccurs="0" maxOccurs="1" />
<xs:any namespace="##any" processContents="skip" minOccurs="0" maxOccurs="unbounded" />
</xs:sequence>
</xs:complexType>
<xs:complexType name="services">
<xs:sequence>
<xs:element name="service" type="service" minOccurs="1" maxOccurs="unbounded" />
</xs:sequence>
</xs:complexType>
<xs:complexType name="imports">
<xs:sequence>
<xs:element name="import" type="import" minOccurs="1" maxOccurs="unbounded" />
</xs:sequence>
</xs:complexType>
<xs:complexType name="import">
<xs:attribute name="resource" type="xs:string" use="required" />
<xs:attribute name="class" type="xs:string" />
</xs:complexType>
<xs:complexType name="configurator">
<xs:attribute name="id" type="xs:string" />
<xs:attribute name="service" type="xs:string" />
<xs:attribute name="class" type="xs:string" />
<xs:attribute name="method" type="xs:string" />
<xs:attribute name="function" type="xs:string" />
</xs:complexType>
<xs:complexType name="service">
<xs:choice maxOccurs="unbounded">
<xs:element name="file" type="xs:string" minOccurs="0" maxOccurs="1" />
<xs:element name="argument" type="argument" minOccurs="0" maxOccurs="unbounded" />
<xs:element name="configurator" type="configurator" minOccurs="0" maxOccurs="1" />
<xs:element name="call" type="call" minOccurs="0" maxOccurs="unbounded" />
</xs:choice>
<xs:attribute name="id" type="xs:string" />
<xs:attribute name="class" type="xs:string" />
<xs:attribute name="shared" type="boolean" />
<xs:attribute name="constructor" type="xs:string" />
<xs:attribute name="alias" type="xs:string" />
</xs:complexType>
<xs:complexType name="parameters">
<xs:sequence>
<xs:element name="parameter" type="parameter" minOccurs="1" maxOccurs="unbounded" />
</xs:sequence>
<xs:attribute name="type" type="parameter_type" />
<xs:attribute name="key" type="xs:string" />
</xs:complexType>
<xs:complexType name="parameter" mixed="true">
<xs:sequence>
<xs:element name="parameter" type="parameter" minOccurs="0" maxOccurs="unbounded" />
</xs:sequence>
<xs:attribute name="type" type="parameter_type" />
<xs:attribute name="id" type="xs:string" />
<xs:attribute name="key" type="xs:string" />
<xs:attribute name="on-invalid" type="xs:string" />
</xs:complexType>
<xs:complexType name="argument" mixed="true">
<xs:choice maxOccurs="unbounded">
<xs:element name="argument" type="argument" minOccurs="0" maxOccurs="unbounded" />
<xs:element name="service" type="service" />
</xs:choice>
<xs:attribute name="type" type="argument_type" />
<xs:attribute name="id" type="xs:string" />
<xs:attribute name="key" type="xs:string" />
<xs:attribute name="on-invalid" type="xs:string" />
</xs:complexType>
<xs:complexType name="call" mixed="true">
<xs:choice maxOccurs="unbounded">
<xs:element name="argument" type="argument" minOccurs="0" maxOccurs="unbounded" />
<xs:element name="service" type="service" />
</xs:choice>
<xs:attribute name="method" type="xs:string" />
</xs:complexType>
<xs:simpleType name="parameter_type">
<xs:restriction base="xs:string">
<xs:enumeration value="collection" />
<xs:enumeration value="service" />
<xs:enumeration value="string" />
<xs:enumeration value="constant" />
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="argument_type">
<xs:restriction base="xs:string">
<xs:enumeration value="collection" />
<xs:enumeration value="service" />
<xs:enumeration value="string" />
<xs:enumeration value="constant" />
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="boolean">
<xs:restriction base="xs:string">
<xs:pattern value="(%.+%|true|false)" />
</xs:restriction>
</xs:simpleType>
</xs:schema>

View File

@ -17,6 +17,11 @@ class ProjectExtension extends LoaderExtension
} }
public function getNamespace() public function getNamespace()
{
return 'http://www.example.com/schema/project';
}
public function getAlias()
{ {
return 'project'; return 'project';
} }

View File

@ -1,4 +1,6 @@
<?xml version="1.0" ?> <?xml version="1.0" ?>
<container xmlns="http://symfony-project.org/2.0/container"> <container xmlns="http://www.symfony-project.org/schema/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.symfony-project.org/schema/services http://www.symfony-project.org/schema/services/services-1.0.xsd">
</container> </container>

View File

@ -1,7 +1,7 @@
<?xml version="1.0" ?> <?xml version="1.0" ?>
<container xmlns="http://symfony-project.org/2.0/container" <container xmlns="http://www.symfony-project.org/schema/services"
xmlns:project="http://symfony-project.org/2.0/container/project"> xmlns:project="http://www.example.com/schema/project">
<project:bar /> <project:bar />

View File

@ -1,7 +1,7 @@
<?xml version="1.0" ?> <?xml version="1.0" ?>
<container xmlns="http://symfony-project.org/2.0/container" <container xmlns="http://www.symfony-project.org/schema/services"
xmlns:foobar="http://symfony-project.org/2.0/container/foobar"> xmlns:foobar="http://www.example.com/schema/foobar">
<foobar:foobar /> <foobar:foobar />

View File

@ -1,7 +1,7 @@
<?xml version="1.0" ?> <?xml version="1.0" ?>
<container xmlns="http://symfony-project.org/2.0/container" <container xmlns="http://www.symfony-project.org/schema/services"
xmlns:project="http://symfony-project.org/2.0/container/project"> xmlns:project="http://www.example.com/schema/project">
<foobar /> <foobar />

View File

@ -1,6 +1,8 @@
<?xml version="1.0" ?> <?xml version="1.0" ?>
<container xmlns="http://symfony-project.org/2.0/container"> <container xmlns="http://www.symfony-project.org/schema/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.symfony-project.org/schema/services http://www.symfony-project.org/schema/services/services-1.0.xsd">
<parameters> <parameters>
<parameter>a string</parameter> <parameter>a string</parameter>
<parameter key="FOO">bar</parameter> <parameter key="FOO">bar</parameter>

View File

@ -1,6 +1,8 @@
<?xml version="1.0" ?> <?xml version="1.0" ?>
<container xmlns="http://symfony-project.org/2.0/container"> <container xmlns="http://www.symfony-project.org/schema/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.symfony-project.org/schema/services http://www.symfony-project.org/schema/services/services-1.0.xsd">
<parameters> <parameters>
<parameter key="foo">foo</parameter> <parameter key="foo">foo</parameter>
<parameter key="values" type="collection"> <parameter key="values" type="collection">

View File

@ -1,6 +1,8 @@
<?xml version="1.0" ?> <?xml version="1.0" ?>
<container xmlns="http://symfony-project.org/2.0/container"> <container xmlns="http://www.symfony-project.org/schema/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.symfony-project.org/schema/services http://www.symfony-project.org/schema/services/services-1.0.xsd">
<imports> <imports>
<import resource="services2.xml" /> <import resource="services2.xml" />
<import resource="services3.xml" /> <import resource="services3.xml" />

View File

@ -1,6 +1,8 @@
<?xml version="1.0" ?> <?xml version="1.0" ?>
<container xmlns="http://symfony-project.org/2.0/container"> <container xmlns="http://www.symfony-project.org/schema/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.symfony-project.org/schema/services http://www.symfony-project.org/schema/services/services-1.0.xsd">
<services> <services>
<service id="foo" class="FooClass"> <service id="foo" class="FooClass">
<argument type="service"> <argument type="service">

View File

@ -1,6 +1,8 @@
<?xml version="1.0" ?> <?xml version="1.0" ?>
<container xmlns="http://symfony-project.org/2.0/container"> <container xmlns="http://www.symfony-project.org/schema/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.symfony-project.org/schema/services http://www.symfony-project.org/schema/services/services-1.0.xsd">
<services> <services>
<service id="foo" class="FooClass" /> <service id="foo" class="FooClass" />
<service id="baz" class="BazClass" /> <service id="baz" class="BazClass" />

View File

@ -1,6 +1,8 @@
<?xml version="1.0" ?> <?xml version="1.0" ?>
<container xmlns="http://symfony-project.org/2.0/container"> <container xmlns="http://www.symfony-project.org/schema/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.symfony-project.org/schema/services http://www.symfony-project.org/schema/services/services-1.0.xsd">
<services> <services>
<service id="foo" class="BarClass" /> <service id="foo" class="BarClass" />
</services> </services>

View File

@ -1,6 +1,8 @@
<?xml version="1.0" ?> <?xml version="1.0" ?>
<container xmlns="http://symfony-project.org/2.0/container"> <container xmlns="http://www.symfony-project.org/schema/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.symfony-project.org/schema/services http://www.symfony-project.org/schema/services/services-1.0.xsd">
<parameters> <parameters>
<parameter key="foo">bar</parameter> <parameter key="foo">bar</parameter>
<parameter key="bar">foo is %%foo bar</parameter> <parameter key="bar">foo is %%foo bar</parameter>

View File

@ -1,6 +1,8 @@
<?xml version="1.0" ?> <?xml version="1.0" ?>
<container xmlns="http://symfony-project.org/2.0/container"> <container xmlns="http://www.symfony-project.org/schema/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.symfony-project.org/schema/services http://www.symfony-project.org/schema/services/services-1.0.xsd">
<parameters> <parameters>
<parameter key="baz_class">BazClass</parameter> <parameter key="baz_class">BazClass</parameter>
<parameter key="foo_class">FooClass</parameter> <parameter key="foo_class">FooClass</parameter>