[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;
use Symfony\Components\DependencyInjection\Loader\Loader;
use Symfony\Components\DependencyInjection\Loader\LoaderExtensionInterface;
/*
* This file is part of the Symfony framework.
@ -22,13 +22,18 @@ use Symfony\Components\DependencyInjection\Loader\Loader;
*/
class BuilderConfiguration
{
protected $definitions = array();
protected $parameters = array();
protected $aliases = array();
protected $resources = array();
protected $definitions;
protected $parameters;
protected $aliases;
protected $resources;
protected $extensions;
public function __construct(array $definitions = array(), array $parameters = array())
{
$this->aliases = array();
$this->resources = array();
$this->extensions = array();
$this->setDefinitions($definitions);
$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 $values array An array of values to customize the extension
* @param $extension LoaderExtensionInterface A LoaderExtensionInterface instance
* @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
*/
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;
}

View File

@ -26,17 +26,21 @@ class IniFileLoader extends FileLoader
/**
* 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
*
* @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);
$configuration = new BuilderConfiguration();
if (null === $configuration) {
$configuration = new BuilderConfiguration();
}
$configuration->addResource(new FileResource($path));

View File

@ -2,6 +2,8 @@
namespace Symfony\Components\DependencyInjection\Loader;
use Symfony\Components\DependencyInjection\BuilderConfiguration;
/*
* This file is part of the Symfony framework.
*
@ -36,19 +38,20 @@ abstract class LoaderExtension implements LoaderExtensionInterface
/**
* Loads a specific configuration.
*
* @param string The tag name
* @param array An array of configuration values
* @param string $tag The tag name
* @param array $config An array of configuration values
* @param BuilderConfiguration $configuration 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)
{
if (!method_exists($this, $method = $tag.'Load')) {
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;
use Symfony\Components\DependencyInjection\BuilderConfiguration;
/*
* This file is part of the Symfony framework.
*
@ -31,12 +33,15 @@ interface LoaderExtensionInterface
/**
* Loads a specific configuration.
*
* @param string The tag name
* @param array An array of configuration values
* @param string $tag The tag name
* @param array $config An array of configuration values
* @param BuilderConfiguration $configuration 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).

View File

@ -2,6 +2,8 @@
namespace Symfony\Components\DependencyInjection\Loader;
use Symfony\Components\DependencyInjection\BuilderConfiguration;
/*
* 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
* 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
*/
function load($resource);
function load($resource, $main = true, BuilderConfiguration $configuration = null);
static function registerExtension(LoaderExtensionInterface $extension);
}

View File

@ -29,17 +29,21 @@ class XmlFileLoader extends FileLoader
/**
* 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
*/
public function load($file)
public function load($file, $main = true, BuilderConfiguration $configuration = null)
{
$path = $this->findFile($file);
$xml = $this->parseFile($path);
$configuration = new BuilderConfiguration();
if (null === $configuration) {
$configuration = new BuilderConfiguration();
}
$configuration->addResource(new FileResource($path));
@ -58,6 +62,10 @@ class XmlFileLoader extends FileLoader
// extensions
$this->loadFromExtensions($configuration, $xml);
if ($main) {
$configuration->mergeExtensionsConfiguration();
}
return $configuration;
}
@ -77,11 +85,11 @@ class XmlFileLoader extends FileLoader
}
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;
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));
return $loader->load($importedFile);
return $loader->load($importedFile, false, $configuration);
}
protected function parseDefinitions(BuilderConfiguration $configuration, $xml, $file)
@ -325,12 +333,11 @@ EOF
}
$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));
$config->addResource(new FileResource($r->getFileName()));
$configuration->merge($config);
$configuration->loadFromExtension($this->getExtension($node->namespaceURI), $node->localName, $values);
}
}

View File

@ -32,17 +32,21 @@ class YamlFileLoader extends FileLoader
/**
* 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
*/
public function load($file)
public function load($file, $main = true, BuilderConfiguration $configuration = null)
{
$path = $this->findFile($file);
$content = $this->loadFile($path);
$configuration = new BuilderConfiguration();
if (null === $configuration) {
$configuration = new BuilderConfiguration();
}
$configuration->addResource(new FileResource($path));
@ -66,6 +70,10 @@ class YamlFileLoader extends FileLoader
// extensions
$this->loadFromExtensions($configuration, $content);
if ($main) {
$configuration->mergeExtensionsConfiguration();
}
return $configuration;
}
@ -76,11 +84,11 @@ class YamlFileLoader extends FileLoader
}
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;
if (isset($import['class']) && $import['class'] !== get_class($this)) {
@ -101,7 +109,7 @@ class YamlFileLoader extends FileLoader
$importedFile = $this->getAbsolutePath($import['resource'], dirname($file));
return $loader->load($importedFile);
return $loader->load($importedFile, false, $configuration);
}
protected function parseDefinitions(BuilderConfiguration $configuration, $content, $file)
@ -232,12 +240,7 @@ class YamlFileLoader extends FileLoader
$values = array();
}
$config = static::getExtension($namespace)->load($tag, $values);
$r = new \ReflectionObject($this->getExtension($namespace));
$config->addResource(new FileResource($r->getFileName()));
$configuration->merge($config);
$configuration->loadFromExtension($this->getExtension($namespace), $tag, $values);
}
}
}

View File

@ -11,6 +11,7 @@
namespace Symfony\Tests\Components\DependencyInjection\Loader;
use Symfony\Components\DependencyInjection\Builder;
use Symfony\Components\DependencyInjection\BuilderConfiguration;
use Symfony\Components\DependencyInjection\Loader\FileLoader;
class XmlDumperTest extends \PHPUnit_Framework_TestCase
@ -44,7 +45,7 @@ class ProjectLoader extends FileLoader
{
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';
use Symfony\Components\DependencyInjection\BuilderConfiguration;
class LoaderExtensionTest extends \PHPUnit_Framework_TestCase
{
public function testLoad()
@ -19,14 +21,14 @@ class LoaderExtensionTest extends \PHPUnit_Framework_TestCase
$extension = new \ProjectExtension();
try {
$extension->load('foo', array());
$extension->load('foo', array(), new BuilderConfiguration());
$this->fail('->load() throws an InvalidArgumentException if the tag does not exist');
} catch (\Exception $e) {
$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');
}
$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');
}
}

View File

@ -13,10 +13,11 @@ namespace Symfony\Tests\Components\DependencyInjection\Loader;
require_once __DIR__.'/../../../../../fixtures/Symfony/Components/DependencyInjection/includes/ProjectExtension.php';
use Symfony\Components\DependencyInjection\Loader\Loader;
use Symfony\Components\DependencyInjection\BuilderConfiguration;
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
{
public function barLoad(array $config)
public function barLoad(array $config, BuilderConfiguration $configuration)
{
$configuration = new BuilderConfiguration();
$configuration->setDefinition('project.service.bar', new Definition('FooClass'));
$configuration->setParameter('project.parameter.bar', isset($config['foo']) ? $config['foo'] : 'foobar');