diff --git a/src/Symfony/Component/DependencyInjection/CHANGELOG.md b/src/Symfony/Component/DependencyInjection/CHANGELOG.md index 7c4ff51e8c..c13da0b280 100644 --- a/src/Symfony/Component/DependencyInjection/CHANGELOG.md +++ b/src/Symfony/Component/DependencyInjection/CHANGELOG.md @@ -7,6 +7,7 @@ CHANGELOG * allowed specifying a directory to recursively load all configuration files it contains * deprecated the concept of scopes * added `Definition::setShared()` and `Definition::isShared()` + * added ResettableContainerInterface to be able to reset the container to release memory on shutdown 2.7.0 ----- diff --git a/src/Symfony/Component/DependencyInjection/Container.php b/src/Symfony/Component/DependencyInjection/Container.php index 0222d7063b..513fc9de0f 100644 --- a/src/Symfony/Component/DependencyInjection/Container.php +++ b/src/Symfony/Component/DependencyInjection/Container.php @@ -13,6 +13,7 @@ namespace Symfony\Component\DependencyInjection; use Symfony\Component\DependencyInjection\Exception\InactiveScopeException; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Exception\LogicException; use Symfony\Component\DependencyInjection\Exception\RuntimeException; use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException; @@ -60,7 +61,7 @@ use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag; * * @api */ -class Container implements IntrospectableContainerInterface +class Container implements IntrospectableContainerInterface, ResettableContainerInterface { /** * @var ParameterBagInterface @@ -375,6 +376,18 @@ class Container implements IntrospectableContainerInterface return isset($this->services[$id]) || array_key_exists($id, $this->services); } + /** + * {@inheritdoc} + */ + public function reset() + { + if (!empty($this->scopedServices)) { + throw new LogicException('Resetting the container is not allowed when a scope is active.'); + } + + $this->services = array(); + } + /** * Gets all service ids. * diff --git a/src/Symfony/Component/DependencyInjection/ResettableContainerInterface.php b/src/Symfony/Component/DependencyInjection/ResettableContainerInterface.php new file mode 100644 index 0000000000..b74e676245 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/ResettableContainerInterface.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection; + +/** + * ResettableContainerInterface defines additional resetting functionality + * for containers, allowing to release shared services when the container is + * not needed anymore. + * + * @author Christophe Coevoet + */ +interface ResettableContainerInterface extends ContainerInterface +{ + /** + * Resets shared services from the container. + * + * The container is not intended to be used again after being reset in a normal workflow. This method is + * meant as a way to release references for ref-counting. + * A subsequent call to ContainerInterface::get will recreate a new instance of the shared service. + */ + public function reset(); +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/ContainerTest.php b/src/Symfony/Component/DependencyInjection/Tests/ContainerTest.php index 09b4a12e2f..cffef39008 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/ContainerTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/ContainerTest.php @@ -320,6 +320,49 @@ class ContainerTest extends \PHPUnit_Framework_TestCase $this->assertTrue($sc->initialized('alias'), '->initialized() returns true for alias if aliased service is initialized'); } + public function testReset() + { + $c = new Container(); + $c->set('bar', new \stdClass()); + + $c->reset(); + + $this->assertNull($c->get('bar', ContainerInterface::NULL_ON_INVALID_REFERENCE)); + } + + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\LogicException + * @expectedExceptionMessage Resetting the container is not allowed when a scope is active. + * @group legacy + */ + public function testCannotResetInActiveScope() + { + $c = new Container(); + $c->addScope(new Scope('foo')); + $c->set('bar', new \stdClass()); + + $c->enterScope('foo'); + + $c->reset(); + } + + /** + * @group legacy + */ + public function testResetAfterLeavingScope() + { + $c = new Container(); + $c->addScope(new Scope('foo')); + $c->set('bar', new \stdClass()); + + $c->enterScope('foo'); + $c->leaveScope('foo'); + + $c->reset(); + + $this->assertNull($c->get('bar', ContainerInterface::NULL_ON_INVALID_REFERENCE)); + } + /** * @group legacy */ diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index 5f714d97d5..d70439bd08 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -23,6 +23,7 @@ use Symfony\Component\DependencyInjection\Loader\IniFileLoader; use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; use Symfony\Component\DependencyInjection\Loader\DirectoryLoader; use Symfony\Component\DependencyInjection\Loader\ClosureLoader; +use Symfony\Component\DependencyInjection\ResettableContainerInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Bundle\BundleInterface; @@ -180,6 +181,10 @@ abstract class Kernel implements KernelInterface, TerminableInterface $bundle->setContainer(null); } + if ($this->container instanceof ResettableContainerInterface) { + $this->container->reset(); + } + $this->container = null; }