merged branch fabpot/contagious-services (PR #7007)
This PR was merged into the master branch. Discussion ---------- [2.3] [WIP] Synchronized services... | Q | A | ------------- | --- | Bug fix? | no | New feature? | no | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #5300, #6756 | License | MIT | Doc PR | symfony/symfony-docs#2343 Todo: - [x] update documentation - [x] find a better name than contagious (synchronized)? refs #6932, refs #5012 This PR is a proof of concept that tries to find a solution for some problems we have with scopes and services depending on scoped services (mostly the request service in Symfony). Basically, whenever you want to inject the Request into a service, you have two possibilities: * put your own service into the request scope (a new service will be created whenever a sub-request is run, and the service is not available outside the request scope); * set the request service reference as non-strict (your service is always available but the request you have depends on when the service is created the first time). This PR addresses this issue by allowing to use the second option but you service still always has the right Request service (see below for a longer explanation on how it works). There is another issue that this PR fixes: edge cases and weird behaviors. There are several bug reports about some weird behaviors, and most of the time, this is related to the sub-requests. That's because the Request is injected into several Symfony objects without being updated correctly when leaving the request scope. Let me explain that: when a listener for instance needs the Request object, it can listen to the `kernel.request` event and store the request somewhere. So, whenever you enter a sub-request, the listener will get the new one. But when the sub-request ends, the listener has no way to know that it needs to reset the request to the master one. In practice, that's not really an issue, but let me show you an example of this issue in practice: * You have a controller that is called with the English locale; * The controller (probably via a template) renders a sub-request that uses the French locale; * After the rendering, and from the controller, you try to generate a URL. Which locale the router will use? Yes, the French locale, which is wrong. To fix these issues, this PR introduces a new notion in the DIC: synchronized services. When a service is marked as synchronized, all method calls involving this service will be called each time this service is set. When in a scope, methods are also called to restore the previous version of the service when the scope leaves. If you have a look at the router or the locale listener, you will see that there is now a `setRequest` method that will called whenever the request service changes (because the `Container::set()` method is called or because the service is changed by a scope change). Commits -------17269e1
[DependencyInjection] fixed management of scoped services with an invalid behavior set to nullbb83b3e
[HttpKernel] added a safeguard for when a fragment is rendered outside the context of a master request5d7b835
[FrameworkBundle] added some functional testsff9d688
fixed Request management for FragmentHandler1b98ad3
fixed Request management for LocaleListenera7b2b7e
fixed Request management for RequestListener0892135
[HttpKernel] ensured that the Request is null when outside of the Request scope2ffcfb9
[FrameworkBundle] made the Request service synchronizedec1e7ca
[DependencyInjection] added a way to automatically update scoped services
This commit is contained in:
commit
74f96bfebf
@ -49,16 +49,8 @@ class HttpKernelExtensionTest extends TestCase
|
||||
$strategy->expects($this->once())->method('getName')->will($this->returnValue('inline'));
|
||||
$strategy->expects($this->once())->method('render')->will($return);
|
||||
|
||||
// simulate a master request
|
||||
$event = $this->getMockBuilder('Symfony\Component\HttpKernel\Event\GetResponseEvent')->disableOriginalConstructor()->getMock();
|
||||
$event
|
||||
->expects($this->once())
|
||||
->method('getRequest')
|
||||
->will($this->returnValue(Request::create('/')))
|
||||
;
|
||||
|
||||
$renderer = new FragmentHandler(array($strategy));
|
||||
$renderer->onKernelRequest($event);
|
||||
$renderer->setRequest(Request::create('/'));
|
||||
|
||||
return $renderer;
|
||||
}
|
||||
|
@ -14,9 +14,9 @@
|
||||
|
||||
<services>
|
||||
<service id="fragment.handler" class="%fragment.handler.class%">
|
||||
<tag name="kernel.event_subscriber" />
|
||||
<argument type="collection" />
|
||||
<argument>%kernel.debug%</argument>
|
||||
<call method="setRequest"><argument type="service" id="request" on-invalid="null" strict="false" /></call>
|
||||
</service>
|
||||
|
||||
<service id="fragment.renderer.inline" class="%fragment.renderer.inline.class%">
|
||||
|
@ -94,6 +94,7 @@
|
||||
<argument type="service" id="router" />
|
||||
<argument type="service" id="router.request_context" on-invalid="ignore" />
|
||||
<argument type="service" id="logger" on-invalid="ignore" />
|
||||
<call method="setRequest"><argument type="service" id="request" on-invalid="null" strict="false" /></call>
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
||||
|
@ -40,7 +40,7 @@
|
||||
This service definition only defines the scope of the request.
|
||||
It is used to check references scope.
|
||||
-->
|
||||
<service id="request" scope="request" synthetic="true" />
|
||||
<service id="request" scope="request" synthetic="true" synchronized="true" />
|
||||
|
||||
<service id="service_container" synthetic="true" />
|
||||
|
||||
|
@ -38,6 +38,7 @@
|
||||
<tag name="kernel.event_subscriber" />
|
||||
<argument>%kernel.default_locale%</argument>
|
||||
<argument type="service" id="router" on-invalid="ignore" />
|
||||
<call method="setRequest"><argument type="service" id="request" on-invalid="null" strict="false" /></call>
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
||||
|
@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Controller;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\DependencyInjection\ContainerAware;
|
||||
use Symfony\Component\HttpKernel\Controller\ControllerReference;
|
||||
|
||||
class SubRequestController extends ContainerAware
|
||||
{
|
||||
public function indexAction()
|
||||
{
|
||||
$handler = $this->container->get('fragment.handler');
|
||||
|
||||
$errorUrl = $this->generateUrl('subrequest_fragment_error', array('_locale' => 'fr', '_format' => 'json'));
|
||||
$altUrl = $this->generateUrl('subrequest_fragment', array('_locale' => 'fr', '_format' => 'json'));
|
||||
|
||||
// simulates a failure during the rendering of a fragment...
|
||||
// should render fr/json
|
||||
$content = $handler->render($errorUrl, 'inline', array('alt' => $altUrl));
|
||||
|
||||
// ...to check that the FragmentListener still references the right Request
|
||||
// when rendering another fragment after the error occured
|
||||
// should render en/html instead of fr/json
|
||||
$content .= $handler->render(new ControllerReference('TestBundle:SubRequest:fragment'));
|
||||
|
||||
// forces the LocaleListener to set fr for the locale...
|
||||
// should render fr/json
|
||||
$content .= $handler->render($altUrl);
|
||||
|
||||
// ...and check that after the rendering, the original Request is back
|
||||
// and en is used as a locale
|
||||
// should use en/html instead of fr/json
|
||||
$content .= '--'.$this->generateUrl('subrequest_fragment');
|
||||
|
||||
// The RouterListener is also tested as if it does not keep the right
|
||||
// Request in the context, a 301 would be generated
|
||||
|
||||
return new Response($content);
|
||||
}
|
||||
|
||||
public function fragmentAction(Request $request)
|
||||
{
|
||||
return new Response('--'.$request->getLocale().'/'.$request->getRequestFormat());
|
||||
}
|
||||
|
||||
public function fragmentErrorAction()
|
||||
{
|
||||
throw new \RuntimeException('error');
|
||||
}
|
||||
|
||||
protected function generateUrl($name, $arguments = array())
|
||||
{
|
||||
return $this->container->get('router')->generate($name, $arguments);
|
||||
}
|
||||
}
|
@ -21,3 +21,18 @@ session_showflash:
|
||||
profiler:
|
||||
path: /profiler
|
||||
defaults: { _controller: TestBundle:Profiler:index }
|
||||
|
||||
subrequest_index:
|
||||
path: /subrequest/{_locale}.{_format}
|
||||
defaults: { _controller: TestBundle:SubRequest:index, _format: "html" }
|
||||
schemes: [https]
|
||||
|
||||
subrequest_fragment_error:
|
||||
path: /subrequest/fragment/error/{_locale}.{_format}
|
||||
defaults: { _controller: TestBundle:SubRequest:fragmentError, _format: "html" }
|
||||
schemes: [http]
|
||||
|
||||
subrequest_fragment:
|
||||
path: /subrequest/fragment/{_locale}.{_format}
|
||||
defaults: { _controller: TestBundle:SubRequest:fragment, _format: "html" }
|
||||
schemes: [http]
|
||||
|
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\FrameworkBundle\Tests\Functional;
|
||||
|
||||
/**
|
||||
* @group functional
|
||||
*/
|
||||
class SubRequestsTest extends WebTestCase
|
||||
{
|
||||
public function testStateAfterSubRequest()
|
||||
{
|
||||
$client = $this->createClient(array('test_case' => 'Session', 'root_config' => 'config.yml'));
|
||||
$client->request('GET', 'https://localhost/subrequest/en');
|
||||
|
||||
$this->assertEquals('--fr/json--en/html--fr/json--http://localhost/subrequest/fragment/en', $client->getResponse()->getContent());
|
||||
}
|
||||
}
|
@ -88,9 +88,7 @@ class ResolveInvalidReferencesPass implements CompilerPassInterface
|
||||
$exists = $this->container->has($id);
|
||||
|
||||
// resolve invalid behavior
|
||||
if ($exists && ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $invalidBehavior) {
|
||||
$arguments[$k] = new Reference($id, ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, $argument->isStrict());
|
||||
} elseif (!$exists && ContainerInterface::NULL_ON_INVALID_REFERENCE === $invalidBehavior) {
|
||||
if (!$exists && ContainerInterface::NULL_ON_INVALID_REFERENCE === $invalidBehavior) {
|
||||
$arguments[$k] = null;
|
||||
} elseif (!$exists && ContainerInterface::IGNORE_ON_INVALID_REFERENCE === $invalidBehavior) {
|
||||
if ($inMethodCall) {
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace Symfony\Component\DependencyInjection;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Exception\InactiveScopeException;
|
||||
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
|
||||
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
|
||||
@ -206,6 +207,10 @@ class Container implements IntrospectableContainerInterface
|
||||
}
|
||||
|
||||
$this->services[$id] = $service;
|
||||
|
||||
if (method_exists($this, $method = 'synchronize'.strtr($id, array('_' => '', '.' => '_')).'Service')) {
|
||||
$this->$method();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -221,7 +226,7 @@ class Container implements IntrospectableContainerInterface
|
||||
{
|
||||
$id = strtolower($id);
|
||||
|
||||
return isset($this->services[$id]) || method_exists($this, 'get'.strtr($id, array('_' => '', '.' => '_')).'Service');
|
||||
return array_key_exists($id, $this->services) || method_exists($this, 'get'.strtr($id, array('_' => '', '.' => '_')).'Service');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -247,7 +252,7 @@ class Container implements IntrospectableContainerInterface
|
||||
{
|
||||
$id = strtolower($id);
|
||||
|
||||
if (isset($this->services[$id])) {
|
||||
if (array_key_exists($id, $this->services)) {
|
||||
return $this->services[$id];
|
||||
}
|
||||
|
||||
@ -263,10 +268,14 @@ class Container implements IntrospectableContainerInterface
|
||||
} catch (\Exception $e) {
|
||||
unset($this->loading[$id]);
|
||||
|
||||
if (isset($this->services[$id])) {
|
||||
if (array_key_exists($id, $this->services)) {
|
||||
unset($this->services[$id]);
|
||||
}
|
||||
|
||||
if ($e instanceof InactiveScopeException && self::EXCEPTION_ON_INVALID_REFERENCE !== $invalidBehavior) {
|
||||
return null;
|
||||
}
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
@ -289,7 +298,7 @@ class Container implements IntrospectableContainerInterface
|
||||
*/
|
||||
public function initialized($id)
|
||||
{
|
||||
return isset($this->services[strtolower($id)]);
|
||||
return array_key_exists(strtolower($id), $this->services);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -393,8 +402,11 @@ class Container implements IntrospectableContainerInterface
|
||||
$services = $this->scopeStacks[$name]->pop();
|
||||
$this->scopedServices += $services;
|
||||
|
||||
array_unshift($services, $this->services);
|
||||
$this->services = call_user_func_array('array_merge', $services);
|
||||
foreach ($services as $array) {
|
||||
foreach ($array as $id => $service) {
|
||||
$this->set($id, $service, $name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -46,6 +46,11 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
|
||||
*/
|
||||
private $definitions = array();
|
||||
|
||||
/**
|
||||
* @var Definition[]
|
||||
*/
|
||||
private $obsoleteDefinitions = array();
|
||||
|
||||
/**
|
||||
* @var Alias[]
|
||||
*/
|
||||
@ -351,14 +356,28 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
|
||||
|
||||
if ($this->isFrozen()) {
|
||||
// setting a synthetic service on a frozen container is alright
|
||||
if (!isset($this->definitions[$id]) || !$this->definitions[$id]->isSynthetic()) {
|
||||
if (
|
||||
(!isset($this->definitions[$id]) && !isset($this->obsoleteDefinitions[$id]))
|
||||
||
|
||||
(isset($this->definitions[$id]) && !$this->definitions[$id]->isSynthetic())
|
||||
||
|
||||
(isset($this->obsoleteDefinitions[$id]) && !$this->obsoleteDefinitions[$id]->isSynthetic())
|
||||
) {
|
||||
throw new BadMethodCallException(sprintf('Setting service "%s" on a frozen container is not allowed.', $id));
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($this->definitions[$id])) {
|
||||
$this->obsoleteDefinitions[$id] = $this->definitions[$id];
|
||||
}
|
||||
|
||||
unset($this->definitions[$id], $this->aliases[$id]);
|
||||
|
||||
parent::set($id, $service, $scope);
|
||||
|
||||
if (isset($this->obsoleteDefinitions[$id]) && $this->obsoleteDefinitions[$id]->isSynchronized()) {
|
||||
$this->synchronize($id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -885,19 +904,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
|
||||
}
|
||||
|
||||
foreach ($definition->getMethodCalls() as $call) {
|
||||
$services = self::getServiceConditionals($call[1]);
|
||||
|
||||
$ok = true;
|
||||
foreach ($services as $s) {
|
||||
if (!$this->has($s)) {
|
||||
$ok = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($ok) {
|
||||
call_user_func_array(array($service, $call[0]), $this->resolveServices($parameterBag->resolveValue($call[1])));
|
||||
}
|
||||
$this->callMethod($service, $call);
|
||||
}
|
||||
|
||||
$properties = $this->resolveServices($parameterBag->resolveValue($definition->getProperties()));
|
||||
@ -999,4 +1006,43 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
|
||||
|
||||
return $services;
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronizes a service change.
|
||||
*
|
||||
* This method updates all services that depend on the given
|
||||
* service by calling all methods referencing it.
|
||||
*
|
||||
* @param string $id A service id
|
||||
*/
|
||||
private function synchronize($id)
|
||||
{
|
||||
foreach ($this->definitions as $definitionId => $definition) {
|
||||
// only check initialized services
|
||||
if (!$this->initialized($definitionId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($definition->getMethodCalls() as $call) {
|
||||
foreach ($call[1] as $argument) {
|
||||
if ($argument instanceof Reference && $id == (string) $argument) {
|
||||
$this->callMethod($this->get($definitionId), $call);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function callMethod($service, $call)
|
||||
{
|
||||
$services = self::getServiceConditionals($call[1]);
|
||||
|
||||
foreach ($services as $s) {
|
||||
if (!$this->has($s)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
call_user_func_array(array($service, $call[0]), $this->resolveServices($this->getParameterBag()->resolveValue($call[1])));
|
||||
}
|
||||
}
|
||||
|
@ -36,6 +36,7 @@ class Definition
|
||||
private $public;
|
||||
private $synthetic;
|
||||
private $abstract;
|
||||
private $synchronized;
|
||||
|
||||
protected $arguments;
|
||||
|
||||
@ -56,6 +57,7 @@ class Definition
|
||||
$this->tags = array();
|
||||
$this->public = true;
|
||||
$this->synthetic = false;
|
||||
$this->synchronized = false;
|
||||
$this->abstract = false;
|
||||
$this->properties = array();
|
||||
}
|
||||
@ -569,6 +571,34 @@ class Definition
|
||||
return $this->public;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the synchronized flag of this service.
|
||||
*
|
||||
* @param Boolean $boolean
|
||||
*
|
||||
* @return Definition The current instance
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function setSynchronized($boolean)
|
||||
{
|
||||
$this->synchronized = (Boolean) $boolean;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this service is synchronized.
|
||||
*
|
||||
* @return Boolean
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function isSynchronized()
|
||||
{
|
||||
return $this->synchronized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether this definition is synthetic, that is not constructed by the
|
||||
* container, but dynamically injected.
|
||||
|
@ -567,7 +567,7 @@ EOF;
|
||||
*/
|
||||
private function addServices()
|
||||
{
|
||||
$publicServices = $privateServices = $aliasServices = '';
|
||||
$publicServices = $privateServices = $aliasServices = $synchronizers = '';
|
||||
$definitions = $this->container->getDefinitions();
|
||||
ksort($definitions);
|
||||
foreach ($definitions as $id => $definition) {
|
||||
@ -576,6 +576,8 @@ EOF;
|
||||
} else {
|
||||
$privateServices .= $this->addService($id, $definition);
|
||||
}
|
||||
|
||||
$synchronizers .= $this->addServiceSynchronizer($id, $definition);
|
||||
}
|
||||
|
||||
$aliases = $this->container->getAliases();
|
||||
@ -584,7 +586,60 @@ EOF;
|
||||
$aliasServices .= $this->addServiceAlias($alias, $id);
|
||||
}
|
||||
|
||||
return $publicServices.$aliasServices.$privateServices;
|
||||
return $publicServices.$aliasServices.$synchronizers.$privateServices;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds synchronizer methods.
|
||||
*
|
||||
* @param string $id A service identifier
|
||||
* @param Definition $definition A Definition instance
|
||||
*/
|
||||
private function addServiceSynchronizer($id, Definition $definition)
|
||||
{
|
||||
if (!$definition->isSynchronized()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$code = '';
|
||||
foreach ($this->container->getDefinitions() as $definitionId => $definition) {
|
||||
foreach ($definition->getMethodCalls() as $call) {
|
||||
foreach ($call[1] as $argument) {
|
||||
if ($argument instanceof Reference && $id == (string) $argument) {
|
||||
$arguments = array();
|
||||
foreach ($call[1] as $value) {
|
||||
$arguments[] = $this->dumpValue($value);
|
||||
}
|
||||
|
||||
$call = $this->wrapServiceConditionals($call[1], sprintf("\$this->get('%s')->%s(%s);", $definitionId, $call[0], implode(', ', $arguments)));
|
||||
|
||||
$code .= <<<EOF
|
||||
if (\$this->initialized('$definitionId')) {
|
||||
$call
|
||||
}
|
||||
|
||||
EOF;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$code) {
|
||||
return;
|
||||
}
|
||||
|
||||
$name = Container::camelize($id);
|
||||
|
||||
return <<<EOF
|
||||
|
||||
/**
|
||||
* Updates the '$id' service.
|
||||
*/
|
||||
protected function synchronize{$name}Service()
|
||||
{
|
||||
$code }
|
||||
|
||||
EOF;
|
||||
}
|
||||
|
||||
private function addNewInstance($id, Definition $definition, $return, $instantiation)
|
||||
|
@ -127,6 +127,12 @@ class XmlDumper extends Dumper
|
||||
if (!$definition->isPublic()) {
|
||||
$service->setAttribute('public', 'false');
|
||||
}
|
||||
if ($definition->isSynthetic()) {
|
||||
$service->setAttribute('synthetic', 'true');
|
||||
}
|
||||
if ($definition->isSynchronized()) {
|
||||
$service->setAttribute('synchronized', 'true');
|
||||
}
|
||||
|
||||
foreach ($definition->getTags() as $name => $tags) {
|
||||
foreach ($tags as $attributes) {
|
||||
@ -239,6 +245,9 @@ class XmlDumper extends Dumper
|
||||
} elseif ($behaviour == ContainerInterface::IGNORE_ON_INVALID_REFERENCE) {
|
||||
$element->setAttribute('on-invalid', 'ignore');
|
||||
}
|
||||
if (!$value->isStrict()) {
|
||||
$element->setAttribute('strict', 'false');
|
||||
}
|
||||
} elseif ($value instanceof Definition) {
|
||||
$element->setAttribute('type', 'service');
|
||||
$this->addService($value, null, $element);
|
||||
|
@ -94,6 +94,14 @@ class YamlDumper extends Dumper
|
||||
$code .= sprintf(" file: %s\n", $definition->getFile());
|
||||
}
|
||||
|
||||
if ($definition->isSynthetic()) {
|
||||
$code .= sprintf(" synthetic: true\n");
|
||||
}
|
||||
|
||||
if ($definition->isSynchronized()) {
|
||||
$code .= sprintf(" synchronized: true\n");
|
||||
}
|
||||
|
||||
if ($definition->getFactoryMethod()) {
|
||||
$code .= sprintf(" factory_method: %s\n", $definition->getFactoryMethod());
|
||||
}
|
||||
|
@ -148,7 +148,7 @@ class XmlFileLoader extends FileLoader
|
||||
$definition = new Definition();
|
||||
}
|
||||
|
||||
foreach (array('class', 'scope', 'public', 'factory-class', 'factory-method', 'factory-service', 'synthetic', 'abstract') as $key) {
|
||||
foreach (array('class', 'scope', 'public', 'factory-class', 'factory-method', 'factory-service', 'synthetic', 'synchronized', 'abstract') as $key) {
|
||||
if (isset($service[$key])) {
|
||||
$method = 'set'.str_replace('-', '', $key);
|
||||
$definition->$method((string) $service->getAttributeAsPhp($key));
|
||||
|
@ -153,6 +153,10 @@ class YamlFileLoader extends FileLoader
|
||||
$definition->setSynthetic($service['synthetic']);
|
||||
}
|
||||
|
||||
if (isset($service['synchronized'])) {
|
||||
$definition->setSynchronized($service['synchronized']);
|
||||
}
|
||||
|
||||
if (isset($service['public'])) {
|
||||
$definition->setPublic($service['public']);
|
||||
}
|
||||
|
@ -86,6 +86,7 @@
|
||||
<xsd:attribute name="scope" type="xsd:string" />
|
||||
<xsd:attribute name="public" type="boolean" />
|
||||
<xsd:attribute name="synthetic" type="boolean" />
|
||||
<xsd:attribute name="synchronized" type="boolean" />
|
||||
<xsd:attribute name="abstract" type="boolean" />
|
||||
<xsd:attribute name="factory-class" type="xsd:string" />
|
||||
<xsd:attribute name="factory-method" type="xsd:string" />
|
||||
|
@ -22,6 +22,7 @@ use Symfony\Component\DependencyInjection\Definition;
|
||||
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
|
||||
use Symfony\Component\DependencyInjection\Scope;
|
||||
use Symfony\Component\Config\Resource\FileResource;
|
||||
|
||||
class ContainerBuilderTest extends \PHPUnit_Framework_TestCase
|
||||
@ -577,6 +578,52 @@ class ContainerBuilderTest extends \PHPUnit_Framework_TestCase
|
||||
$this->assertEquals($a, $container->get('a'));
|
||||
}
|
||||
|
||||
public function testSetOnSynchronizedService()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
$container->register('baz', 'BazClass')
|
||||
->setSynchronized(true)
|
||||
;
|
||||
$container->register('bar', 'BarClass')
|
||||
->addMethodCall('setBaz', array(new Reference('baz')))
|
||||
;
|
||||
|
||||
$container->set('baz', $baz = new \BazClass());
|
||||
$this->assertSame($baz, $container->get('bar')->getBaz());
|
||||
|
||||
$container->set('baz', $baz = new \BazClass());
|
||||
$this->assertSame($baz, $container->get('bar')->getBaz());
|
||||
}
|
||||
|
||||
public function testSynchronizedServiceWithScopes()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
$container->addScope(new Scope('foo'));
|
||||
$container->register('baz', 'BazClass')
|
||||
->setSynthetic(true)
|
||||
->setSynchronized(true)
|
||||
->setScope('foo')
|
||||
;
|
||||
$container->register('bar', 'BarClass')
|
||||
->addMethodCall('setBaz', array(new Reference('baz', ContainerInterface::NULL_ON_INVALID_REFERENCE, false)))
|
||||
;
|
||||
$container->compile();
|
||||
|
||||
$container->enterScope('foo');
|
||||
$container->set('baz', $outerBaz = new \BazClass(), 'foo');
|
||||
$this->assertSame($outerBaz, $container->get('bar')->getBaz());
|
||||
|
||||
$container->enterScope('foo');
|
||||
$container->set('baz', $innerBaz = new \BazClass(), 'foo');
|
||||
$this->assertSame($innerBaz, $container->get('bar')->getBaz());
|
||||
$container->leaveScope('foo');
|
||||
|
||||
$this->assertNotSame($innerBaz, $container->get('bar')->getBaz());
|
||||
$this->assertSame($outerBaz, $container->get('bar')->getBaz());
|
||||
|
||||
$container->leaveScope('foo');
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException BadMethodCallException
|
||||
*/
|
||||
|
@ -15,6 +15,7 @@ use Symfony\Component\DependencyInjection\Scope;
|
||||
use Symfony\Component\DependencyInjection\Container;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
|
||||
use Symfony\Component\DependencyInjection\Exception\InactiveScopeException;
|
||||
|
||||
class ContainerTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
@ -98,7 +99,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('scoped', 'scoped_foo', 'bar', 'foo_bar', 'foo.baz', 'circular', 'throw_exception', 'throws_exception_on_service_configuration', 'service_container'), $sc->getServiceIds(), '->getServiceIds() returns defined service ids by getXXXService() methods');
|
||||
$this->assertEquals(array('scoped', 'scoped_foo', 'inactive', 'bar', 'foo_bar', 'foo.baz', 'circular', 'throw_exception', 'throws_exception_on_service_configuration', 'service_container'), $sc->getServiceIds(), '->getServiceIds() returns defined service ids by getXXXService() methods');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -179,6 +180,15 @@ class ContainerTest extends \PHPUnit_Framework_TestCase
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Symfony\Component\DependencyInjection\Container::get
|
||||
*/
|
||||
public function testGetReturnsNullOnInactiveScope()
|
||||
{
|
||||
$sc = new ProjectServiceContainer();
|
||||
$this->assertNull($sc->get('inactive', ContainerInterface::NULL_ON_INVALID_REFERENCE));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Symfony\Component\DependencyInjection\Container::has
|
||||
*/
|
||||
@ -476,6 +486,11 @@ class ProjectServiceContainer extends Container
|
||||
return $this->services['scoped_foo'] = $this->scopedServices['foo']['scoped_foo'] = new \stdClass();
|
||||
}
|
||||
|
||||
protected function getInactiveService()
|
||||
{
|
||||
throw new InactiveScopeException('request', 'request');
|
||||
}
|
||||
|
||||
protected function getBarService()
|
||||
{
|
||||
return $this->__bar;
|
||||
|
@ -149,7 +149,19 @@ class DefinitionTest extends \PHPUnit_Framework_TestCase
|
||||
$def = new Definition('stdClass');
|
||||
$this->assertFalse($def->isSynthetic(), '->isSynthetic() returns false by default');
|
||||
$this->assertSame($def, $def->setSynthetic(true), '->setSynthetic() implements a fluent interface');
|
||||
$this->assertTrue($def->isSynthetic(), '->isSynthetic() returns true if the instance must not be public.');
|
||||
$this->assertTrue($def->isSynthetic(), '->isSynthetic() returns true if the service is synthetic.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Symfony\Component\DependencyInjection\Definition::setSynchronized
|
||||
* @covers Symfony\Component\DependencyInjection\Definition::isSynchronized
|
||||
*/
|
||||
public function testSetIsSynchronized()
|
||||
{
|
||||
$def = new Definition('stdClass');
|
||||
$this->assertFalse($def->isSynchronized(), '->isSynchronized() returns false by default');
|
||||
$this->assertSame($def, $def->setSynchronized(true), '->setSynchronized() implements a fluent interface');
|
||||
$this->assertTrue($def->isSynchronized(), '->isSynchronized() returns true if the service is synchronized.');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -71,5 +71,14 @@ $container
|
||||
->register('baz', 'Baz')
|
||||
->addMethodCall('setFoo', array(new Reference('foo_with_inline')))
|
||||
;
|
||||
$container
|
||||
->register('request', 'Request')
|
||||
->setSynthetic(true)
|
||||
->setSynchronized(true)
|
||||
;
|
||||
$container
|
||||
->register('depends_on_request', 'stdClass')
|
||||
->addMethodCall('setRequest', array(new Reference('request', ContainerInterface::NULL_ON_INVALID_REFERENCE, false)))
|
||||
;
|
||||
|
||||
return $container;
|
||||
|
@ -12,6 +12,8 @@ digraph sc {
|
||||
node_foo_with_inline [label="foo_with_inline\nFoo\n", shape=record, fillcolor="#eeeeee", style="filled"];
|
||||
node_inlined [label="inlined\nBar\n", shape=record, fillcolor="#eeeeee", style="filled"];
|
||||
node_baz [label="baz\nBaz\n", shape=record, fillcolor="#eeeeee", style="filled"];
|
||||
node_request [label="request\nRequest\n", shape=record, fillcolor="#eeeeee", style="filled"];
|
||||
node_depends_on_request [label="depends_on_request\nstdClass\n", shape=record, fillcolor="#eeeeee", style="filled"];
|
||||
node_service_container [label="service_container\nSymfony\\Component\\DependencyInjection\\ContainerBuilder\n", shape=record, fillcolor="#9999ff", style="filled"];
|
||||
node_foo2 [label="foo2\n\n", shape=record, fillcolor="#ff9999", style="filled"];
|
||||
node_foo3 [label="foo3\n\n", shape=record, fillcolor="#ff9999", style="filled"];
|
||||
@ -28,4 +30,5 @@ digraph sc {
|
||||
node_foo_with_inline -> node_inlined [label="setBar()" style="dashed"];
|
||||
node_inlined -> node_baz [label="setBaz()" style="dashed"];
|
||||
node_baz -> node_foo_with_inline [label="setFoo()" style="dashed"];
|
||||
node_depends_on_request -> node_request [label="setRequest()" style="dashed"];
|
||||
}
|
||||
|
@ -13,6 +13,11 @@ class BarClass
|
||||
{
|
||||
$this->baz = $baz;
|
||||
}
|
||||
|
||||
public function getBaz()
|
||||
{
|
||||
return $this->baz;
|
||||
}
|
||||
}
|
||||
|
||||
class BazClass
|
||||
|
@ -60,6 +60,23 @@ class ProjectServiceContainer extends Container
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the 'depends_on_request' service.
|
||||
*
|
||||
* This service is shared.
|
||||
* This method always returns the same instance of the service.
|
||||
*
|
||||
* @return stdClass A stdClass instance.
|
||||
*/
|
||||
protected function getDependsOnRequestService()
|
||||
{
|
||||
$this->services['depends_on_request'] = $instance = new \stdClass();
|
||||
|
||||
$instance->setRequest($this->get('request', ContainerInterface::NULL_ON_INVALID_REFERENCE));
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the 'factory_service' service.
|
||||
*
|
||||
@ -168,6 +185,19 @@ class ProjectServiceContainer extends Container
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the 'request' service.
|
||||
*
|
||||
* This service is shared.
|
||||
* This method always returns the same instance of the service.
|
||||
*
|
||||
* @throws RuntimeException always since this service is expected to be injected dynamically
|
||||
*/
|
||||
protected function getRequestService()
|
||||
{
|
||||
throw new RuntimeException('You have requested a synthetic service ("request"). The DIC does not know how to construct this service.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the alias_for_foo service alias.
|
||||
*
|
||||
@ -178,6 +208,16 @@ class ProjectServiceContainer extends Container
|
||||
return $this->get('foo');
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the 'request' service.
|
||||
*/
|
||||
protected function synchronizeRequestService()
|
||||
{
|
||||
if ($this->initialized('depends_on_request')) {
|
||||
$this->get('depends_on_request')->setRequest($this->get('request', ContainerInterface::NULL_ON_INVALID_REFERENCE));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the 'inlined' service.
|
||||
*
|
||||
|
@ -69,6 +69,23 @@ class ProjectServiceContainer extends Container
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the 'depends_on_request' service.
|
||||
*
|
||||
* This service is shared.
|
||||
* This method always returns the same instance of the service.
|
||||
*
|
||||
* @return stdClass A stdClass instance.
|
||||
*/
|
||||
protected function getDependsOnRequestService()
|
||||
{
|
||||
$this->services['depends_on_request'] = $instance = new \stdClass();
|
||||
|
||||
$instance->setRequest($this->get('request', ContainerInterface::NULL_ON_INVALID_REFERENCE));
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the 'factory_service' service.
|
||||
*
|
||||
@ -174,6 +191,19 @@ class ProjectServiceContainer extends Container
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the 'request' service.
|
||||
*
|
||||
* This service is shared.
|
||||
* This method always returns the same instance of the service.
|
||||
*
|
||||
* @throws RuntimeException always since this service is expected to be injected dynamically
|
||||
*/
|
||||
protected function getRequestService()
|
||||
{
|
||||
throw new RuntimeException('You have requested a synthetic service ("request"). The DIC does not know how to construct this service.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the alias_for_foo service alias.
|
||||
*
|
||||
@ -184,6 +214,16 @@ class ProjectServiceContainer extends Container
|
||||
return $this->get('foo');
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the 'request' service.
|
||||
*/
|
||||
protected function synchronizeRequestService()
|
||||
{
|
||||
if ($this->initialized('depends_on_request')) {
|
||||
$this->get('depends_on_request')->setRequest($this->get('request', ContainerInterface::NULL_ON_INVALID_REFERENCE));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
@ -46,5 +46,6 @@
|
||||
<service id="alias_for_foo" alias="foo" />
|
||||
<service id="another_alias_for_foo" alias="foo" public="false" />
|
||||
<service id="factory_service" factory-method="getInstance" factory-service="baz_factory" />
|
||||
<service id="request" class="Request" synthetic="true" synchronized="true"/>
|
||||
</services>
|
||||
</container>
|
||||
|
@ -67,6 +67,12 @@
|
||||
<argument type="service" id="foo_with_inline"/>
|
||||
</call>
|
||||
</service>
|
||||
<service id="request" class="Request" synthetic="true" synchronized="true"/>
|
||||
<service id="depends_on_request" class="stdClass">
|
||||
<call method="setRequest">
|
||||
<argument type="service" id="request" on-invalid="null" strict="false"/>
|
||||
</call>
|
||||
</service>
|
||||
<service id="alias_for_foo" alias="foo"/>
|
||||
</services>
|
||||
</container>
|
||||
|
@ -24,3 +24,7 @@ services:
|
||||
alias: foo
|
||||
public: false
|
||||
factory_service: { class: BazClass, factory_method: getInstance, factory_service: baz_factory }
|
||||
request:
|
||||
class: Request
|
||||
synthetic: true
|
||||
synchronized: true
|
||||
|
@ -57,4 +57,13 @@ services:
|
||||
calls:
|
||||
- [setFoo, ['@foo_with_inline']]
|
||||
|
||||
request:
|
||||
class: Request
|
||||
synthetic: true
|
||||
synchronized: true
|
||||
depends_on_request:
|
||||
class: stdClass
|
||||
calls:
|
||||
- [setRequest, ['@?request']]
|
||||
|
||||
alias_for_foo: @foo
|
||||
|
@ -185,6 +185,9 @@ class XmlFileLoaderTest extends \PHPUnit_Framework_TestCase
|
||||
$this->assertEquals('getInstance', $services['factory_service']->getFactoryMethod());
|
||||
$this->assertEquals('baz_factory', $services['factory_service']->getFactoryService());
|
||||
|
||||
$this->assertTrue($services['request']->isSynthetic(), '->load() parses the synthetic flag');
|
||||
$this->assertTrue($services['request']->isSynchronized(), '->load() parses the synchronized flag');
|
||||
|
||||
$aliases = $container->getAliases();
|
||||
$this->assertTrue(isset($aliases['alias_for_foo']), '->load() parses <service> elements');
|
||||
$this->assertEquals('foo', (string) $aliases['alias_for_foo'], '->load() parses aliases');
|
||||
|
@ -128,6 +128,9 @@ class YamlFileLoaderTest extends \PHPUnit_Framework_TestCase
|
||||
$this->assertEquals(array(array('setBar', array('foo', new Reference('foo'), array(true, false)))), $services['method_call2']->getMethodCalls(), '->load() parses the method_call tag');
|
||||
$this->assertEquals('baz_factory', $services['factory_service']->getFactoryService());
|
||||
|
||||
$this->assertTrue($services['request']->isSynthetic(), '->load() parses the synthetic flag');
|
||||
$this->assertTrue($services['request']->isSynchronized(), '->load() parses the synchronized flag');
|
||||
|
||||
$aliases = $container->getAliases();
|
||||
$this->assertTrue(isset($aliases['alias_for_foo']), '->load() parses aliases');
|
||||
$this->assertEquals('foo', (string) $aliases['alias_for_foo'], '->load() parses aliases');
|
||||
|
@ -58,11 +58,13 @@ class ContainerAwareHttpKernel extends HttpKernel
|
||||
try {
|
||||
$response = parent::handle($request, $type, $catch);
|
||||
} catch (\Exception $e) {
|
||||
$this->container->set('request', null, 'request');
|
||||
$this->container->leaveScope('request');
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$this->container->set('request', null, 'request');
|
||||
$this->container->leaveScope('request');
|
||||
|
||||
return $response;
|
||||
|
@ -12,7 +12,6 @@
|
||||
namespace Symfony\Component\HttpKernel\EventListener;
|
||||
|
||||
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
|
||||
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Routing\RequestContextAwareInterface;
|
||||
@ -27,7 +26,6 @@ class LocaleListener implements EventSubscriberInterface
|
||||
{
|
||||
private $router;
|
||||
private $defaultLocale;
|
||||
private $locales = array();
|
||||
|
||||
public function __construct($defaultLocale = 'en', RequestContextAwareInterface $router = null)
|
||||
{
|
||||
@ -35,24 +33,27 @@ class LocaleListener implements EventSubscriberInterface
|
||||
$this->router = $router;
|
||||
}
|
||||
|
||||
public function onKernelResponse(FilterResponseEvent $event)
|
||||
public function setRequest(Request $request = null)
|
||||
{
|
||||
array_shift($this->locales);
|
||||
if (null === $request) {
|
||||
return;
|
||||
}
|
||||
|
||||
// setting back the locale to the previous value
|
||||
$locale = isset($this->locales[0]) ? $this->locales[0] : $this->defaultLocale;
|
||||
$request = $event->getRequest();
|
||||
$this->setLocale($request, $locale);
|
||||
if ($locale = $request->attributes->get('_locale')) {
|
||||
$request->setLocale($locale);
|
||||
}
|
||||
|
||||
if (null !== $this->router) {
|
||||
$this->router->getContext()->setParameter('_locale', $request->getLocale());
|
||||
}
|
||||
}
|
||||
|
||||
public function onKernelRequest(GetResponseEvent $event)
|
||||
{
|
||||
$request = $event->getRequest();
|
||||
|
||||
$request->setDefaultLocale($this->defaultLocale);
|
||||
$this->setLocale($request, $request->attributes->get('_locale', $this->defaultLocale));
|
||||
|
||||
array_unshift($this->locales, $request->getLocale());
|
||||
$this->setRequest($request);
|
||||
}
|
||||
|
||||
public static function getSubscribedEvents()
|
||||
@ -60,16 +61,6 @@ class LocaleListener implements EventSubscriberInterface
|
||||
return array(
|
||||
// must be registered after the Router to have access to the _locale
|
||||
KernelEvents::REQUEST => array(array('onKernelRequest', 16)),
|
||||
KernelEvents::RESPONSE => 'onKernelResponse',
|
||||
);
|
||||
}
|
||||
|
||||
private function setLocale(Request $request, $locale)
|
||||
{
|
||||
$request->setLocale($locale);
|
||||
|
||||
if (null !== $this->router) {
|
||||
$this->router->getContext()->setParameter('_locale', $request->getLocale());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ use Symfony\Component\Routing\Matcher\RequestMatcherInterface;
|
||||
use Symfony\Component\Routing\RequestContext;
|
||||
use Symfony\Component\Routing\RequestContextAwareInterface;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Initializes the context from the request and sets request attributes based on a matching route.
|
||||
@ -59,12 +60,31 @@ class RouterListener implements EventSubscriberInterface
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current Request.
|
||||
*
|
||||
* The application should call this method whenever the Request
|
||||
* object changes (entering a Request scope for instance, but
|
||||
* also when leaving a Request scope -- especially when they are
|
||||
* nested).
|
||||
*
|
||||
* @param Request|null $request A Request instance
|
||||
*/
|
||||
public function setRequest(Request $request = null)
|
||||
{
|
||||
if (null !== $request) {
|
||||
$this->context->fromRequest($request);
|
||||
}
|
||||
}
|
||||
|
||||
public function onKernelRequest(GetResponseEvent $event)
|
||||
{
|
||||
$request = $event->getRequest();
|
||||
|
||||
// initialize the context that is also used by the generator (assuming matcher and generator share the same context instance)
|
||||
$this->context->fromRequest($request);
|
||||
// we call setRequest even if most of the time, it has already been done to keep compatibility
|
||||
// with frameworks which do not use the Symfony service container
|
||||
$this->setRequest($request);
|
||||
|
||||
if ($request->attributes->has('_controller')) {
|
||||
// routing is already done
|
||||
|
@ -15,10 +15,6 @@ use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
use Symfony\Component\HttpKernel\Controller\ControllerReference;
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
|
||||
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
/**
|
||||
* Renders a URI that represents a resource fragment.
|
||||
@ -30,11 +26,11 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
*
|
||||
* @see FragmentRendererInterface
|
||||
*/
|
||||
class FragmentHandler implements EventSubscriberInterface
|
||||
class FragmentHandler
|
||||
{
|
||||
private $debug;
|
||||
private $renderers;
|
||||
private $requests;
|
||||
private $request;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
@ -49,7 +45,6 @@ class FragmentHandler implements EventSubscriberInterface
|
||||
$this->addRenderer($renderer);
|
||||
}
|
||||
$this->debug = $debug;
|
||||
$this->requests = array();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -63,23 +58,13 @@ class FragmentHandler implements EventSubscriberInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the Request object.
|
||||
* Sets the current Request.
|
||||
*
|
||||
* @param GetResponseEvent $event A GetResponseEvent instance
|
||||
* @param Request $request The current Request
|
||||
*/
|
||||
public function onKernelRequest(GetResponseEvent $event)
|
||||
public function setRequest(Request $request = null)
|
||||
{
|
||||
array_unshift($this->requests, $event->getRequest());
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the most recent Request object.
|
||||
*
|
||||
* @param FilterResponseEvent $event A FilterResponseEvent instance
|
||||
*/
|
||||
public function onKernelResponse(FilterResponseEvent $event)
|
||||
{
|
||||
array_shift($this->requests);
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -108,7 +93,11 @@ class FragmentHandler implements EventSubscriberInterface
|
||||
throw new \InvalidArgumentException(sprintf('The "%s" renderer does not exist.', $renderer));
|
||||
}
|
||||
|
||||
return $this->deliver($this->renderers[$renderer]->render($uri, $this->requests[0], $options));
|
||||
if (null === $this->request) {
|
||||
throw new \LogicException('Rendering a fragment can only be done when handling a master Request.');
|
||||
}
|
||||
|
||||
return $this->deliver($this->renderers[$renderer]->render($uri, $this->request, $options));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -126,7 +115,7 @@ class FragmentHandler implements EventSubscriberInterface
|
||||
protected function deliver(Response $response)
|
||||
{
|
||||
if (!$response->isSuccessful()) {
|
||||
throw new \RuntimeException(sprintf('Error when rendering "%s" (Status code is %s).', $this->requests[0]->getUri(), $response->getStatusCode()));
|
||||
throw new \RuntimeException(sprintf('Error when rendering "%s" (Status code is %s).', $this->request->getUri(), $response->getStatusCode()));
|
||||
}
|
||||
|
||||
if (!$response instanceof StreamedResponse) {
|
||||
@ -135,12 +124,4 @@ class FragmentHandler implements EventSubscriberInterface
|
||||
|
||||
$response->sendContent();
|
||||
}
|
||||
|
||||
public static function getSubscribedEvents()
|
||||
{
|
||||
return array(
|
||||
KernelEvents::REQUEST => 'onKernelRequest',
|
||||
KernelEvents::RESPONSE => 'onKernelResponse',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\HttpKernel\Tests;
|
||||
namespace Symfony\Component\HttpKernel\Tests\DependencyInjection;
|
||||
|
||||
use Symfony\Component\HttpKernel\HttpKernelInterface;
|
||||
use Symfony\Component\HttpKernel\DependencyInjection\ContainerAwareHttpKernel;
|
||||
@ -54,10 +54,15 @@ class ContainerAwareHttpKernelTest extends \PHPUnit_Framework_TestCase
|
||||
->with($this->equalTo('request'))
|
||||
;
|
||||
$container
|
||||
->expects($this->once())
|
||||
->expects($this->at(1))
|
||||
->method('set')
|
||||
->with($this->equalTo('request'), $this->equalTo($request), $this->equalTo('request'))
|
||||
;
|
||||
$container
|
||||
->expects($this->at(2))
|
||||
->method('set')
|
||||
->with($this->equalTo('request'), $this->equalTo(null), $this->equalTo('request'))
|
||||
;
|
||||
|
||||
$dispatcher = new EventDispatcher();
|
||||
$resolver = $this->getMock('Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface');
|
||||
@ -101,10 +106,15 @@ class ContainerAwareHttpKernelTest extends \PHPUnit_Framework_TestCase
|
||||
->with($this->equalTo('request'))
|
||||
;
|
||||
$container
|
||||
->expects($this->once())
|
||||
->expects($this->at(1))
|
||||
->method('set')
|
||||
->with($this->equalTo('request'), $this->equalTo($request), $this->equalTo('request'))
|
||||
;
|
||||
$container
|
||||
->expects($this->at(2))
|
||||
->method('set')
|
||||
->with($this->equalTo('request'), $this->equalTo(null), $this->equalTo('request'))
|
||||
;
|
||||
|
||||
$dispatcher = new EventDispatcher();
|
||||
$resolver = $this->getMock('Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface');
|
||||
|
@ -17,13 +17,6 @@ use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class FragmentHandlerTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
protected function setUp()
|
||||
{
|
||||
if (!class_exists('Symfony\Component\EventDispatcher\EventDispatcher')) {
|
||||
$this->markTestSkipped('The "EventDispatcher" component is not available');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \InvalidArgumentException
|
||||
*/
|
||||
@ -81,14 +74,7 @@ class FragmentHandlerTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
$handler = new FragmentHandler();
|
||||
$handler->addRenderer($renderer);
|
||||
|
||||
$event = $this->getMockBuilder('Symfony\Component\HttpKernel\Event\GetResponseEvent')->disableOriginalConstructor()->getMock();
|
||||
$event
|
||||
->expects($this->once())
|
||||
->method('getRequest')
|
||||
->will($this->returnValue(Request::create('/')))
|
||||
;
|
||||
$handler->onKernelRequest($event);
|
||||
$handler->setRequest(Request::create('/'));
|
||||
|
||||
return $handler;
|
||||
}
|
||||
|
Reference in New Issue
Block a user