adds scope to the DI container
This commit is contained in:
parent
59a974e8f6
commit
1d5b6ed908
@ -70,6 +70,8 @@ class FrameworkBundle extends Bundle
|
||||
{
|
||||
parent::registerExtensions($container);
|
||||
|
||||
$container->addScope('request');
|
||||
|
||||
$container->addCompilerPass(new AddSecurityVotersPass());
|
||||
$container->addCompilerPass(new ConverterManagerPass());
|
||||
$container->addCompilerPass(new RoutingResolverPass());
|
||||
|
49
src/Symfony/Bundle/FrameworkBundle/HttpKernel.php
Normal file
49
src/Symfony/Bundle/FrameworkBundle/HttpKernel.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Bundle\FrameworkBundle;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\HttpKernelInterface;
|
||||
use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\HttpKernel\HttpKernel as BaseHttpKernel;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcher as BaseEventDispatcher;
|
||||
|
||||
/**
|
||||
* This HttpKernel is used to manage scope changes of the DI container.
|
||||
*
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
*/
|
||||
class HttpKernel extends BaseHttpKernel
|
||||
{
|
||||
protected $container;
|
||||
|
||||
public function __construct(ContainerInterface $container, BaseEventDispatcher $eventDispatcher, ControllerResolverInterface $controllerResolver)
|
||||
{
|
||||
parent::__construct($eventDispatcher, $controllerResolver);
|
||||
|
||||
$this->container = $container;
|
||||
}
|
||||
|
||||
public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true)
|
||||
{
|
||||
$this->container->enterScope('request');
|
||||
$this->container->set('request', $request, 'request');
|
||||
|
||||
try {
|
||||
$response = parent::handle($request, $type, $catch);
|
||||
|
||||
if (HttpKernelInterface::MASTER_REQUEST !== $type) {
|
||||
$this->container->leaveScope('request');
|
||||
}
|
||||
|
||||
return $response;
|
||||
} catch (\Exception $e) {
|
||||
if (HttpKernelInterface::MASTER_REQUEST !== $type) {
|
||||
$this->container->leaveScope('request');
|
||||
}
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
@ -6,7 +6,7 @@
|
||||
|
||||
<parameters>
|
||||
<parameter key="event_dispatcher.class">Symfony\Bundle\FrameworkBundle\EventDispatcher</parameter>
|
||||
<parameter key="http_kernel.class">Symfony\Component\HttpKernel\HttpKernel</parameter>
|
||||
<parameter key="http_kernel.class">Symfony\Bundle\FrameworkBundle\HttpKernel</parameter>
|
||||
<parameter key="response.class">Symfony\Component\HttpFoundation\Response</parameter>
|
||||
<parameter key="error_handler.class">Symfony\Component\HttpKernel\Debug\ErrorHandler</parameter>
|
||||
<parameter key="error_handler.level">null</parameter>
|
||||
@ -22,6 +22,7 @@
|
||||
</service>
|
||||
|
||||
<service id="http_kernel" class="%http_kernel.class%">
|
||||
<argument type="service" id="service_container" />
|
||||
<argument type="service" id="event_dispatcher" />
|
||||
<argument type="service" id="controller_resolver" />
|
||||
</service>
|
||||
@ -31,9 +32,9 @@
|
||||
your front controller (app.php) so that it passes an instance of
|
||||
YourRequestClass to the Kernel.
|
||||
-->
|
||||
<service id="request" factory-service="http_kernel" factory-method="getRequest" shared="false" />
|
||||
<service id="request" scope="request" />
|
||||
|
||||
<service id="response" class="%response.class%" shared="false">
|
||||
<service id="response" class="%response.class%" scope="prototype">
|
||||
<call method="setCharset">
|
||||
<argument>%kernel.charset%</argument>
|
||||
</call>
|
||||
|
@ -70,19 +70,19 @@
|
||||
|
||||
<service id="templating.helper.assets" class="%templating.helper.assets.class%">
|
||||
<tag name="templating.helper" alias="assets" />
|
||||
<argument type="service" id="request" />
|
||||
<argument type="service" id="request" strict="false" />
|
||||
<argument>%templating.assets.base_urls%</argument>
|
||||
<argument>%templating.assets.version%</argument>
|
||||
</service>
|
||||
|
||||
<service id="templating.helper.request" class="%templating.helper.request.class%">
|
||||
<tag name="templating.helper" alias="request" />
|
||||
<argument type="service" id="request" />
|
||||
<argument type="service" id="request" strict="false" />
|
||||
</service>
|
||||
|
||||
<service id="templating.helper.session" class="%templating.helper.session.class%">
|
||||
<tag name="templating.helper" alias="session" />
|
||||
<argument type="service" id="request" />
|
||||
<argument type="service" id="request" strict="false" />
|
||||
</service>
|
||||
|
||||
<service id="templating.helper.router" class="%templating.helper.router.class%">
|
||||
|
@ -12,15 +12,15 @@
|
||||
</parameters>
|
||||
|
||||
<services>
|
||||
<service id="test.client" class="%test.client.class%" shared="false">
|
||||
<service id="test.client" class="%test.client.class%" scope="prototype">
|
||||
<argument type="service" id="kernel" />
|
||||
<argument>%test.client.parameters%</argument>
|
||||
<argument type="service" id="test.client.history" />
|
||||
<argument type="service" id="test.client.cookiejar" />
|
||||
</service>
|
||||
|
||||
<service id="test.client.history" class="%test.client.history.class%" shared="false" />
|
||||
<service id="test.client.history" class="%test.client.history.class%" scope="prototype" />
|
||||
|
||||
<service id="test.client.cookiejar" class="%test.client.cookiejar.class%" shared="false" />
|
||||
<service id="test.client.cookiejar" class="%test.client.cookiejar.class%" scope="prototype" />
|
||||
</services>
|
||||
</container>
|
||||
|
134
src/Symfony/Bundle/FrameworkBundle/Tests/HttpKernelTest.php
Normal file
134
src/Symfony/Bundle/FrameworkBundle/Tests/HttpKernelTest.php
Normal file
@ -0,0 +1,134 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Bundle\FrameworkBundle\Tests;
|
||||
|
||||
use Symfony\Component\HttpKernel\HttpKernelInterface;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcher;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Bundle\FrameworkBundle\HttpKernel;
|
||||
|
||||
class HttpKernelTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider getProviderTypes
|
||||
*/
|
||||
public function testHandle($type)
|
||||
{
|
||||
$request = new Request();
|
||||
$expected = new Response();
|
||||
|
||||
$container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface');
|
||||
$container
|
||||
->expects($this->once())
|
||||
->method('enterScope')
|
||||
->with($this->equalTo('request'))
|
||||
;
|
||||
if ($type !== HttpKernelInterface::MASTER_REQUEST) {
|
||||
$container
|
||||
->expects($this->once())
|
||||
->method('leaveScope')
|
||||
->with($this->equalTo('request'))
|
||||
;
|
||||
} else {
|
||||
$container
|
||||
->expects($this->never())
|
||||
->method('leaveScope')
|
||||
;
|
||||
}
|
||||
$container
|
||||
->expects($this->once())
|
||||
->method('set')
|
||||
->with($this->equalTo('request'), $this->equalTo($request), $this->equalTo('request'))
|
||||
;
|
||||
|
||||
$dispatcher = new EventDispatcher();
|
||||
$resolver = $this->getMock('Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface');
|
||||
$kernel = new HttpKernel($container, $dispatcher, $resolver);
|
||||
|
||||
$controller = function() use($expected)
|
||||
{
|
||||
return $expected;
|
||||
};
|
||||
|
||||
$resolver->expects($this->once())
|
||||
->method('getController')
|
||||
->with($request)
|
||||
->will($this->returnValue($controller));
|
||||
$resolver->expects($this->once())
|
||||
->method('getArguments')
|
||||
->with($request, $controller)
|
||||
->will($this->returnValue(array()));
|
||||
|
||||
$actual = $kernel->handle($request, $type);
|
||||
|
||||
$this->assertSame($expected, $actual, '->handle() returns the response');
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getProviderTypes
|
||||
*/
|
||||
public function testHandleRestoresThePreviousRequestOnException($type)
|
||||
{
|
||||
$request = new Request();
|
||||
$expected = new \Exception();
|
||||
|
||||
$container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface');
|
||||
$container
|
||||
->expects($this->once())
|
||||
->method('enterScope')
|
||||
->with($this->equalTo('request'))
|
||||
;
|
||||
if ($type !== HttpKernelInterface::MASTER_REQUEST) {
|
||||
$container
|
||||
->expects($this->once())
|
||||
->method('leaveScope')
|
||||
->with($this->equalTo('request'))
|
||||
;
|
||||
} else {
|
||||
$container
|
||||
->expects($this->never())
|
||||
->method('leaveScope')
|
||||
;
|
||||
}
|
||||
$container
|
||||
->expects($this->once())
|
||||
->method('set')
|
||||
->with($this->equalTo('request'), $this->equalTo($request), $this->equalTo('request'))
|
||||
;
|
||||
|
||||
$dispatcher = new EventDispatcher();
|
||||
$resolver = $this->getMock('Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface');
|
||||
$kernel = new HttpKernel($container, $dispatcher, $resolver);
|
||||
|
||||
$controller = function() use ($expected)
|
||||
{
|
||||
throw $expected;
|
||||
};
|
||||
|
||||
$resolver->expects($this->once())
|
||||
->method('getController')
|
||||
->with($request)
|
||||
->will($this->returnValue($controller));
|
||||
$resolver->expects($this->once())
|
||||
->method('getArguments')
|
||||
->with($request, $controller)
|
||||
->will($this->returnValue(array()));
|
||||
|
||||
try {
|
||||
$kernel->handle($request, $type);
|
||||
$this->fail('->handle() suppresses the controller exception');
|
||||
} catch (\Exception $actual) {
|
||||
$this->assertSame($expected, $actual, '->handle() throws the controller exception');
|
||||
}
|
||||
}
|
||||
|
||||
public function getProviderTypes()
|
||||
{
|
||||
return array(
|
||||
array(HttpKernelInterface::MASTER_REQUEST),
|
||||
array(HttpKernelInterface::SUB_REQUEST),
|
||||
);
|
||||
}
|
||||
}
|
@ -25,37 +25,38 @@ use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
*
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
*/
|
||||
class AnalyzeServiceReferencesPass implements RepeatablePassInterface, CompilerAwareInterface
|
||||
class AnalyzeServiceReferencesPass implements RepeatablePassInterface
|
||||
{
|
||||
protected $graph;
|
||||
protected $container;
|
||||
protected $currentId;
|
||||
protected $currentDefinition;
|
||||
protected $repeatedPass;
|
||||
protected $ignoreMethodCalls;
|
||||
|
||||
public function __construct($ignoreMethodCalls = false)
|
||||
{
|
||||
$this->ignoreMethodCalls = (Boolean) $ignoreMethodCalls;
|
||||
}
|
||||
|
||||
public function setRepeatedPass(RepeatedPass $repeatedPass) {
|
||||
$this->repeatedPass = $repeatedPass;
|
||||
}
|
||||
|
||||
public function setCompiler(Compiler $compiler)
|
||||
{
|
||||
$this->graph = $compiler->getServiceReferenceGraph();
|
||||
}
|
||||
|
||||
public function process(ContainerBuilder $container)
|
||||
{
|
||||
$this->container = $container;
|
||||
|
||||
if (null === $this->graph) {
|
||||
$this->graph = $this->repeatedPass->getCompiler()->getServiceReferenceGraph();
|
||||
}
|
||||
$this->graph = $container->getCompiler()->getServiceReferenceGraph();
|
||||
$this->graph->clear();
|
||||
|
||||
foreach ($container->getDefinitions() as $id => $definition) {
|
||||
$this->currentId = $id;
|
||||
$this->currentDefinition = $definition;
|
||||
$this->processArguments($definition->getArguments());
|
||||
$this->processArguments($definition->getMethodCalls());
|
||||
|
||||
if (!$this->ignoreMethodCalls) {
|
||||
$this->processArguments($definition->getMethodCalls());
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($container->getAliases() as $id => $alias) {
|
||||
|
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Compiler;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
|
||||
/**
|
||||
* Checks your services for circular references
|
||||
*
|
||||
* References from method calls are ignored since we might be able to resolve
|
||||
* these references depending on the order in which services are called.
|
||||
*
|
||||
* Circular reference from method calls will only be detected at run-time.
|
||||
*
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
*/
|
||||
class CheckCircularReferencesPass implements CompilerPassInterface
|
||||
{
|
||||
protected $currentId;
|
||||
protected $currentNode;
|
||||
protected $currentPath;
|
||||
|
||||
public function process(ContainerBuilder $container)
|
||||
{
|
||||
$graph = $container->getCompiler()->getServiceReferenceGraph();
|
||||
|
||||
foreach ($graph->getNodes() as $id => $node) {
|
||||
$this->currentId = $id;
|
||||
$this->currentPath = array($id);
|
||||
|
||||
$this->checkOutEdges($node->getOutEdges());
|
||||
}
|
||||
}
|
||||
|
||||
protected function checkOutEdges(array $edges)
|
||||
{
|
||||
foreach ($edges as $edge) {
|
||||
$node = $edge->getDestNode();
|
||||
$this->currentPath[] = $id = $node->getId();
|
||||
|
||||
if ($this->currentId === $id) {
|
||||
throw new \RuntimeException(sprintf('Circular reference detected for "%s", path: "%s".', $this->currentId, implode(' -> ', $this->currentPath)));
|
||||
}
|
||||
|
||||
$this->checkOutEdges($node->getOutEdges());
|
||||
array_pop($this->currentPath);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Compiler;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
|
||||
/**
|
||||
* Checks the scope of references
|
||||
*
|
||||
* Especially, we disallow services of wider scope to have references to
|
||||
* services of a narrower scope by default since it is generally a sign for a
|
||||
* wrong implementation.
|
||||
*
|
||||
* If someone specifically wants to allow this, then he can set the reference
|
||||
* to strict=false.
|
||||
*
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
*/
|
||||
class CheckReferenceScopePass implements CompilerPassInterface
|
||||
{
|
||||
protected $container;
|
||||
protected $currentId;
|
||||
protected $currentScope;
|
||||
protected $currentScopeAncestors;
|
||||
protected $currentScopeChildren;
|
||||
|
||||
public function process(ContainerBuilder $container)
|
||||
{
|
||||
$this->container = $container;
|
||||
|
||||
$children = $this->container->getScopeChildren();
|
||||
$ancestors = array();
|
||||
|
||||
$scopes = $this->container->getScopes();
|
||||
foreach ($scopes as $name => $parent) {
|
||||
$ancestors[$name] = array($parent);
|
||||
|
||||
while (isset($scopes[$parent])) {
|
||||
$ancestors[$name][] = $parent = $scopes[$parent];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($container->getDefinitions() as $id => $definition) {
|
||||
$this->currentId = $id;
|
||||
$this->currentScope = $scope = $definition->getScope();
|
||||
|
||||
if (ContainerInterface::SCOPE_PROTOTYPE === $scope) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ContainerInterface::SCOPE_CONTAINER === $scope) {
|
||||
$this->currentScopeChildren = array_keys($scopes);
|
||||
$this->currentScopeAncestors = array();
|
||||
} else {
|
||||
$this->currentScopeChildren = $children[$scope];
|
||||
$this->currentScopeAncestors = $ancestors[$scope];
|
||||
}
|
||||
|
||||
$this->validateReferences($definition->getArguments());
|
||||
$this->validateReferences($definition->getMethodCalls());
|
||||
}
|
||||
}
|
||||
|
||||
protected function validateReferences(array $arguments)
|
||||
{
|
||||
foreach ($arguments as $argument) {
|
||||
if (is_array($argument)) {
|
||||
$this->validateReferences($argument);
|
||||
} else if ($argument instanceof Reference) {
|
||||
if (!$argument->isStrict()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (null === $definition = $this->getDefinition($id = (string) $argument)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->currentScope === $scope = $definition->getScope()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (in_array($scope, $this->currentScopeChildren, true)) {
|
||||
throw new \RuntimeException(sprintf(
|
||||
'Scope Widening Injection detected: The definition "%s" references the service "%s" which belongs to a narrower scope. '
|
||||
.'Generally, it is safer to either move "%s" to scope "%s" or alternatively rely on the provider pattern by injecting the container itself, and requesting the service "%s" each time it is needed. '
|
||||
.'In rare, special cases however that might not be necessary, then you can set the reference to strict=false to get rid of this warning.',
|
||||
$this->currentId,
|
||||
$id,
|
||||
$this->currentId,
|
||||
$scope,
|
||||
$id
|
||||
));
|
||||
}
|
||||
|
||||
if (!in_array($scope, $this->currentScopeAncestors, true)) {
|
||||
throw new \RuntimeException(sprintf(
|
||||
'Cross-Scope Injection detected: The definition "%s" references the service "%s" which belongs to another scope hierarchy. '
|
||||
.'This service might not be available consistently. Generally, it is safer to either move the definition "%s" to scope "%s", or '
|
||||
.'declare "%s" as a child scope of "%s". If you can be sure that the other scope is always active, you can set the reference to strict=false to get rid of this warning.',
|
||||
$this->currentId,
|
||||
$id,
|
||||
$this->currentId,
|
||||
$scope,
|
||||
$this->currentScope,
|
||||
$scope
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function getDefinition($id)
|
||||
{
|
||||
if (!$this->container->hasDefinition($id)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->container->getDefinition($id);
|
||||
}
|
||||
}
|
@ -70,10 +70,6 @@ class Compiler
|
||||
|
||||
protected function startPass(CompilerPassInterface $pass)
|
||||
{
|
||||
if ($pass instanceof CompilerAwareInterface) {
|
||||
$pass->setCompiler($this);
|
||||
}
|
||||
|
||||
$this->currentPass = $pass;
|
||||
$this->currentStartTime = microtime(true);
|
||||
}
|
||||
|
@ -1,23 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Compiler;
|
||||
|
||||
/**
|
||||
* This interface can be implemented by passes that need to access the
|
||||
* compiler.
|
||||
*
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
*/
|
||||
interface CompilerAwareInterface
|
||||
{
|
||||
function setCompiler(Compiler $compiler);
|
||||
}
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Compiler;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\DependencyInjection\Definition;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
@ -32,7 +33,7 @@ class InlineServiceDefinitionsPass implements RepeatablePassInterface
|
||||
|
||||
public function process(ContainerBuilder $container)
|
||||
{
|
||||
$this->graph = $this->repeatedPass->getCompiler()->getServiceReferenceGraph();
|
||||
$this->graph = $container->getCompiler()->getServiceReferenceGraph();
|
||||
|
||||
foreach ($container->getDefinitions() as $id => $definition) {
|
||||
$definition->setArguments(
|
||||
@ -56,7 +57,7 @@ class InlineServiceDefinitionsPass implements RepeatablePassInterface
|
||||
}
|
||||
|
||||
if ($this->isInlinableDefinition($container, $id, $definition = $container->getDefinition($id))) {
|
||||
if ($definition->isShared()) {
|
||||
if (ContainerInterface::SCOPE_PROTOTYPE !== $definition->getScope()) {
|
||||
$arguments[$k] = $definition;
|
||||
} else {
|
||||
$arguments[$k] = clone $definition;
|
||||
@ -73,7 +74,7 @@ class InlineServiceDefinitionsPass implements RepeatablePassInterface
|
||||
|
||||
protected function isInlinableDefinition(ContainerBuilder $container, $id, Definition $definition)
|
||||
{
|
||||
if (!$definition->isShared()) {
|
||||
if (ContainerInterface::SCOPE_PROTOTYPE === $definition->getScope()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -46,6 +46,9 @@ class PassConfig
|
||||
new ResolveReferencesToAliasesPass(),
|
||||
new ResolveInterfaceInjectorsPass(),
|
||||
new ResolveInvalidReferencesPass(),
|
||||
new AnalyzeServiceReferencesPass(true),
|
||||
new CheckCircularReferencesPass(),
|
||||
new CheckReferenceScopePass(),
|
||||
);
|
||||
|
||||
$this->removingPasses = array(
|
||||
|
@ -24,7 +24,6 @@ use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
class RemoveUnusedDefinitionsPass implements RepeatablePassInterface
|
||||
{
|
||||
protected $repeatedPass;
|
||||
protected $graph;
|
||||
|
||||
public function setRepeatedPass(RepeatedPass $repeatedPass)
|
||||
{
|
||||
@ -33,7 +32,7 @@ class RemoveUnusedDefinitionsPass implements RepeatablePassInterface
|
||||
|
||||
public function process(ContainerBuilder $container)
|
||||
{
|
||||
$this->graph = $this->repeatedPass->getCompiler()->getServiceReferenceGraph();
|
||||
$graph = $container->getCompiler()->getServiceReferenceGraph();
|
||||
|
||||
$hasChanged = false;
|
||||
foreach ($container->getDefinitions() as $id => $definition) {
|
||||
@ -41,8 +40,8 @@ class RemoveUnusedDefinitionsPass implements RepeatablePassInterface
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->graph->hasNode($id)) {
|
||||
$edges = $this->graph->getNode($id)->getInEdges();
|
||||
if ($graph->hasNode($id)) {
|
||||
$edges = $graph->getNode($id)->getInEdges();
|
||||
$referencingAliases = array();
|
||||
$sourceIds = array();
|
||||
foreach ($edges as $edge) {
|
||||
|
@ -18,10 +18,9 @@ use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
*
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
*/
|
||||
class RepeatedPass implements CompilerPassInterface, CompilerAwareInterface
|
||||
class RepeatedPass implements CompilerPassInterface
|
||||
{
|
||||
protected $repeat;
|
||||
protected $compiler;
|
||||
protected $passes;
|
||||
|
||||
public function __construct(array $passes)
|
||||
@ -37,23 +36,14 @@ class RepeatedPass implements CompilerPassInterface, CompilerAwareInterface
|
||||
$this->passes = $passes;
|
||||
}
|
||||
|
||||
public function setCompiler(Compiler $compiler)
|
||||
{
|
||||
$this->compiler = $compiler;
|
||||
}
|
||||
|
||||
public function getCompiler()
|
||||
{
|
||||
return $this->compiler;
|
||||
}
|
||||
|
||||
public function process(ContainerBuilder $container)
|
||||
{
|
||||
$compiler = $container->getCompiler();
|
||||
$this->repeat = false;
|
||||
foreach ($this->passes as $pass) {
|
||||
$time = microtime(true);
|
||||
$pass->process($container);
|
||||
$this->compiler->addLogMessage(sprintf(
|
||||
$compiler->addLogMessage(sprintf(
|
||||
'%s finished in %.3fs', get_class($pass), microtime(true) - $time
|
||||
));
|
||||
}
|
||||
|
@ -51,6 +51,7 @@ use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag;
|
||||
* (for instance, ignore a setter if the service does not exist)
|
||||
*
|
||||
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
*/
|
||||
class Container implements ContainerInterface
|
||||
{
|
||||
@ -66,7 +67,13 @@ class Container implements ContainerInterface
|
||||
public function __construct(ParameterBagInterface $parameterBag = null)
|
||||
{
|
||||
$this->parameterBag = null === $parameterBag ? new ParameterBag() : $parameterBag;
|
||||
$this->services = array();
|
||||
|
||||
$this->services =
|
||||
$this->scopes =
|
||||
$this->scopeChildren =
|
||||
$this->scopedServices =
|
||||
$this->scopeStacks = array();
|
||||
|
||||
$this->set('service_container', $this);
|
||||
}
|
||||
|
||||
@ -147,10 +154,25 @@ class Container implements ContainerInterface
|
||||
*
|
||||
* @param string $id The service identifier
|
||||
* @param object $service The service instance
|
||||
* @param string $scope The scope of the service
|
||||
*/
|
||||
public function set($id, $service)
|
||||
public function set($id, $service, $scope = self::SCOPE_CONTAINER)
|
||||
{
|
||||
$this->services[strtolower($id)] = $service;
|
||||
if (self::SCOPE_PROTOTYPE === $scope) {
|
||||
throw new \InvalidArgumentException('You cannot set services of scope "prototype".');
|
||||
}
|
||||
|
||||
$id = strtolower($id);
|
||||
|
||||
if (self::SCOPE_CONTAINER !== $scope) {
|
||||
if (!isset($this->scopedServices[$scope])) {
|
||||
throw new \RuntimeException('You cannot set services of inactive scopes.');
|
||||
}
|
||||
|
||||
$this->scopedServices[$scope][$id] = $service;
|
||||
}
|
||||
|
||||
$this->services[$id] = $service;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -227,6 +249,141 @@ class Container implements ContainerInterface
|
||||
return array_merge($ids, array_keys($this->services));
|
||||
}
|
||||
|
||||
/**
|
||||
* This is called when you enter a scope
|
||||
*
|
||||
* @param string $name
|
||||
* @return void
|
||||
*/
|
||||
public function enterScope($name)
|
||||
{
|
||||
if (!isset($this->scopes[$name])) {
|
||||
throw new \InvalidArgumentException(sprintf('The scope "%s" does not exist.', $name));
|
||||
}
|
||||
|
||||
if (self::SCOPE_CONTAINER !== $this->scopes[$name] && !isset($this->scopedServices[$this->scopes[$name]])) {
|
||||
throw new \RuntimeException(sprintf('The parent scope "%s" must be active when entering this scope.', $this->scopes[$name]));
|
||||
}
|
||||
|
||||
// check if a scope of this name is already active, if so we need to
|
||||
// remove all services of this scope, and those of any of its child
|
||||
// scopes from the global services map
|
||||
if (isset($this->scopedServices[$name])) {
|
||||
$services = array($this->services, $name => $this->scopedServices[$name]);
|
||||
unset($this->scopedServices[$name]);
|
||||
|
||||
foreach ($this->scopeChildren[$name] as $child) {
|
||||
$services[$child] = $this->scopedServices[$child];
|
||||
unset($this->scopedServices[$child]);
|
||||
}
|
||||
|
||||
// update global map
|
||||
$this->services = call_user_func_array('array_diff_key', $services);
|
||||
array_shift($services);
|
||||
|
||||
// add stack entry for this scope so we can restore the removed services later
|
||||
if (!isset($this->scopeStacks[$name])) {
|
||||
$this->scopeStacks[$name] = new \SplStack();
|
||||
}
|
||||
$this->scopeStacks[$name]->push($services);
|
||||
}
|
||||
|
||||
$this->scopedServices[$name] = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* This is called to leave the current scope, and move back to the parent
|
||||
* scope.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function leaveScope($name)
|
||||
{
|
||||
if (!isset($this->scopedServices[$name])) {
|
||||
throw new \InvalidArgumentException(sprintf('The scope "%s" is not active.', $name));
|
||||
}
|
||||
|
||||
// remove all services of this scope, or any of its child scopes from
|
||||
// the global service map
|
||||
$services = array($this->services, $this->scopedServices[$name]);
|
||||
unset($this->scopedServices[$name]);
|
||||
foreach ($this->scopeChildren[$name] as $child) {
|
||||
if (!isset($this->scopedServices[$child])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$services[] = $this->scopedServices[$child];
|
||||
unset($this->scopedServices[$child]);
|
||||
}
|
||||
$this->services = call_user_func_array('array_diff_key', $services);
|
||||
|
||||
// check if we need to restore services of a previous scope of this type
|
||||
if (isset($this->scopeStacks[$name]) && count($this->scopeStacks[$name]) > 0) {
|
||||
$services = $this->scopeStacks[$name]->pop();
|
||||
$this->scopedServices += $services;
|
||||
|
||||
array_unshift($services, $this->services);
|
||||
$this->services = call_user_func_array('array_merge', $services);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a scope to the container
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $parentScope
|
||||
* @return void
|
||||
*/
|
||||
public function addScope($name, $parentScope = self::SCOPE_CONTAINER)
|
||||
{
|
||||
if (self::SCOPE_CONTAINER === $name || self::SCOPE_PROTOTYPE === $name) {
|
||||
throw new \InvalidArgumentException(sprintf('The scope "%s" is reserved.', $name));
|
||||
}
|
||||
if (isset($this->scopes[$name])) {
|
||||
throw new \InvalidArgumentException(sprintf('A scope with name "%s" already exists.', $name));
|
||||
}
|
||||
if (self::SCOPE_CONTAINER !== $parentScope && !isset($this->scopes[$parentScope])) {
|
||||
throw new \InvalidArgumentException(sprintf('The parent scope "%s" does not exist, or is invalid.', $parentScope));
|
||||
}
|
||||
|
||||
$this->scopes[$name] = $parentScope;
|
||||
$this->scopeChildren[$name] = array();
|
||||
|
||||
// normalize the child relations
|
||||
if ($parentScope !== self::SCOPE_CONTAINER) {
|
||||
$this->scopeChildren[$parentScope][] = $name;
|
||||
|
||||
foreach ($this->scopeChildren as $pName => $childScopes) {
|
||||
if (in_array($parentScope, $childScopes, true)) {
|
||||
$this->scopeChildren[$pName][] = $name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this container has a certain scope
|
||||
*
|
||||
* @return Boolean
|
||||
*/
|
||||
public function hasScope($name)
|
||||
{
|
||||
return isset($this->scopes[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this scope is currently active
|
||||
*
|
||||
* This does not actually check if the passed scope actually exists.
|
||||
*
|
||||
* @param string $name
|
||||
* @return Boolean
|
||||
*/
|
||||
public function isScopeActive($name)
|
||||
{
|
||||
return isset($this->scopedServices[$name]);
|
||||
}
|
||||
|
||||
static public function camelize($id)
|
||||
{
|
||||
return preg_replace(array('/(?:^|_)+(.)/e', '/\.(.)/e'), array("strtoupper('\\1')", "'_'.strtoupper('\\1')"), $id);
|
||||
|
@ -178,6 +178,16 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
|
||||
return $this->compiler;
|
||||
}
|
||||
|
||||
public function getScopes()
|
||||
{
|
||||
return $this->scopes;
|
||||
}
|
||||
|
||||
public function getScopeChildren()
|
||||
{
|
||||
return $this->scopeChildren;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a service.
|
||||
*
|
||||
@ -186,7 +196,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
|
||||
*
|
||||
* @throws BadMethodCallException
|
||||
*/
|
||||
public function set($id, $service)
|
||||
public function set($id, $service, $scope = self::SCOPE_CONTAINER)
|
||||
{
|
||||
if ($this->isFrozen()) {
|
||||
throw new \BadMethodCallException('Setting service on a frozen container is not allowed');
|
||||
@ -196,7 +206,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
|
||||
|
||||
unset($this->definitions[$id], $this->aliases[$id]);
|
||||
|
||||
parent::set($id, $service);
|
||||
parent::set($id, $service, $scope);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -691,8 +701,16 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
|
||||
$injector->processDefinition($definition, $service);
|
||||
}
|
||||
|
||||
if ($definition->isShared()) {
|
||||
$this->services[strtolower($id)] = $service;
|
||||
if (self::SCOPE_PROTOTYPE !== $scope = $definition->getScope()) {
|
||||
if (self::SCOPE_CONTAINER !== $scope && !isset($this->scopedServices[$scope])) {
|
||||
throw new \RuntimeException('You tried to create a service of an inactive scope.');
|
||||
}
|
||||
|
||||
$this->services[$lowerId = strtolower($id)] = $service;
|
||||
|
||||
if (self::SCOPE_CONTAINER !== $scope) {
|
||||
$this->scopedServices[$scope][$lowerId] = $service;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($definition->getMethodCalls() as $call) {
|
||||
|
@ -15,20 +15,24 @@ namespace Symfony\Component\DependencyInjection;
|
||||
* ContainerInterface is the interface implemented by service container classes.
|
||||
*
|
||||
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
*/
|
||||
interface ContainerInterface
|
||||
{
|
||||
const EXCEPTION_ON_INVALID_REFERENCE = 1;
|
||||
const NULL_ON_INVALID_REFERENCE = 2;
|
||||
const IGNORE_ON_INVALID_REFERENCE = 3;
|
||||
const SCOPE_CONTAINER = 'container';
|
||||
const SCOPE_PROTOTYPE = 'prototype';
|
||||
|
||||
/**
|
||||
* Sets a service.
|
||||
*
|
||||
* @param string $id The service identifier
|
||||
* @param object $service The service instance
|
||||
* @param string $scope The scope of the service
|
||||
*/
|
||||
function set($id, $service);
|
||||
function set($id, $service, $scope = self::SCOPE_CONTAINER);
|
||||
|
||||
/**
|
||||
* Gets a service.
|
||||
@ -52,4 +56,46 @@ interface ContainerInterface
|
||||
* @return Boolean true if the service is defined, false otherwise
|
||||
*/
|
||||
function has($id);
|
||||
|
||||
/**
|
||||
* Enters the given scope
|
||||
*
|
||||
* @param string $name
|
||||
* @return void
|
||||
*/
|
||||
function enterScope($name);
|
||||
|
||||
/**
|
||||
* Leaves the current scope, and re-enters the parent scope
|
||||
*
|
||||
* @param string $name
|
||||
* @return void
|
||||
*/
|
||||
function leaveScope($name);
|
||||
|
||||
/**
|
||||
* Adds a scope to the container
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $parentScope
|
||||
* @return void
|
||||
*/
|
||||
function addScope($name, $parentScope = self::SCOPE_CONTAINER);
|
||||
|
||||
/**
|
||||
* Whether this container has the given scope
|
||||
*
|
||||
* @param string $name
|
||||
* @return Boolean
|
||||
*/
|
||||
function hasScope($name);
|
||||
|
||||
/**
|
||||
* Determines whether the given scope is currently active.
|
||||
*
|
||||
* It does however not check if the scope actually exists.
|
||||
*
|
||||
* @return Boolean
|
||||
*/
|
||||
function isScopeActive($name);
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ class Definition
|
||||
protected $file;
|
||||
protected $factoryMethod;
|
||||
protected $factoryService;
|
||||
protected $shared;
|
||||
protected $scope;
|
||||
protected $arguments;
|
||||
protected $calls;
|
||||
protected $configurator;
|
||||
@ -40,7 +40,7 @@ class Definition
|
||||
$this->class = $class;
|
||||
$this->arguments = $arguments;
|
||||
$this->calls = array();
|
||||
$this->shared = true;
|
||||
$this->scope = ContainerInterface::SCOPE_CONTAINER;
|
||||
$this->tags = array();
|
||||
$this->public = true;
|
||||
}
|
||||
@ -331,27 +331,27 @@ class Definition
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets if the service must be shared or not.
|
||||
* Sets the scope of the service
|
||||
*
|
||||
* @param Boolean $shared Whether the service must be shared or not
|
||||
* @param string $string Whether the service must be shared or not
|
||||
*
|
||||
* @return Definition The current instance
|
||||
*/
|
||||
public function setShared($shared)
|
||||
public function setScope($scope)
|
||||
{
|
||||
$this->shared = (Boolean) $shared;
|
||||
$this->scope = $scope;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the service must be shared.
|
||||
* Returns the scope of the service
|
||||
*
|
||||
* @return Boolean true if the service is shared, false otherwise
|
||||
* @return string
|
||||
*/
|
||||
public function isShared()
|
||||
public function getScope()
|
||||
{
|
||||
return $this->shared;
|
||||
return $this->scope;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -15,6 +15,7 @@ use Symfony\Component\DependencyInjection\Definition;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Component\DependencyInjection\Parameter;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* GraphvizDumper dumps a service container as a graphviz file.
|
||||
@ -135,7 +136,7 @@ class GraphvizDumper extends Dumper
|
||||
$container = clone $this->container;
|
||||
|
||||
foreach ($container->getDefinitions() as $id => $definition) {
|
||||
$nodes[$id] = array('class' => str_replace('\\', '\\\\', $this->container->getParameterBag()->resolveValue($definition->getClass())), 'attributes' => array_merge($this->options['node.definition'], array('style' => $definition->isShared() ? 'filled' : 'dotted')));
|
||||
$nodes[$id] = array('class' => str_replace('\\', '\\\\', $this->container->getParameterBag()->resolveValue($definition->getClass())), 'attributes' => array_merge($this->options['node.definition'], array('style' => ContainerInterface::SCOPE_PROTOTYPE !== $definition->getScope() ? 'filled' : 'dotted')));
|
||||
|
||||
$container->setDefinition($id, new Definition('stdClass'));
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ class PhpDumper extends Dumper
|
||||
protected $definitionVariables;
|
||||
protected $referenceVariables;
|
||||
protected $variableCount;
|
||||
protected $reservedVariables = array('instance');
|
||||
protected $reservedVariables = array('instance', 'class');
|
||||
|
||||
public function __construct(ContainerBuilder $container)
|
||||
{
|
||||
@ -270,8 +270,10 @@ EOF;
|
||||
$simple = $this->isSimpleInstance($id, $definition);
|
||||
|
||||
$instantiation = '';
|
||||
if ($definition->isShared()) {
|
||||
if (ContainerInterface::SCOPE_CONTAINER === $definition->getScope()) {
|
||||
$instantiation = "\$this->services['$id'] = ".($simple ? '' : '$instance');
|
||||
} else if (ContainerInterface::SCOPE_PROTOTYPE !== $scope = $definition->getScope()) {
|
||||
$instantiation = "\$this->services['$id'] = \$this->scopedServices['$scope']['$id'] = ".($simple ? '' : '$instance');
|
||||
} elseif (!$simple) {
|
||||
$instantiation = '$instance';
|
||||
}
|
||||
@ -399,7 +401,7 @@ EOF;
|
||||
}
|
||||
|
||||
$doc = '';
|
||||
if ($definition->isShared()) {
|
||||
if (ContainerInterface::SCOPE_PROTOTYPE !== $definition->getScope()) {
|
||||
$doc .= <<<EOF
|
||||
|
||||
*
|
||||
@ -430,6 +432,17 @@ EOF;
|
||||
|
||||
EOF;
|
||||
|
||||
$scope = $definition->getScope();
|
||||
if (ContainerInterface::SCOPE_CONTAINER !== $scope && ContainerInterface::SCOPE_PROTOTYPE !== $scope) {
|
||||
$code .= <<<EOF
|
||||
if (!isset(\$this->scopedServices['$scope'])) {
|
||||
throw new \RuntimeException('You cannot create a service ("$id") of an inactive scope ("$scope").');
|
||||
}
|
||||
|
||||
|
||||
EOF;
|
||||
}
|
||||
|
||||
$code .=
|
||||
$this->addServiceInclude($id, $definition).
|
||||
$this->addServiceLocalTempVariables($id, $definition).
|
||||
@ -518,7 +531,7 @@ EOF;
|
||||
{
|
||||
$bagClass = $this->container->isFrozen() ? 'FrozenParameterBag' : 'ParameterBag';
|
||||
|
||||
return <<<EOF
|
||||
$code = <<<EOF
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
@ -526,9 +539,21 @@ EOF;
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct(new $bagClass(\$this->getDefaultParameters()));
|
||||
|
||||
EOF;
|
||||
|
||||
if (count($scopes = $this->container->getScopes()) > 0) {
|
||||
$code .= "\n";
|
||||
$code .= " \$this->scopes = ".$this->dumpValue($scopes).";\n";
|
||||
$code .= " \$this->scopeChildren = ".$this->dumpValue($this->container->getScopeChildren()).";\n";
|
||||
}
|
||||
|
||||
$code .= <<<EOF
|
||||
}
|
||||
|
||||
EOF;
|
||||
|
||||
return $code;
|
||||
}
|
||||
|
||||
protected function addDefaultParametersMethod()
|
||||
|
@ -121,8 +121,8 @@ class XmlDumper extends Dumper
|
||||
if ($definition->getFactoryService()) {
|
||||
$service->setAttribute ('factory-service', $definition->getFactoryService());
|
||||
}
|
||||
if (!$definition->isShared()) {
|
||||
$service->setAttribute ('shared', 'false');
|
||||
if (ContainerInterface::SCOPE_CONTAINER !== $scope = $definition->getScope()) {
|
||||
$service->setAttribute ('scope', $scope);
|
||||
}
|
||||
|
||||
foreach ($definition->getTags() as $name => $tags) {
|
||||
|
@ -95,8 +95,8 @@ class YamlDumper extends Dumper
|
||||
$code .= sprintf(" calls:\n %s\n", str_replace("\n", "\n ", Yaml::dump($this->dumpValue($definition->getMethodCalls()), 1)));
|
||||
}
|
||||
|
||||
if (!$definition->isShared()) {
|
||||
$code .= " shared: false\n";
|
||||
if (ContainerInterface::SCOPE_CONTAINER !== $scope = $definition->getScope()) {
|
||||
$code .= sprintf(" scope: %s\n", $scope);
|
||||
}
|
||||
|
||||
if ($callable = $definition->getConfigurator()) {
|
||||
|
@ -11,6 +11,8 @@
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Loader;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Alias;
|
||||
|
||||
use Symfony\Component\DependencyInjection\InterfaceInjector;
|
||||
@ -137,7 +139,7 @@ class XmlFileLoader extends FileLoader
|
||||
|
||||
$definition = new Definition();
|
||||
|
||||
foreach (array('class', 'shared', 'public', 'factory-method', 'factory-service') as $key) {
|
||||
foreach (array('class', 'scope', 'public', 'factory-method', 'factory-service') as $key) {
|
||||
if (isset($service[$key])) {
|
||||
$method = 'set'.str_replace('-', '', $key);
|
||||
$definition->$method((string) $service->getAttributeAsPhp($key));
|
||||
@ -155,7 +157,7 @@ class XmlFileLoader extends FileLoader
|
||||
$definition->setConfigurator((string) $service->configurator['function']);
|
||||
} else {
|
||||
if (isset($service->configurator['service'])) {
|
||||
$class = new Reference((string) $service->configurator['service']);
|
||||
$class = new Reference((string) $service->configurator['service'], ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, false);
|
||||
} else {
|
||||
$class = (string) $service->configurator['class'];
|
||||
}
|
||||
|
@ -144,8 +144,8 @@ class YamlFileLoader extends FileLoader
|
||||
$definition->setClass($service['class']);
|
||||
}
|
||||
|
||||
if (isset($service['shared'])) {
|
||||
$definition->setShared($service['shared']);
|
||||
if (isset($service['scope'])) {
|
||||
$definition->setScope($service['scope']);
|
||||
}
|
||||
|
||||
if (isset($service['public'])) {
|
||||
@ -237,10 +237,23 @@ class YamlFileLoader extends FileLoader
|
||||
{
|
||||
if (is_array($value)) {
|
||||
$value = array_map(array($this, 'resolveServices'), $value);
|
||||
} else if (is_string($value) && 0 === strpos($value, '@?')) {
|
||||
$value = new Reference(substr($value, 2), ContainerInterface::IGNORE_ON_INVALID_REFERENCE);
|
||||
} else if (is_string($value) && 0 === strpos($value, '@')) {
|
||||
$value = new Reference(substr($value, 1));
|
||||
} else if (is_string($value) && 0 === strpos($value, '@')) {
|
||||
if (0 === strpos($value, '@?')) {
|
||||
$value = substr($value, 2);
|
||||
$invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE;
|
||||
} else {
|
||||
$value = substr($value, 1);
|
||||
$invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
|
||||
}
|
||||
|
||||
if ('=' === substr($value, -1)) {
|
||||
$value = substr($value, 0, -1);
|
||||
$strict = false;
|
||||
} else {
|
||||
$strict = true;
|
||||
}
|
||||
|
||||
$value = new Reference($value, $invalidBehavior, $strict);
|
||||
}
|
||||
|
||||
return $value;
|
||||
|
@ -101,7 +101,7 @@
|
||||
</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="scope" type="xsd:string" />
|
||||
<xsd:attribute name="public" type="boolean" />
|
||||
<xsd:attribute name="factory-method" type="xsd:string" />
|
||||
<xsd:attribute name="factory-service" type="xsd:string" />
|
||||
@ -140,6 +140,7 @@
|
||||
<xsd:attribute name="id" type="xsd:string" />
|
||||
<xsd:attribute name="key" type="xsd:string" />
|
||||
<xsd:attribute name="on-invalid" type="xsd:string" />
|
||||
<xsd:attribute name="strict" type="boolean" />
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="call" mixed="true">
|
||||
|
@ -20,19 +20,22 @@ class Reference
|
||||
{
|
||||
protected $id;
|
||||
protected $invalidBehavior;
|
||||
protected $strict;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $id The service identifier
|
||||
* @param int $invalidBehavior The behavior when the service does not exist
|
||||
* @param string $id The service identifier
|
||||
* @param int $invalidBehavior The behavior when the service does not exist
|
||||
* @param Boolean $strict Sets how this reference is validated
|
||||
*
|
||||
* @see Container
|
||||
*/
|
||||
public function __construct($id, $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE)
|
||||
public function __construct($id, $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, $strict = true)
|
||||
{
|
||||
$this->id = $id;
|
||||
$this->invalidBehavior = $invalidBehavior;
|
||||
$this->strict = $strict;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -49,4 +52,9 @@ class Reference
|
||||
{
|
||||
return $this->invalidBehavior;
|
||||
}
|
||||
|
||||
public function isStrict()
|
||||
{
|
||||
return $this->strict;
|
||||
}
|
||||
}
|
||||
|
@ -42,7 +42,14 @@ class SimpleXMLElement extends \SimpleXMLElement
|
||||
} elseif (isset($arg['on-invalid']) && 'null' == $arg['on-invalid']) {
|
||||
$invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE;
|
||||
}
|
||||
$arguments[$key] = new Reference((string) $arg['id'], $invalidBehavior);
|
||||
|
||||
if (isset($arg['strict'])) {
|
||||
$strict = self::phpize($arg['strict']);
|
||||
} else {
|
||||
$strict = true;
|
||||
}
|
||||
|
||||
$arguments[$key] = new Reference((string) $arg['id'], $invalidBehavior, $strict);
|
||||
break;
|
||||
case 'collection':
|
||||
$arguments[$key] = $arg->getArgumentsAsPhp($name);
|
||||
|
@ -11,6 +11,19 @@
|
||||
|
||||
namespace Symfony\Component\DependencyInjection;
|
||||
|
||||
/**
|
||||
* Represents a variable.
|
||||
*
|
||||
* $var = new Variable('a');
|
||||
*
|
||||
* will be dumped as
|
||||
*
|
||||
* $a
|
||||
*
|
||||
* by the PHP dumper.
|
||||
*
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
*/
|
||||
class Variable
|
||||
{
|
||||
protected $name;
|
||||
|
@ -27,7 +27,6 @@ class HttpKernel implements HttpKernelInterface
|
||||
{
|
||||
protected $dispatcher;
|
||||
protected $resolver;
|
||||
protected $request;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
@ -46,16 +45,10 @@ class HttpKernel implements HttpKernelInterface
|
||||
*/
|
||||
public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true)
|
||||
{
|
||||
// set the current request, stash the previous one
|
||||
$previousRequest = $this->request;
|
||||
$this->request = $request;
|
||||
|
||||
try {
|
||||
$response = $this->handleRaw($request, $type);
|
||||
} catch (\Exception $e) {
|
||||
if (false === $catch) {
|
||||
$this->request = $previousRequest;
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
@ -63,28 +56,15 @@ class HttpKernel implements HttpKernelInterface
|
||||
$event = new Event($this, 'core.exception', array('request_type' => $type, 'request' => $request, 'exception' => $e));
|
||||
$this->dispatcher->notifyUntil($event);
|
||||
if (!$event->isProcessed()) {
|
||||
$this->request = $previousRequest;
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$response = $this->filterResponse($event->getReturnValue(), $request, 'A "core.exception" listener returned a non response object.', $type);
|
||||
}
|
||||
|
||||
// restore the previous request
|
||||
$this->request = $previousRequest;
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRequest()
|
||||
{
|
||||
return $this->request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a request to convert it to a response.
|
||||
*
|
||||
|
@ -39,11 +39,4 @@ interface HttpKernelInterface
|
||||
* @throws \Exception When an Exception occurs during processing
|
||||
*/
|
||||
function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true);
|
||||
|
||||
/**
|
||||
* Returns the current request.
|
||||
*
|
||||
* @return Request|null The request currently being handled
|
||||
*/
|
||||
function getRequest();
|
||||
}
|
||||
|
@ -288,7 +288,11 @@ class Container implements ContainerInterface
|
||||
public function __construct(ParameterBagInterface $parameterBag = null)
|
||||
{
|
||||
$this->parameterBag = null === $parameterBag ? new ParameterBag() : $parameterBag;
|
||||
$this->services = array();
|
||||
$this->services =
|
||||
$this->scopes =
|
||||
$this->scopeChildren =
|
||||
$this->scopedServices =
|
||||
$this->scopeStacks = array();
|
||||
$this->set('service_container', $this);
|
||||
}
|
||||
public function compile()
|
||||
@ -316,9 +320,19 @@ class Container implements ContainerInterface
|
||||
{
|
||||
$this->parameterBag->set($name, $value);
|
||||
}
|
||||
public function set($id, $service)
|
||||
public function set($id, $service, $scope = self::SCOPE_CONTAINER)
|
||||
{
|
||||
$this->services[strtolower($id)] = $service;
|
||||
if (self::SCOPE_PROTOTYPE === $scope) {
|
||||
throw new \InvalidArgumentException('You cannot set services of scope "prototype".');
|
||||
}
|
||||
$id = strtolower($id);
|
||||
if (self::SCOPE_CONTAINER !== $scope) {
|
||||
if (!isset($this->scopedServices[$scope])) {
|
||||
throw new \RuntimeException('You cannot set services of inactive scopes.');
|
||||
}
|
||||
$this->scopedServices[$scope][$id] = $service;
|
||||
}
|
||||
$this->services[$id] = $service;
|
||||
}
|
||||
public function has($id)
|
||||
{
|
||||
@ -355,6 +369,82 @@ class Container implements ContainerInterface
|
||||
}
|
||||
return array_merge($ids, array_keys($this->services));
|
||||
}
|
||||
public function enterScope($name)
|
||||
{
|
||||
if (!isset($this->scopes[$name])) {
|
||||
throw new \InvalidArgumentException(sprintf('The scope "%s" does not exist.', $name));
|
||||
}
|
||||
if (self::SCOPE_CONTAINER !== $this->scopes[$name] && !isset($this->scopedServices[$this->scopes[$name]])) {
|
||||
throw new \RuntimeException(sprintf('The parent scope "%s" must be active when entering this scope.', $this->scopes[$name]));
|
||||
}
|
||||
if (isset($this->scopedServices[$name])) {
|
||||
$services = array($this->services, $name => $this->scopedServices[$name]);
|
||||
unset($this->scopedServices[$name]);
|
||||
foreach ($this->scopeChildren[$name] as $child) {
|
||||
$services[$child] = $this->scopedServices[$child];
|
||||
unset($this->scopedServices[$child]);
|
||||
}
|
||||
$this->services = call_user_func_array('array_diff_key', $services);
|
||||
array_shift($services);
|
||||
if (!isset($this->scopeStacks[$name])) {
|
||||
$this->scopeStacks[$name] = new \SplStack();
|
||||
}
|
||||
$this->scopeStacks[$name]->push($services);
|
||||
}
|
||||
$this->scopedServices[$name] = array();
|
||||
}
|
||||
public function leaveScope($name)
|
||||
{
|
||||
if (!isset($this->scopedServices[$name])) {
|
||||
throw new \InvalidArgumentException(sprintf('The scope "%s" is not active.', $name));
|
||||
}
|
||||
$services = array($this->services, $this->scopedServices[$name]);
|
||||
unset($this->scopedServices[$name]);
|
||||
foreach ($this->scopeChildren[$name] as $child) {
|
||||
if (!isset($this->scopedServices[$child])) {
|
||||
continue;
|
||||
}
|
||||
$services[] = $this->scopedServices[$child];
|
||||
unset($this->scopedServices[$child]);
|
||||
}
|
||||
$this->services = call_user_func_array('array_diff_key', $services);
|
||||
if (isset($this->scopeStacks[$name]) && count($this->scopeStacks[$name]) > 0) {
|
||||
$services = $this->scopeStacks[$name]->pop();
|
||||
$this->scopedServices += $services;
|
||||
array_unshift($services, $this->services);
|
||||
$this->services = call_user_func_array('array_merge', $services);
|
||||
}
|
||||
}
|
||||
public function addScope($name, $parentScope = self::SCOPE_CONTAINER)
|
||||
{
|
||||
if (self::SCOPE_CONTAINER === $name || self::SCOPE_PROTOTYPE === $name) {
|
||||
throw new \InvalidArgumentException(sprintf('The scope "%s" is reserved.', $name));
|
||||
}
|
||||
if (isset($this->scopes[$name])) {
|
||||
throw new \InvalidArgumentException(sprintf('A scope with name "%s" already exists.', $name));
|
||||
}
|
||||
if (self::SCOPE_CONTAINER !== $parentScope && !isset($this->scopes[$parentScope])) {
|
||||
throw new \InvalidArgumentException(sprintf('The parent scope "%s" does not exist, or is invalid.', $parentScope));
|
||||
}
|
||||
$this->scopes[$name] = $parentScope;
|
||||
$this->scopeChildren[$name] = array();
|
||||
if ($parentScope !== self::SCOPE_CONTAINER) {
|
||||
$this->scopeChildren[$parentScope][] = $name;
|
||||
foreach ($this->scopeChildren as $pName => $childScopes) {
|
||||
if (in_array($parentScope, $childScopes, true)) {
|
||||
$this->scopeChildren[$pName][] = $name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public function hasScope($name)
|
||||
{
|
||||
return isset($this->scopes[$name]);
|
||||
}
|
||||
public function isScopeActive($name)
|
||||
{
|
||||
return isset($this->scopedServices[$name]);
|
||||
}
|
||||
static public function camelize($id)
|
||||
{
|
||||
return preg_replace(array('/(?:^|_)+(.)/e', '/\.(.)/e'), array("strtoupper('\\1')", "'_'.strtoupper('\\1')"), $id);
|
||||
@ -379,9 +469,16 @@ interface ContainerInterface
|
||||
const EXCEPTION_ON_INVALID_REFERENCE = 1;
|
||||
const NULL_ON_INVALID_REFERENCE = 2;
|
||||
const IGNORE_ON_INVALID_REFERENCE = 3;
|
||||
function set($id, $service);
|
||||
const SCOPE_CONTAINER = 'container';
|
||||
const SCOPE_PROTOTYPE = 'prototype';
|
||||
function set($id, $service, $scope = self::SCOPE_CONTAINER);
|
||||
function get($id, $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE);
|
||||
function has($id);
|
||||
function enterScope($name);
|
||||
function leaveScope($name);
|
||||
function addScope($name, $parentScope = self::SCOPE_CONTAINER);
|
||||
function hasScope($name);
|
||||
function isScopeActive($name);
|
||||
}
|
||||
}
|
||||
namespace Symfony\Component\DependencyInjection\ParameterBag
|
||||
|
@ -87,9 +87,8 @@ class AnalyzeServiceReferencesPassTest extends \PHPUnit_Framework_TestCase
|
||||
protected function process(ContainerBuilder $container)
|
||||
{
|
||||
$pass = new RepeatedPass(array(new AnalyzeServiceReferencesPass()));
|
||||
$pass->setCompiler($compiler = new Compiler());
|
||||
$pass->process($container);
|
||||
|
||||
return $compiler->getServiceReferenceGraph();
|
||||
return $container->getCompiler()->getServiceReferenceGraph();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Tests\Component\DependencyInjection\Compiler;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Compiler\CheckCircularReferencesPass;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Compiler\AnalyzeServiceReferencesPass;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Compiler\Compiler;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
|
||||
class CheckCircularReferencesPassTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
* @expectedException \RuntimeException
|
||||
*/
|
||||
public function testProcess()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
$container->register('a')->addArgument(new Reference('b'));
|
||||
$container->register('b')->addArgument(new Reference('a'));
|
||||
|
||||
$this->process($container);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \RuntimeException
|
||||
*/
|
||||
public function testProcessDetectsIndirectCircularReference()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
$container->register('a')->addArgument(new Reference('b'));
|
||||
$container->register('b')->addArgument(new Reference('c'));
|
||||
$container->register('c')->addArgument(new Reference('a'));
|
||||
|
||||
$this->process($container);
|
||||
}
|
||||
|
||||
public function testProcessIgnoresMethodCalls()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
$container->register('a')->addArgument(new Reference('b'));
|
||||
$container->register('b')->addMethodCall('setA', array(new Reference('a')));
|
||||
|
||||
$this->process($container);
|
||||
}
|
||||
|
||||
protected function process(ContainerBuilder $container)
|
||||
{
|
||||
$compiler = new Compiler();
|
||||
$passConfig = $compiler->getPassConfig();
|
||||
$passConfig->setOptimizationPasses(array(
|
||||
new AnalyzeServiceReferencesPass(true),
|
||||
new CheckCircularReferencesPass(),
|
||||
));
|
||||
$passConfig->setRemovingPasses(array());
|
||||
|
||||
$compiler->compile($container);
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Tests\Component\DependencyInjection\Compiler;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Compiler\CheckReferenceScopePass;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
|
||||
class CheckReferenceScopePassTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testProcessIgnoresScopeWideningIfNonStrictReference()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
$container->register('a')->addArgument(new Reference('b', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, false));
|
||||
$container->register('b')->setScope('prototype');
|
||||
|
||||
$this->process($container);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \RuntimeException
|
||||
*/
|
||||
public function testProcessDetectsScopeWidening()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
$container->register('a')->addArgument(new Reference('b'));
|
||||
$container->register('b')->setScope('prototype');
|
||||
|
||||
$this->process($container);
|
||||
}
|
||||
|
||||
public function testProcessIgnoresCrossScopeHierarchyReferenceIfNotStrict()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
$container->addScope('a');
|
||||
$container->addScope('b');
|
||||
|
||||
$container->register('a')->setScope('a')->addArgument(new Reference('b', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, false));
|
||||
$container->register('b')->setScope('b');
|
||||
|
||||
$this->process($container);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \RuntimeException
|
||||
*/
|
||||
public function testProcessDetectsCrossScopeHierarchyReference()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
$container->addScope('a');
|
||||
$container->addScope('b');
|
||||
|
||||
$container->register('a')->setScope('a')->addArgument(new Reference('b'));
|
||||
$container->register('b')->setScope('b');
|
||||
|
||||
$this->process($container);
|
||||
}
|
||||
|
||||
public function testProcess()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
$container->register('a')->addArgument(new Reference('b'));
|
||||
$container->register('b');
|
||||
|
||||
$this->process($container);
|
||||
}
|
||||
|
||||
protected function process(ContainerBuilder $container)
|
||||
{
|
||||
$pass = new CheckReferenceScopePass();
|
||||
$pass->process($container);
|
||||
}
|
||||
}
|
@ -41,7 +41,7 @@ class InlineServiceDefinitionsPassTest extends \PHPUnit_Framework_TestCase
|
||||
$this->assertSame($container->getDefinition('inlinable.service'), $arguments[0]);
|
||||
}
|
||||
|
||||
public function testProcessDoesNotInlinesWhenAliasedServiceIsNotShared()
|
||||
public function testProcessDoesNotInlineWhenAliasedServiceIsNotOfPrototypeScope()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
$container
|
||||
@ -61,17 +61,17 @@ class InlineServiceDefinitionsPassTest extends \PHPUnit_Framework_TestCase
|
||||
$this->assertSame($ref, $arguments[0]);
|
||||
}
|
||||
|
||||
public function testProcessDoesInlineNonSharedService()
|
||||
public function testProcessDoesInlineServiceOfPrototypeScope()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
$container
|
||||
->register('foo')
|
||||
->setShared(false)
|
||||
->setScope('prototype')
|
||||
;
|
||||
$container
|
||||
->register('bar')
|
||||
->setPublic(false)
|
||||
->setShared(false)
|
||||
->setScope('prototype')
|
||||
;
|
||||
$container->setAlias('moo', 'bar');
|
||||
|
||||
@ -113,7 +113,6 @@ class InlineServiceDefinitionsPassTest extends \PHPUnit_Framework_TestCase
|
||||
protected function process(ContainerBuilder $container)
|
||||
{
|
||||
$repeatedPass = new RepeatedPass(array(new AnalyzeServiceReferencesPass(), new InlineServiceDefinitionsPass()));
|
||||
$repeatedPass->setCompiler(new Compiler());
|
||||
$repeatedPass->process($container);
|
||||
}
|
||||
}
|
@ -84,7 +84,6 @@ class RemoveUnusedDefinitionsPassTest extends \PHPUnit_Framework_TestCase
|
||||
protected function process(ContainerBuilder $container)
|
||||
{
|
||||
$repeatedPass = new RepeatedPass(array(new AnalyzeServiceReferencesPass(), new RemoveUnusedDefinitionsPass()));
|
||||
$repeatedPass->setCompiler(new Compiler());
|
||||
$repeatedPass->process($container);
|
||||
}
|
||||
}
|
@ -112,7 +112,7 @@ class ContainerBuilderTest extends \PHPUnit_Framework_TestCase
|
||||
$this->assertEquals('Circular reference detected for service "baz" (services currently loading: baz).', $e->getMessage(), '->get() throws a LogicException if the service has a circular reference to itself');
|
||||
}
|
||||
|
||||
$builder->register('foobar', 'stdClass')->setShared(true);
|
||||
$builder->register('foobar', 'stdClass')->setScope('container');
|
||||
$this->assertTrue($builder->get('bar') === $builder->get('bar'), '->get() always returns the same instance if the service is shared');
|
||||
}
|
||||
|
||||
|
@ -97,7 +97,7 @@ class ContainerTest extends \PHPUnit_Framework_TestCase
|
||||
$this->assertEquals(array('service_container', 'foo', 'bar'), $sc->getServiceIds(), '->getServiceIds() returns all defined service ids');
|
||||
|
||||
$sc = new ProjectServiceContainer();
|
||||
$this->assertEquals(array('bar', 'foo_bar', 'foo.baz', 'service_container'), $sc->getServiceIds(), '->getServiceIds() returns defined service ids by getXXXService() methods');
|
||||
$this->assertEquals(array('scoped', 'scoped_foo', 'bar', 'foo_bar', 'foo.baz', 'service_container'), $sc->getServiceIds(), '->getServiceIds() returns defined service ids by getXXXService() methods');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -110,6 +110,37 @@ class ContainerTest extends \PHPUnit_Framework_TestCase
|
||||
$this->assertEquals($foo, $sc->get('foo'), '->set() sets a service');
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \InvalidArgumentException
|
||||
*/
|
||||
public function testSetDoesNotAllowPrototypeScope()
|
||||
{
|
||||
$c = new Container();
|
||||
$c->set('foo', new \stdClass(), 'prototype');
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \RuntimeException
|
||||
*/
|
||||
public function testSetDoesNotAllowInactiveScope()
|
||||
{
|
||||
$c = new Container();
|
||||
$c->addScope('foo');
|
||||
$c->set('foo', new \stdClass(), 'foo');
|
||||
}
|
||||
|
||||
public function testSetAlsoSetsScopedService()
|
||||
{
|
||||
$c = new Container();
|
||||
$c->addScope('foo');
|
||||
$c->enterScope('foo');
|
||||
$c->set('foo', $foo = new \stdClass(), 'foo');
|
||||
|
||||
$services = $this->getField($c, 'scopedServices');
|
||||
$this->assertTrue(isset($services['foo']['foo']));
|
||||
$this->assertSame($foo, $services['foo']['foo']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Symfony\Component\DependencyInjection\Container::get
|
||||
*/
|
||||
@ -148,6 +179,151 @@ class ContainerTest extends \PHPUnit_Framework_TestCase
|
||||
$this->assertTrue($sc->has('foo_bar'), '->has() returns true if a get*Method() is defined');
|
||||
$this->assertTrue($sc->has('foo.baz'), '->has() returns true if a get*Method() is defined');
|
||||
}
|
||||
|
||||
public function testEnterLeaveCurrentScope()
|
||||
{
|
||||
$container = new ProjectServiceContainer();
|
||||
$container->addScope('foo');
|
||||
|
||||
$container->enterScope('foo');
|
||||
$scoped1 = $container->get('scoped');
|
||||
$scopedFoo1 = $container->get('scoped_foo');
|
||||
|
||||
$container->enterScope('foo');
|
||||
$scoped2 = $container->get('scoped');
|
||||
$scoped3 = $container->get('scoped');
|
||||
$scopedFoo2 = $container->get('scoped_foo');
|
||||
|
||||
$container->leaveScope('foo');
|
||||
$scoped4 = $container->get('scoped');
|
||||
$scopedFoo3 = $container->get('scoped_foo');
|
||||
|
||||
$this->assertNotSame($scoped1, $scoped2);
|
||||
$this->assertSame($scoped2, $scoped3);
|
||||
$this->assertSame($scoped1, $scoped4);
|
||||
$this->assertNotSame($scopedFoo1, $scopedFoo2);
|
||||
$this->assertSame($scopedFoo1, $scopedFoo3);
|
||||
}
|
||||
|
||||
public function testEnterLeaveScopeWithChildScopes()
|
||||
{
|
||||
$container = new Container();
|
||||
$container->addScope('foo');
|
||||
$container->addScope('bar', 'foo');
|
||||
|
||||
$this->assertFalse($container->isScopeActive('foo'));
|
||||
|
||||
$container->enterScope('foo');
|
||||
$container->enterScope('bar');
|
||||
|
||||
$this->assertTrue($container->isScopeActive('foo'));
|
||||
$this->assertFalse($container->has('a'));
|
||||
|
||||
$a = new \stdClass();
|
||||
$container->set('a', $a, 'bar');
|
||||
|
||||
$services = $this->getField($container, 'scopedServices');
|
||||
$this->assertTrue(isset($services['bar']['a']));
|
||||
$this->assertSame($a, $services['bar']['a']);
|
||||
|
||||
$this->assertTrue($container->has('a'));
|
||||
$container->leaveScope('foo');
|
||||
|
||||
$services = $this->getField($container, 'scopedServices');
|
||||
$this->assertFalse(isset($services['bar']));
|
||||
|
||||
$this->assertFalse($container->isScopeActive('foo'));
|
||||
$this->assertFalse($container->has('a'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \InvalidArgumentException
|
||||
* @dataProvider getBuiltInScopes
|
||||
*/
|
||||
public function testAddScopeDoesNotAllowBuiltInScopes($scope)
|
||||
{
|
||||
$container = new Container();
|
||||
$container->addScope($scope);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \InvalidArgumentException
|
||||
*/
|
||||
public function testAddScopeDoesNotAllowExistingScope()
|
||||
{
|
||||
$container = new Container();
|
||||
$container->addScope('foo');
|
||||
$container->addScope('foo');
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \InvalidArgumentException
|
||||
* @dataProvider getInvalidParentScopes
|
||||
*/
|
||||
public function testAddScopeDoesNotAllowInvalidParentScope($scope)
|
||||
{
|
||||
$c = new Container();
|
||||
$c->addScope('foo', $scope);
|
||||
}
|
||||
|
||||
public function testAddScope()
|
||||
{
|
||||
$c = new Container();
|
||||
$c->addScope('foo');
|
||||
$c->addScope('bar', 'foo');
|
||||
|
||||
$this->assertSame(array('foo' => 'container', 'bar' => 'foo'), $this->getField($c, 'scopes'));
|
||||
$this->assertSame(array('foo' => array('bar'), 'bar' => array()), $this->getField($c, 'scopeChildren'));
|
||||
}
|
||||
|
||||
public function testHasScope()
|
||||
{
|
||||
$c = new Container();
|
||||
|
||||
$this->assertFalse($c->hasScope('foo'));
|
||||
$c->addScope('foo');
|
||||
$this->assertTrue($c->hasScope('foo'));
|
||||
}
|
||||
|
||||
public function testIsScopeActive()
|
||||
{
|
||||
$c = new Container();
|
||||
|
||||
$this->assertFalse($c->isScopeActive('foo'));
|
||||
$c->addScope('foo');
|
||||
|
||||
$this->assertFalse($c->isScopeActive('foo'));
|
||||
$c->enterScope('foo');
|
||||
|
||||
$this->assertTrue($c->isScopeActive('foo'));
|
||||
$c->leaveScope('foo');
|
||||
|
||||
$this->assertFalse($c->isScopeActive('foo'));
|
||||
}
|
||||
|
||||
public function getInvalidParentScopes()
|
||||
{
|
||||
return array(
|
||||
array(ContainerInterface::SCOPE_PROTOTYPE),
|
||||
array('bar'),
|
||||
);
|
||||
}
|
||||
|
||||
public function getBuiltInScopes()
|
||||
{
|
||||
return array(
|
||||
array(ContainerInterface::SCOPE_CONTAINER),
|
||||
array(ContainerInterface::SCOPE_PROTOTYPE),
|
||||
);
|
||||
}
|
||||
|
||||
protected function getField($obj, $field)
|
||||
{
|
||||
$reflection = new \ReflectionProperty($obj, $field);
|
||||
$reflection->setAccessible(true);
|
||||
|
||||
return $reflection->getValue($obj);
|
||||
}
|
||||
}
|
||||
|
||||
class ProjectServiceContainer extends Container
|
||||
@ -163,6 +339,24 @@ class ProjectServiceContainer extends Container
|
||||
$this->__foo_baz = new \stdClass();
|
||||
}
|
||||
|
||||
protected function getScopedService()
|
||||
{
|
||||
if (!isset($this->scopedServices['foo'])) {
|
||||
throw new \RuntimeException('Invalid call');
|
||||
}
|
||||
|
||||
return $this->services['scoped'] = $this->scopedServices['foo']['scoped'] = new \stdClass();
|
||||
}
|
||||
|
||||
protected function getScopedFooService()
|
||||
{
|
||||
if (!isset($this->scopedServices['foo'])) {
|
||||
throw new \RuntimeException('invalid call');
|
||||
}
|
||||
|
||||
return $this->services['scoped_foo'] = $this->scopedServices['foo']['scoped_foo'] = new \stdClass();
|
||||
}
|
||||
|
||||
protected function getBarService()
|
||||
{
|
||||
return $this->__bar;
|
||||
|
@ -102,15 +102,15 @@ class DefinitionTest extends \PHPUnit_Framework_TestCase
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Symfony\Component\DependencyInjection\Definition::setShared
|
||||
* @covers Symfony\Component\DependencyInjection\Definition::isShared
|
||||
* @covers Symfony\Component\DependencyInjection\Definition::setScope
|
||||
* @covers Symfony\Component\DependencyInjection\Definition::getScope
|
||||
*/
|
||||
public function testSetIsShared()
|
||||
public function testSetGetScope()
|
||||
{
|
||||
$def = new Definition('stdClass');
|
||||
$this->assertTrue($def->isShared(), '->isShared() returns true by default');
|
||||
$this->assertSame($def, $def->setShared(false), '->setShared() implements a fluent interface');
|
||||
$this->assertFalse($def->isShared(), '->isShared() returns false if the instance must not be shared');
|
||||
$this->assertEquals('container', $def->getScope());
|
||||
$this->assertSame($def, $def->setScope('foo'));
|
||||
$this->assertEquals('foo', $def->getScope());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -14,7 +14,7 @@ $container->
|
||||
addTag('foo', array('bar' => 'bar'))->
|
||||
setFactoryMethod('getInstance')->
|
||||
setArguments(array('foo', new Reference('foo.baz'), array('%foo%' => 'foo is %foo%', 'bar' => '%foo%'), true, new Reference('service_container')))->
|
||||
setShared(false)->
|
||||
setScope('prototype')->
|
||||
addMethodCall('setBar', array(new Reference('bar')))->
|
||||
addMethodCall('initialize')->
|
||||
setConfigurator('sc_configure')
|
||||
@ -22,7 +22,7 @@ $container->
|
||||
$container->
|
||||
register('bar', 'FooClass')->
|
||||
setArguments(array('foo', new Reference('foo.baz'), new Parameter('foo_bar')))->
|
||||
setShared(true)->
|
||||
setScope('container')->
|
||||
setConfigurator(array(new Reference('foo.baz'), 'configure'))
|
||||
;
|
||||
$container->
|
||||
|
@ -6,8 +6,9 @@
|
||||
<services>
|
||||
<service id="foo" class="FooClass" />
|
||||
<service id="baz" class="BazClass" />
|
||||
<service id="shared" class="FooClass" shared="true" />
|
||||
<service id="non_shared" class="FooClass" shared="false" />
|
||||
<service id="scope.container" class="FooClass" scope="container" />
|
||||
<service id="scope.custom" class="FooClass" scope="custom" />
|
||||
<service id="scope.prototype" class="FooClass" scope="prototype" />
|
||||
<service id="constructor" class="FooClass" factory-method="getInstance" />
|
||||
<service id="file" class="FooClass">
|
||||
<file>%path%/foo.php</file>
|
||||
|
@ -6,7 +6,7 @@
|
||||
<parameter key="foo">bar</parameter>
|
||||
</parameters>
|
||||
<services>
|
||||
<service id="foo" class="FooClass" factory-method="getInstance" shared="false">
|
||||
<service id="foo" class="FooClass" factory-method="getInstance" scope="prototype">
|
||||
<tag name="foo" foo="foo"/>
|
||||
<tag name="foo" bar="bar"/>
|
||||
<argument>foo</argument>
|
||||
|
@ -1,8 +1,9 @@
|
||||
services:
|
||||
foo: { class: FooClass }
|
||||
baz: { class: BazClass }
|
||||
shared: { class: FooClass, shared: true }
|
||||
non_shared: { class: FooClass, shared: false }
|
||||
scope.container: { class: FooClass, scope: container }
|
||||
scope.custom: { class: FooClass, scope: custom }
|
||||
scope.prototype: { class: FooClass, scope: prototype }
|
||||
constructor: { class: FooClass, factory_method: getInstance }
|
||||
file: { class: FooClass, file: %path%/foo.php }
|
||||
arguments: { class: FooClass, arguments: [foo, @foo, [true, false]] }
|
||||
|
@ -15,7 +15,7 @@ services:
|
||||
- [setBar, ['@bar']]
|
||||
- [initialize, { }]
|
||||
|
||||
shared: false
|
||||
scope: prototype
|
||||
configurator: sc_configure
|
||||
bar:
|
||||
class: FooClass
|
||||
|
@ -11,6 +11,8 @@
|
||||
|
||||
namespace Symfony\Tests\Component\DependencyInjection\Loader;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Component\DependencyInjection\Definition;
|
||||
@ -132,13 +134,14 @@ class XmlFileLoaderTest extends \PHPUnit_Framework_TestCase
|
||||
$this->assertTrue(isset($services['foo']), '->load() parses <service> elements');
|
||||
$this->assertEquals('Symfony\\Component\\DependencyInjection\\Definition', get_class($services['foo']), '->load() converts <service> element to Definition instances');
|
||||
$this->assertEquals('FooClass', $services['foo']->getClass(), '->load() parses the class attribute');
|
||||
$this->assertTrue($services['shared']->isShared(), '->load() parses the shared attribute');
|
||||
$this->assertFalse($services['non_shared']->isShared(), '->load() parses the shared attribute');
|
||||
$this->assertEquals('container', $services['scope.container']->getScope());
|
||||
$this->assertEquals('custom', $services['scope.custom']->getScope());
|
||||
$this->assertEquals('prototype', $services['scope.prototype']->getScope());
|
||||
$this->assertEquals('getInstance', $services['constructor']->getFactoryMethod(), '->load() parses the factory-method attribute');
|
||||
$this->assertEquals('%path%/foo.php', $services['file']->getFile(), '->load() parses the file tag');
|
||||
$this->assertEquals(array('foo', new Reference('foo'), array(true, false)), $services['arguments']->getArguments(), '->load() parses the argument tags');
|
||||
$this->assertEquals('sc_configure', $services['configurator1']->getConfigurator(), '->load() parses the configurator tag');
|
||||
$this->assertEquals(array(new Reference('baz'), 'configure'), $services['configurator2']->getConfigurator(), '->load() parses the configurator tag');
|
||||
$this->assertEquals(array(new Reference('baz', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, false), 'configure'), $services['configurator2']->getConfigurator(), '->load() parses the configurator tag');
|
||||
$this->assertEquals(array('BazClass', 'configureStatic'), $services['configurator3']->getConfigurator(), '->load() parses the configurator tag');
|
||||
$this->assertEquals(array(array('setBar', array())), $services['method_call1']->getMethodCalls(), '->load() parses the method_call tag');
|
||||
$this->assertEquals(array(array('setBar', array('foo', new Reference('foo'), array(true, false)))), $services['method_call2']->getMethodCalls(), '->load() parses the method_call tag');
|
||||
|
@ -97,8 +97,9 @@ class YamlFileLoaderTest extends \PHPUnit_Framework_TestCase
|
||||
$this->assertTrue(isset($services['foo']), '->load() parses service elements');
|
||||
$this->assertEquals('Symfony\\Component\\DependencyInjection\\Definition', get_class($services['foo']), '->load() converts service element to Definition instances');
|
||||
$this->assertEquals('FooClass', $services['foo']->getClass(), '->load() parses the class attribute');
|
||||
$this->assertTrue($services['shared']->isShared(), '->load() parses the shared attribute');
|
||||
$this->assertFalse($services['non_shared']->isShared(), '->load() parses the shared attribute');
|
||||
$this->assertEquals('container', $services['scope.container']->getScope());
|
||||
$this->assertEquals('custom', $services['scope.custom']->getScope());
|
||||
$this->assertEquals('prototype', $services['scope.prototype']->getScope());
|
||||
$this->assertEquals('getInstance', $services['constructor']->getFactoryMethod(), '->load() parses the factory_method attribute');
|
||||
$this->assertEquals('%path%/foo.php', $services['file']->getFile(), '->load() parses the file tag');
|
||||
$this->assertEquals(array('foo', new Reference('foo'), array(true, false)), $services['arguments']->getArguments(), '->load() parses the argument tags');
|
||||
|
@ -156,122 +156,6 @@ class HttpKernelTest extends \PHPUnit_Framework_TestCase
|
||||
$this->assertEquals('foo', $kernel->handle(new Request())->getContent());
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox A master request should be set on the kernel for the duration of handle(), then unset
|
||||
*/
|
||||
public function testHandleSetsTheCurrentRequest()
|
||||
{
|
||||
$dispatcher = new EventDispatcher();
|
||||
$resolver = $this->getMock('Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface');
|
||||
$kernel = new HttpKernel($dispatcher, $resolver);
|
||||
|
||||
$request = new Request();
|
||||
$expected = new Response();
|
||||
|
||||
$testCase = $this;
|
||||
$controller = function() use($expected, $kernel, $testCase, $request)
|
||||
{
|
||||
$testCase->assertSame($request, $kernel->getRequest(), '->handle() sets the current request when there is no parent request');
|
||||
return $expected;
|
||||
};
|
||||
|
||||
$resolver->expects($this->once())
|
||||
->method('getController')
|
||||
->with($request)
|
||||
->will($this->returnValue($controller));
|
||||
$resolver->expects($this->once())
|
||||
->method('getArguments')
|
||||
->with($request, $controller)
|
||||
->will($this->returnValue(array()));
|
||||
|
||||
$actual = $kernel->handle($request);
|
||||
|
||||
$this->assertSame($expected, $actual, '->handle() returns the response');
|
||||
$this->assertNull($kernel->getRequest(), '->handle() restores the parent (null) request');
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox The parent request is restored following a sub request
|
||||
* @dataProvider provideRequestTypes
|
||||
*/
|
||||
public function testHandleRestoresThePreviousRequest($requestType)
|
||||
{
|
||||
$dispatcher = new EventDispatcher();
|
||||
$resolver = $this->getMock('Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface');
|
||||
$kernel = new HttpKernel($dispatcher, $resolver);
|
||||
|
||||
$parentRequest = new Request(array('name' => 'parent_request'));
|
||||
$request = new Request(array('name' => 'current_request'));
|
||||
$expected = new Response();
|
||||
|
||||
// sets a parent request to emulate a subrequest
|
||||
$reflProp = new \ReflectionProperty($kernel, 'request');
|
||||
$reflProp->setAccessible(true);
|
||||
$reflProp->setValue($kernel, $parentRequest);
|
||||
|
||||
$testCase = $this;
|
||||
$controller = function() use($expected, $kernel, $testCase, $request)
|
||||
{
|
||||
$testCase->assertSame($request, $kernel->getRequest(), '->handle() sets the current request when there is a parent request');
|
||||
return $expected;
|
||||
};
|
||||
|
||||
$resolver->expects($this->once())
|
||||
->method('getController')
|
||||
->with($request)
|
||||
->will($this->returnValue($controller));
|
||||
$resolver->expects($this->once())
|
||||
->method('getArguments')
|
||||
->with($request, $controller)
|
||||
->will($this->returnValue(array()));
|
||||
|
||||
// the behavior should be the same, regardless of request type
|
||||
$actual = $kernel->handle($request, $requestType);
|
||||
|
||||
$this->assertSame($expected, $actual, '->handle() returns the response');
|
||||
$this->assertSame($parentRequest, $kernel->getRequest(), '->handle() restores the parent request');
|
||||
}
|
||||
|
||||
public function provideRequestTypes()
|
||||
{
|
||||
return array(
|
||||
array(HttpKernelInterface::MASTER_REQUEST),
|
||||
array(HttpKernelInterface::SUB_REQUEST),
|
||||
);
|
||||
}
|
||||
|
||||
public function testHandleRestoresThePreviousRequestOnException()
|
||||
{
|
||||
$dispatcher = new EventDispatcher();
|
||||
$resolver = $this->getMock('Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface');
|
||||
$kernel = new HttpKernel($dispatcher, $resolver);
|
||||
$request = new Request();
|
||||
|
||||
$expected = new \Exception();
|
||||
$controller = function() use ($expected)
|
||||
{
|
||||
throw $expected;
|
||||
};
|
||||
|
||||
$resolver->expects($this->once())
|
||||
->method('getController')
|
||||
->with($request)
|
||||
->will($this->returnValue($controller));
|
||||
$resolver->expects($this->once())
|
||||
->method('getArguments')
|
||||
->with($request, $controller)
|
||||
->will($this->returnValue(array()));
|
||||
|
||||
try {
|
||||
$kernel->handle($request);
|
||||
$this->fail('->handle() suppresses the controller exception');
|
||||
} catch (\Exception $actual) {
|
||||
$this->assertSame($expected, $actual, '->handle() throws the controller exception');
|
||||
}
|
||||
|
||||
$this->assertNull($kernel->getRequest(), '->handle() restores the parent (null) request when the controller throws an exception');
|
||||
}
|
||||
|
||||
protected function getResolver($controller = null)
|
||||
{
|
||||
if (null === $controller) {
|
||||
|
Reference in New Issue
Block a user