[DependencyInjection] changed the extension mechanism to allow an extension to be inherit and merge from an existing configuration

This commit is contained in:
Fabien Potencier 2010-06-07 07:57:01 +02:00
parent a79ad894f9
commit b057ef613f
11 changed files with 109 additions and 54 deletions

View File

@ -2,7 +2,7 @@
namespace Symfony\Components\DependencyInjection; namespace Symfony\Components\DependencyInjection;
use Symfony\Components\DependencyInjection\Loader\Loader; use Symfony\Components\DependencyInjection\Loader\LoaderExtensionInterface;
/* /*
* This file is part of the Symfony framework. * This file is part of the Symfony framework.
@ -22,13 +22,18 @@ use Symfony\Components\DependencyInjection\Loader\Loader;
*/ */
class BuilderConfiguration class BuilderConfiguration
{ {
protected $definitions = array(); protected $definitions;
protected $parameters = array(); protected $parameters;
protected $aliases = array(); protected $aliases;
protected $resources = array(); protected $resources;
protected $extensions;
public function __construct(array $definitions = array(), array $parameters = array()) public function __construct(array $definitions = array(), array $parameters = array())
{ {
$this->aliases = array();
$this->resources = array();
$this->extensions = array();
$this->setDefinitions($definitions); $this->setDefinitions($definitions);
$this->setParameters($parameters); $this->setParameters($parameters);
} }
@ -82,20 +87,42 @@ class BuilderConfiguration
} }
/** /**
* Merges the configuration given by an extension. * Loads the configuration for an extension.
* *
* @param $key string The extension tag to load (namespace.tag) * @param $extension LoaderExtensionInterface A LoaderExtensionInterface instance
* @param $values array An array of values to customize the extension * @param $tag string The extension tag to load (without the namespace - namespace.tag)
* @param $values array An array of values that customizes the extension
* *
* @return BuilderConfiguration The current instance * @return BuilderConfiguration The current instance
*/ */
public function mergeExtension($key, array $values = array()) public function loadFromExtension(LoaderExtensionInterface $extension, $tag, array $values = array())
{ {
list($namespace, $tag) = explode('.', $key); $namespace = $extension->getAlias();
$config = Loader::getExtension($namespace)->load($tag, $values); if (!isset($this->extensions[$namespace])) {
$this->extensions[$namespace] = new self();
$this->merge($config); $r = new \ReflectionObject($extension);
$this->extensions[$namespace]->addResource(new FileResource($r->getFileName()));
}
$this->extensions[$namespace] = $extension->load($tag, $values, $this->extensions[$namespace]);
return $this;
}
/**
* Merges the extension configuration.
*
* @return BuilderConfiguration The current instance
*/
public function mergeExtensionsConfiguration()
{
foreach ($this->extensions as $name => $configuration) {
$this->merge($configuration);
}
$this->extensions = array();
return $this; return $this;
} }

View File

@ -26,17 +26,21 @@ class IniFileLoader extends FileLoader
/** /**
* Loads a resource. * Loads a resource.
* *
* @param string $file An INI file path * @param mixed $resource The resource
* @param Boolean $main Whether this is the main load() call
* @param BuilderConfiguration $configuration A BuilderConfiguration instance to use for the configuration
* *
* @return BuilderConfiguration A BuilderConfiguration instance * @return BuilderConfiguration A BuilderConfiguration instance
* *
* @throws \InvalidArgumentException When ini file is not valid * @throws \InvalidArgumentException When ini file is not valid
*/ */
public function load($file) public function load($file, $main = true, BuilderConfiguration $configuration = null)
{ {
$path = $this->findFile($file); $path = $this->findFile($file);
$configuration = new BuilderConfiguration(); if (null === $configuration) {
$configuration = new BuilderConfiguration();
}
$configuration->addResource(new FileResource($path)); $configuration->addResource(new FileResource($path));

View File

@ -2,6 +2,8 @@
namespace Symfony\Components\DependencyInjection\Loader; namespace Symfony\Components\DependencyInjection\Loader;
use Symfony\Components\DependencyInjection\BuilderConfiguration;
/* /*
* This file is part of the Symfony framework. * This file is part of the Symfony framework.
* *
@ -36,19 +38,20 @@ abstract class LoaderExtension implements LoaderExtensionInterface
/** /**
* Loads a specific configuration. * Loads a specific configuration.
* *
* @param string The tag name * @param string $tag The tag name
* @param array An array of configuration values * @param array $config An array of configuration values
* @param BuilderConfiguration $configuration A BuilderConfiguration instance
* *
* @return BuilderConfiguration A BuilderConfiguration instance * @return BuilderConfiguration A BuilderConfiguration instance
* *
* @throws \InvalidArgumentException When provided tag is not defined in this extension * @throws \InvalidArgumentException When provided tag is not defined in this extension
*/ */
public function load($tag, array $config) public function load($tag, array $config, BuilderConfiguration $configuration)
{ {
if (!method_exists($this, $method = $tag.'Load')) { if (!method_exists($this, $method = $tag.'Load')) {
throw new \InvalidArgumentException(sprintf('The tag "%s" is not defined in the "%s" extension.', $tag, $this->getNamespace())); throw new \InvalidArgumentException(sprintf('The tag "%s" is not defined in the "%s" extension.', $tag, $this->getNamespace()));
} }
return $this->$method($config); return $this->$method($config, $configuration);
} }
} }

View File

@ -2,6 +2,8 @@
namespace Symfony\Components\DependencyInjection\Loader; namespace Symfony\Components\DependencyInjection\Loader;
use Symfony\Components\DependencyInjection\BuilderConfiguration;
/* /*
* This file is part of the Symfony framework. * This file is part of the Symfony framework.
* *
@ -31,12 +33,15 @@ interface LoaderExtensionInterface
/** /**
* Loads a specific configuration. * Loads a specific configuration.
* *
* @param string The tag name * @param string $tag The tag name
* @param array An array of configuration values * @param array $config An array of configuration values
* @param BuilderConfiguration $configuration A BuilderConfiguration instance
* *
* @return BuilderConfiguration A BuilderConfiguration instance * @return BuilderConfiguration A BuilderConfiguration instance
*
* @throws \InvalidArgumentException When provided tag is not defined in this extension
*/ */
public function load($tag, array $config); public function load($tag, array $config, BuilderConfiguration $configuration);
/** /**
* Returns the namespace to be used for this extension (XML namespace). * Returns the namespace to be used for this extension (XML namespace).

View File

@ -2,6 +2,8 @@
namespace Symfony\Components\DependencyInjection\Loader; namespace Symfony\Components\DependencyInjection\Loader;
use Symfony\Components\DependencyInjection\BuilderConfiguration;
/* /*
* This file is part of the Symfony framework. * This file is part of the Symfony framework.
* *
@ -56,11 +58,13 @@ interface LoaderInterface
* If you load file1.xml and file2.xml in this order, the value of complex * If you load file1.xml and file2.xml in this order, the value of complex
* will be "foo". * will be "foo".
* *
* @param mixed $resource The resource * @param mixed $resource The resource
* @param Boolean $main Whether this is the main load() call
* @param BuilderConfiguration $configuration A BuilderConfiguration instance to use for the configuration
* *
* @return BuilderConfiguration A BuilderConfiguration instance * @return BuilderConfiguration A BuilderConfiguration instance
*/ */
function load($resource); function load($resource, $main = true, BuilderConfiguration $configuration = null);
static function registerExtension(LoaderExtensionInterface $extension); static function registerExtension(LoaderExtensionInterface $extension);
} }

View File

@ -29,17 +29,21 @@ class XmlFileLoader extends FileLoader
/** /**
* Loads an array of XML files. * Loads an array of XML files.
* *
* @param string $file An XML file path * @param mixed $resource The resource
* @param Boolean $main Whether this is the main load() call
* @param BuilderConfiguration $configuration A BuilderConfiguration instance to use for the configuration
* *
* @return BuilderConfiguration A BuilderConfiguration instance * @return BuilderConfiguration A BuilderConfiguration instance
*/ */
public function load($file) public function load($file, $main = true, BuilderConfiguration $configuration = null)
{ {
$path = $this->findFile($file); $path = $this->findFile($file);
$xml = $this->parseFile($path); $xml = $this->parseFile($path);
$configuration = new BuilderConfiguration(); if (null === $configuration) {
$configuration = new BuilderConfiguration();
}
$configuration->addResource(new FileResource($path)); $configuration->addResource(new FileResource($path));
@ -58,6 +62,10 @@ class XmlFileLoader extends FileLoader
// extensions // extensions
$this->loadFromExtensions($configuration, $xml); $this->loadFromExtensions($configuration, $xml);
if ($main) {
$configuration->mergeExtensionsConfiguration();
}
return $configuration; return $configuration;
} }
@ -77,11 +85,11 @@ class XmlFileLoader extends FileLoader
} }
foreach ($xml->imports->import as $import) { foreach ($xml->imports->import as $import) {
$configuration->merge($this->parseImport($import, $file)); $this->parseImport($configuration, $import, $file);
} }
} }
protected function parseImport($import, $file) protected function parseImport(BuilderConfiguration $configuration, $import, $file)
{ {
$class = null; $class = null;
if (isset($import['class']) && $import['class'] !== get_class($this)) { if (isset($import['class']) && $import['class'] !== get_class($this)) {
@ -102,7 +110,7 @@ class XmlFileLoader extends FileLoader
$importedFile = $this->getAbsolutePath((string) $import['resource'], dirname($file)); $importedFile = $this->getAbsolutePath((string) $import['resource'], dirname($file));
return $loader->load($importedFile); return $loader->load($importedFile, false, $configuration);
} }
protected function parseDefinitions(BuilderConfiguration $configuration, $xml, $file) protected function parseDefinitions(BuilderConfiguration $configuration, $xml, $file)
@ -325,12 +333,11 @@ EOF
} }
$values = static::convertDomElementToArray($node); $values = static::convertDomElementToArray($node);
$config = $this->getExtension($node->namespaceURI)->load($node->localName, is_array($values) ? $values : array($values)); if (!is_array($values)) {
$values = array();
}
$r = new \ReflectionObject($this->getExtension($node->namespaceURI)); $configuration->loadFromExtension($this->getExtension($node->namespaceURI), $node->localName, $values);
$config->addResource(new FileResource($r->getFileName()));
$configuration->merge($config);
} }
} }

View File

@ -32,17 +32,21 @@ class YamlFileLoader extends FileLoader
/** /**
* Loads an array of Yaml files. * Loads an array of Yaml files.
* *
* @param string $file A YAML file path * @param mixed $resource The resource
* @param Boolean $main Whether this is the main load() call
* @param BuilderConfiguration $configuration A BuilderConfiguration instance to use for the configuration
* *
* @return BuilderConfiguration A BuilderConfiguration instance * @return BuilderConfiguration A BuilderConfiguration instance
*/ */
public function load($file) public function load($file, $main = true, BuilderConfiguration $configuration = null)
{ {
$path = $this->findFile($file); $path = $this->findFile($file);
$content = $this->loadFile($path); $content = $this->loadFile($path);
$configuration = new BuilderConfiguration(); if (null === $configuration) {
$configuration = new BuilderConfiguration();
}
$configuration->addResource(new FileResource($path)); $configuration->addResource(new FileResource($path));
@ -66,6 +70,10 @@ class YamlFileLoader extends FileLoader
// extensions // extensions
$this->loadFromExtensions($configuration, $content); $this->loadFromExtensions($configuration, $content);
if ($main) {
$configuration->mergeExtensionsConfiguration();
}
return $configuration; return $configuration;
} }
@ -76,11 +84,11 @@ class YamlFileLoader extends FileLoader
} }
foreach ($content['imports'] as $import) { foreach ($content['imports'] as $import) {
$configuration->merge($this->parseImport($import, $file)); $this->parseImport($configuration, $import, $file);
} }
} }
protected function parseImport($import, $file) protected function parseImport(BuilderConfiguration $configuration, $import, $file)
{ {
$class = null; $class = null;
if (isset($import['class']) && $import['class'] !== get_class($this)) { if (isset($import['class']) && $import['class'] !== get_class($this)) {
@ -101,7 +109,7 @@ class YamlFileLoader extends FileLoader
$importedFile = $this->getAbsolutePath($import['resource'], dirname($file)); $importedFile = $this->getAbsolutePath($import['resource'], dirname($file));
return $loader->load($importedFile); return $loader->load($importedFile, false, $configuration);
} }
protected function parseDefinitions(BuilderConfiguration $configuration, $content, $file) protected function parseDefinitions(BuilderConfiguration $configuration, $content, $file)
@ -232,12 +240,7 @@ class YamlFileLoader extends FileLoader
$values = array(); $values = array();
} }
$config = static::getExtension($namespace)->load($tag, $values); $configuration->loadFromExtension($this->getExtension($namespace), $tag, $values);
$r = new \ReflectionObject($this->getExtension($namespace));
$config->addResource(new FileResource($r->getFileName()));
$configuration->merge($config);
} }
} }
} }

View File

@ -11,6 +11,7 @@
namespace Symfony\Tests\Components\DependencyInjection\Loader; namespace Symfony\Tests\Components\DependencyInjection\Loader;
use Symfony\Components\DependencyInjection\Builder; use Symfony\Components\DependencyInjection\Builder;
use Symfony\Components\DependencyInjection\BuilderConfiguration;
use Symfony\Components\DependencyInjection\Loader\FileLoader; use Symfony\Components\DependencyInjection\Loader\FileLoader;
class XmlDumperTest extends \PHPUnit_Framework_TestCase class XmlDumperTest extends \PHPUnit_Framework_TestCase
@ -44,7 +45,7 @@ class ProjectLoader extends FileLoader
{ {
public $paths; public $paths;
public function load($resource) public function load($resource, $main = true, BuilderConfiguration $configuration = null)
{ {
} }

View File

@ -12,6 +12,8 @@ namespace Symfony\Tests\Components\DependencyInjection\Loader;
require_once __DIR__.'/../../../../../fixtures/Symfony/Components/DependencyInjection/includes/ProjectExtension.php'; require_once __DIR__.'/../../../../../fixtures/Symfony/Components/DependencyInjection/includes/ProjectExtension.php';
use Symfony\Components\DependencyInjection\BuilderConfiguration;
class LoaderExtensionTest extends \PHPUnit_Framework_TestCase class LoaderExtensionTest extends \PHPUnit_Framework_TestCase
{ {
public function testLoad() public function testLoad()
@ -19,14 +21,14 @@ class LoaderExtensionTest extends \PHPUnit_Framework_TestCase
$extension = new \ProjectExtension(); $extension = new \ProjectExtension();
try { try {
$extension->load('foo', array()); $extension->load('foo', array(), new BuilderConfiguration());
$this->fail('->load() throws an InvalidArgumentException if the tag does not exist'); $this->fail('->load() throws an InvalidArgumentException if the tag does not exist');
} catch (\Exception $e) { } catch (\Exception $e) {
$this->assertInstanceOf('\InvalidArgumentException', $e, '->load() throws an InvalidArgumentException if the tag does not exist'); $this->assertInstanceOf('\InvalidArgumentException', $e, '->load() throws an InvalidArgumentException if the tag does not exist');
$this->assertEquals('The tag "foo" is not defined in the "http://www.example.com/schema/project" extension.', $e->getMessage(), '->load() throws an InvalidArgumentException if the tag does not exist'); $this->assertEquals('The tag "foo" is not defined in the "http://www.example.com/schema/project" extension.', $e->getMessage(), '->load() throws an InvalidArgumentException if the tag does not exist');
} }
$config = $extension->load('bar', array('foo' => 'bar')); $config = $extension->load('bar', array('foo' => 'bar'), new BuilderConfiguration());
$this->assertEquals(array('project.parameter.bar' => 'bar'), $config->getParameters(), '->load() calls the method tied to the given tag'); $this->assertEquals(array('project.parameter.bar' => 'bar'), $config->getParameters(), '->load() calls the method tied to the given tag');
} }
} }

View File

@ -13,10 +13,11 @@ namespace Symfony\Tests\Components\DependencyInjection\Loader;
require_once __DIR__.'/../../../../../fixtures/Symfony/Components/DependencyInjection/includes/ProjectExtension.php'; require_once __DIR__.'/../../../../../fixtures/Symfony/Components/DependencyInjection/includes/ProjectExtension.php';
use Symfony\Components\DependencyInjection\Loader\Loader; use Symfony\Components\DependencyInjection\Loader\Loader;
use Symfony\Components\DependencyInjection\BuilderConfiguration;
class ProjectLoader1 extends Loader class ProjectLoader1 extends Loader
{ {
public function load($resource) public function load($resource, $main = true, BuilderConfiguration $configuration = null)
{ {
} }
} }

View File

@ -6,10 +6,8 @@ use Symfony\Components\DependencyInjection\Loader\LoaderExtension;
class ProjectExtension extends LoaderExtension class ProjectExtension extends LoaderExtension
{ {
public function barLoad(array $config) public function barLoad(array $config, BuilderConfiguration $configuration)
{ {
$configuration = new BuilderConfiguration();
$configuration->setDefinition('project.service.bar', new Definition('FooClass')); $configuration->setDefinition('project.service.bar', new Definition('FooClass'));
$configuration->setParameter('project.parameter.bar', isset($config['foo']) ? $config['foo'] : 'foobar'); $configuration->setParameter('project.parameter.bar', isset($config['foo']) ? $config['foo'] : 'foobar');