tweaked DI container

This commit is contained in:
Johannes Schmitt 2010-12-29 20:12:24 +01:00 committed by Fabien Potencier
parent ba2b1aad28
commit db5e180d37
15 changed files with 376 additions and 45 deletions

View File

@ -62,6 +62,9 @@ abstract class AbstractDoctrineExtensionTest extends TestCase
$loadXml = new XmlFileLoader($container, __DIR__.'/Fixtures/config/xml');
$loadXml->load('dbal_service_multiple_connections.xml');
$loader->dbalLoad(array(), $container);
$container->getCompilerPassConfig()->setOptimizationPasses(array());
$container->getCompilerPassConfig()->setRemovingPasses(array());
$container->freeze();
// doctrine.dbal.mysql_connection
@ -82,6 +85,9 @@ abstract class AbstractDoctrineExtensionTest extends TestCase
$loadXml = new XmlFileLoader($container, __DIR__.'/Fixtures/config/xml');
$loadXml->load('dbal_service_single_connection.xml');
$loader->dbalLoad(array(), $container);
$container->getCompilerPassConfig()->setOptimizationPasses(array());
$container->getCompilerPassConfig()->setRemovingPasses(array());
$container->freeze();
// doctrine.dbal.mysql_connection
@ -126,7 +132,7 @@ abstract class AbstractDoctrineExtensionTest extends TestCase
'auto_generate_proxy_classes' => true,
'mappings' => array('YamlBundle' => array()),
);
$loader->dbalLoad(array(), $container);
$loader->ormLoad($config, $container);
@ -203,6 +209,8 @@ abstract class AbstractDoctrineExtensionTest extends TestCase
$this->loadFromFile($container, 'orm_service_simple_single_entity_manager');
$container->getCompilerPassConfig()->setOptimizationPasses(array());
$container->getCompilerPassConfig()->setRemovingPasses(array());
$container->freeze();
$definition = $container->getDefinition('doctrine.dbal.default_connection');
@ -237,6 +245,8 @@ abstract class AbstractDoctrineExtensionTest extends TestCase
$this->loadFromFile($container, 'orm_service_single_entity_manager');
$container->getCompilerPassConfig()->setOptimizationPasses(array());
$container->getCompilerPassConfig()->setRemovingPasses(array());
$container->freeze();
$definition = $container->getDefinition('doctrine.dbal.default_connection');
@ -274,6 +284,8 @@ abstract class AbstractDoctrineExtensionTest extends TestCase
$this->loadFromFile($container, 'orm_service_multiple_entity_managers');
$container->getCompilerPassConfig()->setOptimizationPasses(array());
$container->getCompilerPassConfig()->setRemovingPasses(array());
$container->freeze();
$definition = $container->getDefinition('doctrine.dbal.conn1_connection');
@ -440,6 +452,8 @@ abstract class AbstractDoctrineExtensionTest extends TestCase
$this->loadFromFile($container, 'orm_service_multiple_entity_managers');
$container->getCompilerPassConfig()->setOptimizationPasses(array());
$container->getCompilerPassConfig()->setRemovingPasses(array());
$container->freeze();
$definition = $container->getDefinition('doctrine.orm.dm1_metadata_cache');
@ -457,6 +471,8 @@ abstract class AbstractDoctrineExtensionTest extends TestCase
$this->loadFromFile($container, 'orm_service_simple_single_entity_manager');
$container->getCompilerPassConfig()->setOptimizationPasses(array());
$container->getCompilerPassConfig()->setRemovingPasses(array());
$container->freeze();
$definition = $container->getDefinition('doctrine.orm.default_metadata_cache');
@ -478,6 +494,8 @@ abstract class AbstractDoctrineExtensionTest extends TestCase
$this->loadFromFile($container, 'orm_imports');
$container->getCompilerPassConfig()->setOptimizationPasses(array());
$container->getCompilerPassConfig()->setRemovingPasses(array());
$container->freeze();
$this->assertEquals('apc', $container->getParameter('doctrine.orm.metadata_cache_driver'));

View File

@ -109,6 +109,8 @@ abstract class AbstractMongoDBExtensionTest extends TestCase
$this->loadFromFile($container, 'mongodb_service_simple_single_connection');
$container->getCompilerPassConfig()->setOptimizationPasses(array());
$container->getCompilerPassConfig()->setRemovingPasses(array());
$container->freeze();
$definition = $container->getDefinition('doctrine.odm.mongodb.default_connection');
@ -141,6 +143,8 @@ abstract class AbstractMongoDBExtensionTest extends TestCase
$this->loadFromFile($container, 'mongodb_service_single_connection');
$container->getCompilerPassConfig()->setOptimizationPasses(array());
$container->getCompilerPassConfig()->setRemovingPasses(array());
$container->freeze();
$definition = $container->getDefinition('doctrine.odm.mongodb.default_connection');
@ -167,6 +171,8 @@ abstract class AbstractMongoDBExtensionTest extends TestCase
$this->loadFromFile($container, 'mongodb_service_multiple_connections');
$container->getCompilerPassConfig()->setOptimizationPasses(array());
$container->getCompilerPassConfig()->setRemovingPasses(array());
$container->freeze();
$definition = $container->getDefinition('doctrine.odm.mongodb.conn1_connection');
@ -259,6 +265,8 @@ abstract class AbstractMongoDBExtensionTest extends TestCase
$this->loadFromFile($container, 'mongodb_service_multiple_connections');
$container->getCompilerPassConfig()->setOptimizationPasses(array());
$container->getCompilerPassConfig()->setRemovingPasses(array());
$container->freeze();
$definition = $container->getDefinition('doctrine.odm.mongodb.dm1_metadata_cache');
@ -276,6 +284,8 @@ abstract class AbstractMongoDBExtensionTest extends TestCase
$this->loadFromFile($container, 'mongodb_service_simple_single_connection');
$container->getCompilerPassConfig()->setOptimizationPasses(array());
$container->getCompilerPassConfig()->setRemovingPasses(array());
$container->freeze();
$definition = $container->getDefinition('doctrine.odm.mongodb.default_metadata_cache');
@ -302,6 +312,8 @@ abstract class AbstractMongoDBExtensionTest extends TestCase
$this->loadFromFile($container, 'odm_imports');
$container->getCompilerPassConfig()->setOptimizationPasses(array());
$container->getCompilerPassConfig()->setRemovingPasses(array());
$container->freeze();
$this->assertEquals('apc', $container->getParameter('doctrine.odm.mongodb.metadata_cache_driver'));

View File

@ -85,11 +85,11 @@
<argument type="service" id="security.access.decision_manager" />
</service>
<service id="security.role_hierarchy" class="%security.role_hierarchy.class%">
<service id="security.role_hierarchy" class="%security.role_hierarchy.class%" public="false">
<argument>%security.role_hierarchy.roles%</argument>
</service>
<service id="security.account_checker" class="%security.account_checker.class%" />
<service id="security.account_checker" class="%security.account_checker.class%" public="false" />
<service id="security.encoder.sha1" class="%security.encoder.digest.class%">
<argument>sha1</argument>
@ -101,57 +101,57 @@
<service id="security.encoder.plain" class="%security.encoder.plain.class%" />
<service id="security.logout.handler.session" class="%security.logout.handler.session.class%"></service>
<service id="security.logout.handler.session" class="%security.logout.handler.session.class%" public="false"></service>
<service id="security.authentication.listener.anonymous" class="%security.authentication.listener.anonymous.class%">
<service id="security.authentication.listener.anonymous" class="%security.authentication.listener.anonymous.class%" public="false">
<argument type="service" id="security.context" />
<argument>%security.anonymous.key%</argument>
<argument type="service" id="logger" on-invalid="null" />
</service>
<service id="security.authentication.trust_resolver" class="%security.authentication.trust_resolver.class%">
<service id="security.authentication.trust_resolver" class="%security.authentication.trust_resolver.class%" public="false">
<argument>%security.authentication.trust_resolver.anonymous_class%</argument>
<argument>%security.authentication.trust_resolver.rememberme_class%</argument>
</service>
<service id="security.authentication.retry_entry_point" class="%security.authentication.retry_entry_point.class%" />
<service id="security.authentication.retry_entry_point" class="%security.authentication.retry_entry_point.class%" public="false" />
<service id="security.authentication.form_entry_point" class="%security.authentication.form_entry_point.class%">
<service id="security.authentication.form_entry_point" class="%security.authentication.form_entry_point.class%" public="false">
<argument>%security.authentication.form.login_path%</argument>
<argument>%security.authentication.form.use_forward%</argument>
</service>
<service id="security.authentication.basic_entry_point" class="%security.authentication.basic_entry_point.class%">
<service id="security.authentication.basic_entry_point" class="%security.authentication.basic_entry_point.class%" public="false">
<argument>%security.authentication.basic_entry_point.realm%</argument>
</service>
<service id="security.authentication.digest_entry_point" class="%security.authentication.digest_entry_point.class%">
<service id="security.authentication.digest_entry_point" class="%security.authentication.digest_entry_point.class%" public="false">
<argument>%security.authentication.digest_entry_point.realm%</argument>
<argument>%security.authentication.digest_entry_point.key%</argument>
</service>
<service id="security.channel_listener" class="%security.channel_listener.class%">
<service id="security.channel_listener" class="%security.channel_listener.class%" public="false">
<argument type="service" id="security.access_map" />
<argument type="service" id="security.authentication.retry_entry_point" />
<argument type="service" id="logger" on-invalid="null" />
</service>
<service id="security.access.decision_manager" class="%security.access.decision_manager.class%">
<service id="security.access.decision_manager" class="%security.access.decision_manager.class%" public="false">
<argument type="collection"></argument>
<argument>%security.access.decision_manager.strategy%</argument>
<argument>%security.access.decision_manager.allow_if_all_abstain%</argument>
<argument>%security.access.decision_manager.allow_if_equal_granted_denied%</argument>
</service>
<service id="security.access_map" class="%security.access_map.class%" />
<service id="security.access_map" class="%security.access_map.class%" public="false" />
<service id="security.access.simple_role_voter" class="%security.access.simple_role_voter.class%">
<service id="security.access.simple_role_voter" class="%security.access.simple_role_voter.class%" public="false">
<tag name="security.voter" />
</service>
<service id="security.access.authenticated_voter" class="%security.access.authenticated_voter.class%">
<service id="security.access.authenticated_voter" class="%security.access.authenticated_voter.class%" public="false">
<argument type="service" id="security.authentication.trust_resolver" />
<tag name="security.voter" />
</service>
<service id="security.access.role_hierarchy_voter" class="%security.access.role_hierarchy_voter.class%">
<service id="security.access.role_hierarchy_voter" class="%security.access.role_hierarchy_voter.class%" public="false">
<argument type="service" id="security.role_hierarchy" />
</service>
@ -159,9 +159,9 @@
<tag name="kernel.listener" priority="128" />
<argument type="service" id="security.firewall.map" />
</service>
<service id="security.firewall.map" class="%security.firewall.map.class%" />
<service id="security.firewall.map" class="%security.firewall.map.class%" public="false" />
<service id="security.context_listener" class="%security.context_listener.class%">
<service id="security.context_listener" class="%security.context_listener.class%" public="false">
<argument type="service" id="security.context" />
<argument type="collection"></argument>
<argument type="service" id="logger" on-invalid="null" />

View File

@ -95,6 +95,9 @@ abstract class SecurityExtensionTest extends TestCase
$security = new SecurityExtension();
$container->registerExtension($security);
$this->loadFromFile($container, $file);
$container->getCompilerPassConfig()->setOptimizationPasses(array());
$container->getCompilerPassConfig()->setRemovingPasses(array());
$container->freeze();
return $container;

View File

@ -0,0 +1,91 @@
<?php
namespace Symfony\Component\DependencyInjection\Compiler;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* Compiler Pass Configuration
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class PassConfig
{
const TYPE_OPTIMIZE = 'optimization';
const TYPE_REMOVE = 'removing';
protected $mergePass;
protected $optimizationPasses;
protected $removingPasses;
public function __construct()
{
$this->mergePass = new MergeExtensionConfigurationPass();
$this->optimizationPasses = array(
new ResolveParameterPlaceHoldersPass(),
new ResolveInterfaceInjectorsPass(),
);
$this->removingPasses = array(
new RemoveUnusedDefinitionsPass(),
);
}
public function getPasses()
{
return array_merge(
array($this->mergePass),
$this->optimizationPasses,
$this->removingPasses
);
}
public function addPass(CompilerPassInterface $pass, $type = self::TYPE_OPTIMIZE)
{
$property = $type.'Passes';
if (!isset($this->$property)) {
throw new \InvalidArgumentException(sprintf('Invalid type "%s".', $type));
}
$passes = &$this->$property;
$passes[] = $pass;
}
public function getOptimizationPasses()
{
return $this->optimizationPasses;
}
public function getRemovingPasses()
{
return $this->removingPasses;
}
public function getMergePass()
{
return $this->mergePass;
}
public function setMergePass(CompilerPassInterface $pass)
{
$this->mergePass = $pass;
}
public function setOptimizationPasses(array $passes)
{
$this->optimizationPasses = $passes;
}
public function setRemovingPasses(array $passes)
{
$this->removingPasses = $passes;
}
}

View File

@ -0,0 +1,70 @@
<?php
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* Removes unused service definitions from the container
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class RemoveUnusedDefinitionsPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
$hasChanged = false;
$aliases = $container->getAliases();
foreach ($container->getDefinitions() as $id => $definition) {
if ($definition->isPublic()) {
continue;
}
if (!in_array($id, $aliases, true) && !$this->isReferenced($container, $id)) {
$container->remove($id);
$hasChanged = true;
}
}
if ($hasChanged) {
$this->process($container);
}
}
protected function isReferenced(ContainerBuilder $container, $id)
{
foreach ($container->getDefinitions() as $definition) {
if ($this->isReferencedByArgument($id, $definition->getArguments())) {
return true;
}
foreach ($definition->getMethodCalls() as $arguments)
{
if ($this->isReferencedByArgument($id, $arguments)) {
return true;
}
}
}
return false;
}
protected function isReferencedByArgument($id, $argument)
{
if (is_array($argument)) {
foreach ($argument as $arg) {
if ($this->isReferencedByArgument($id, $arg)) {
return true;
}
}
} else if ($argument instanceof Reference) {
if ($id === (string) $argument) {
return true;
}
}
return false;
}
}

View File

@ -30,9 +30,8 @@ class ResolveInterfaceInjectorsPass implements CompilerPassInterface
if (null !== $definition->getFactoryService()) {
continue;
}
$defClass = $container->getParameterBag()->resolveValue($definition->getClass());
$definition->setClass($defClass);
if ($injector->supports($defClass)) {
if ($injector->supports($definition->getClass())) {
$injector->processDefinition($definition);
}
}

View File

@ -0,0 +1,90 @@
<?php
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* Resolves all parameter placeholders "%somevalue%" to their real values.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class ResolveParameterPlaceHoldersPass implements CompilerPassInterface
{
protected $parameterBag;
public function process(ContainerBuilder $container)
{
$this->parameterBag = $container->getParameterBag();
foreach ($container->getDefinitions() as $id => $definition) {
$definition->setClass($this->resolveValue($definition->getClass()));
$definition->setArguments($this->resolveValue($definition->getArguments()));
$calls = array();
foreach ($definition->getMethodCalls() as $name => $arguments) {
$calls[$this->resolveValue($name)] = $this->resolveValue($arguments);
}
$definition->setMethodCalls($calls);
}
$aliases = array();
foreach ($container->getAliases() as $name => $target) {
$aliases[$this->resolveValue($name)] = $this->resolveValue($target);
}
$container->setAliases($aliases);
$injectors = array();
foreach ($container->getInterfaceInjectors() as $class => $injector) {
$injector->setClass($this->resolveValue($injector->getClass()));
$injectors[$this->resolveValue($class)] = $injector;
}
$container->setInterfaceInjectors($injectors);
}
protected function resolveValue($value)
{
if (is_array($value)) {
$resolved = array();
foreach ($value as $k => $v) {
$resolved[$this->resolveValue($k)] = $this->resolveValue($v);
}
return $resolved;
} else if (is_string($value)) {
return $this->resolveString($value);
} else {
return $value;
}
}
public function resolveString($value)
{
if (preg_match('/^%[^%]+%$/', $value)) {
return $this->resolveValue($this->parameterBag->resolveValue($value));
}
$self = $this;
$parameterBag = $this->parameterBag;
return preg_replace_callback('/(?<!%)%[^%]+%/',
function($parameter) use ($self, $parameterBag) {
$resolved = $parameterBag->resolveValue($parameter[0]);
if (!is_string($resolved)) {
throw new \RuntimeException('You can only embed strings in other parameters.');
}
return $self->resolveString($resolved);
},
$value
);
}
}

View File

@ -2,6 +2,10 @@
namespace Symfony\Component\DependencyInjection;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass;
use Symfony\Component\DependencyInjection\Compiler\ResolveInterfaceInjectorsPass;
use Symfony\Component\DependencyInjection\Compiler\MergeExtensionConfigurationPass;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
@ -35,7 +39,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
protected $resources = array();
protected $extensionConfigs = array();
protected $injectors = array();
protected $compilerPasses;
protected $compilerPassConfig;
/**
* Constructor
@ -45,10 +49,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
{
parent::__construct($parameterBag);
$passes = array();
$passes[] = new MergeExtensionConfigurationPass();
$passes[] = new ResolveInterfaceInjectorsPass();
$this->compilerPasses = $passes;
$this->compilerPassConfig = new PassConfig();
}
/**
@ -154,28 +155,17 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
*/
public function addCompilerPass(CompilerPassInterface $pass)
{
$this->compilerPasses[] = $pass;
$this->compilerPassConfig->addPass($pass);
}
/**
* Returns the currently registered compiler passes
* Returns the compiler pass config which can then be modified
*
* @return array
* @return PassConfig
*/
public function getCompilerPasses()
public function getCompilerPassConfig()
{
return $this->compilerPasses;
}
/**
* Overwrites all existing passes
*
* @param array $passes
* @return void
*/
public function setCompilerPasses(array $passes)
{
$this->compilerPasses = $passes;
return $this->compilerPassConfig;
}
/**
@ -341,7 +331,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
*/
public function freeze()
{
foreach ($this->compilerPasses as $pass) {
foreach ($this->compilerPassConfig->getPasses() as $pass) {
$pass->process($this);
}
@ -472,6 +462,11 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
});
}
public function setInterfaceInjectors(array $injectors)
{
$this->injectors = $injectors;
}
/**
* Registers a service definition.
*

View File

@ -27,6 +27,7 @@ class Definition
protected $calls;
protected $configurator;
protected $tags;
protected $public;
/**
* Constructor.
@ -41,6 +42,7 @@ class Definition
$this->calls = array();
$this->shared = true;
$this->tags = array();
$this->public = true;
}
/**
@ -337,6 +339,29 @@ class Definition
return $this->shared;
}
/**
* Sets the visibility of this service.
*
* @param Boolean $boolean
* @return Definition The current instance
*/
public function setPublic($boolean)
{
$this->public = (Boolean) $boolean;
return $this;
}
/**
* Whether this service is public facing
*
* @return Boolean
*/
public function isPublic()
{
return $this->public;
}
/**
* Sets a configurator to call after the service is fully initialized.
*

View File

@ -44,6 +44,17 @@ class InterfaceInjector
return $this->class;
}
/**
* Sets the interface class
*
* @param string $class
* @return void
*/
public function setClass($class)
{
$this->class = $class;
}
/**
* Adds method calls if Definition is of required interface
*
@ -80,7 +91,7 @@ class InterfaceInjector
{
if (is_string($object)) {
$reflection = new \ReflectionClass($object);
return $reflection->isSubClassOf($this->class)
|| $object === $this->class;
}

View File

@ -131,7 +131,7 @@ class XmlFileLoader extends FileLoader
$definition = new Definition();
foreach (array('class', 'shared', 'factory-method', 'factory-service') as $key) {
foreach (array('class', 'shared', 'public', 'factory-method', 'factory-service') as $key) {
if (isset($service[$key])) {
$method = 'set'.str_replace('-', '', $key);
$definition->$method((string) $service->getAttributeAsPhp($key));

View File

@ -141,6 +141,10 @@ class YamlFileLoader extends FileLoader
$definition->setShared($service['shared']);
}
if (isset($service['public'])) {
$definition->setPublic($service['public']);
}
if (isset($service['factory_method'])) {
$definition->setFactoryMethod($service['factory_method']);
}

View File

@ -102,6 +102,7 @@
<xsd:attribute name="id" type="xsd:string" />
<xsd:attribute name="class" type="xsd:string" />
<xsd:attribute name="shared" type="boolean" />
<xsd:attribute name="public" type="boolean" />
<xsd:attribute name="factory-method" type="xsd:string" />
<xsd:attribute name="factory-service" type="xsd:string" />
<xsd:attribute name="alias" type="xsd:string" />

View File

@ -112,6 +112,18 @@ class DefinitionTest extends \PHPUnit_Framework_TestCase
$this->assertFalse($def->isShared(), '->isShared() returns false if the instance must not be shared');
}
/**
* @covers Symfony\Component\DependencyInjection\Definition::setPublic
* @covers Symfony\Component\DependencyInjection\Definition::isPublic
*/
public function testSetIsPublic()
{
$def = new Definition('stdClass');
$this->assertTrue($def->isPublic(), '->isPublic() returns true by default');
$this->assertSame($def, $def->setPublic(false), '->setPublic() implements a fluent interface');
$this->assertFalse($def->isPublic(), '->isPublic() returns false if the instance must not be public.');
}
/**
* @covers Symfony\Component\DependencyInjection\Definition::setConfigurator
* @covers Symfony\Component\DependencyInjection\Definition::getConfigurator