Synchronized Service alternative, backwards compatible #8904

Merged
merged 7 commits into from Sep 8, 2013
Jump to file
+530 −138
View
39 src/Symfony/Bridge/Twig/Tests/Extension/HttpKernelExtensionTest.php
@@ -14,6 +14,8 @@
use Symfony\Bridge\Twig\Extension\HttpKernelExtension;
use Symfony\Bridge\Twig\Tests\TestCase;
use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\Fragment\FragmentHandler;
class HttpKernelExtensionTest extends TestCase
@@ -23,13 +25,30 @@ class HttpKernelExtensionTest extends TestCase
*/
public function testFragmentWithError()
{
- $kernel = $this->getFragmentHandler($this->throwException(new \Exception('foo')));
+ $renderer = $this->getFragmentHandler($this->throwException(new \Exception('foo')));
- $loader = new \Twig_Loader_Array(array('index' => '{{ fragment("foo") }}'));
- $twig = new \Twig_Environment($loader, array('debug' => true, 'cache' => false));
- $twig->addExtension(new HttpKernelExtension($kernel));
+ $this->renderTemplate($renderer);
+ }
+
+ public function testRenderFragment()
+ {
+ $renderer = $this->getFragmentHandler($this->returnValue(new Response('html')));
+
+ $response = $this->renderTemplate($renderer);
- $this->renderTemplate($kernel);
+ $this->assertEquals('html', $response);
+ }
+
+ public function testUnknownFragmentRenderer()
+ {
+ $context = $this->getMockBuilder('Symfony\\Component\\HttpFoundation\\RequestStack')
+ ->disableOriginalConstructor()
+ ->getMock()
+ ;
+ $renderer = new FragmentHandler(array(), false, $context);
+
+ $this->setExpectedException('InvalidArgumentException', 'The "inline" renderer does not exist.');
+ $renderer->render('/foo');
}
protected function getFragmentHandler($return)
@@ -38,8 +57,14 @@ protected function getFragmentHandler($return)
$strategy->expects($this->once())->method('getName')->will($this->returnValue('inline'));
$strategy->expects($this->once())->method('render')->will($return);
- $renderer = new FragmentHandler(array($strategy));
- $renderer->setRequest(Request::create('/'));
+ $context = $this->getMockBuilder('Symfony\\Component\\HttpFoundation\\RequestStack')
+ ->disableOriginalConstructor()
+ ->getMock()
+ ;
+
+ $context->expects($this->any())->method('getCurrentRequest')->will($this->returnValue(Request::create('/')));
+
+ $renderer = new FragmentHandler(array($strategy), false, $context);
return $renderer;
}
View
2 src/Symfony/Bundle/FrameworkBundle/Resources/config/fragment_renderer.xml
@@ -17,7 +17,7 @@
<service id="fragment.handler" class="%fragment.handler.class%">
<argument type="collection" />
<argument>%kernel.debug%</argument>
- <call method="setRequest"><argument type="service" id="request" on-invalid="null" strict="false" /></call>
+ <argument type="service" id="request_stack" />
</service>
<service id="fragment.renderer.inline" class="%fragment.renderer.inline.class%">
View
2 src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml
@@ -94,7 +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>
+ <argument type="service" id="request_stack" />
</service>
</services>
</container>
View
4 src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml
@@ -12,6 +12,7 @@
<parameter key="cache_clearer.class">Symfony\Component\HttpKernel\CacheClearer\ChainCacheClearer</parameter>
<parameter key="file_locator.class">Symfony\Component\HttpKernel\Config\FileLocator</parameter>
<parameter key="uri_signer.class">Symfony\Component\HttpKernel\UriSigner</parameter>
+ <parameter key="request_stack.class">Symfony\Component\HttpFoundation\RequestStack</parameter>
</parameters>
<services>
@@ -23,8 +24,11 @@
<argument type="service" id="event_dispatcher" />
<argument type="service" id="service_container" />
<argument type="service" id="controller_resolver" />
+ <argument type="service" id="request_stack" />
</service>
+ <service id="request_stack" class="%request_stack.class%" />
+
<service id="cache_warmer" class="%cache_warmer.class%">
<argument type="collection" />
</service>
View
2 src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml
@@ -38,7 +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>
+ <argument type="service" id="request_stack" />
</service>
</services>
</container>
View
1 src/Symfony/Component/HttpFoundation/CHANGELOG.md
@@ -4,6 +4,7 @@ CHANGELOG
2.4.0
-----
+ * added RequestStack
* added Request::getEncodings()
* added accessors methods to session handlers
View
103 src/Symfony/Component/HttpFoundation/RequestStack.php
@@ -0,0 +1,103 @@
+<?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\Component\HttpFoundation;
+
+/**
+ * Request stack that controls the lifecycle of requests.
+ *
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ */
+class RequestStack
+{
+ /**
+ * @var Request[]
+ */
+ private $requests = array();
+
+ /**
+ * Pushes a Request on the stack.
+ *
+ * This method should generally not be called directly as the stack
+ * management should be taken care of by the application itself.
+ */
+ public function push(Request $request)
+ {
+ $this->requests[] = $request;
+ }
+
+ /**
+ * Pops the current request from the stack.
+ *
+ * This operation lets the current request go out of scope.
+ *
+ * This method should generally not be called directly as the stack
+ * management should be taken care of by the application itself.
+ *
+ * @return Request
+ */
+ public function pop()
+ {
+ if (!$this->requests) {
+ throw new \LogicException('Unable to pop a Request as the stack is already empty.');
+ }
+
+ return array_pop($this->requests);
+ }
+
+ /**
+ * @return Request|null
+ */
+ public function getCurrentRequest()
+ {
+ return end($this->requests) ?: null;
+ }
+
+ /**
+ * Gets the master Request.
+ *
+ * Be warned that making your code aware of the master request
+ * might make it un-compatible with other features of your framework
+ * like ESI support.
+ *
+ * @return Request|null
+ */
+ public function getMasterRequest()
+ {
+ if (!$this->requests) {
+ return null;
+ }
+
+ return $this->requests[0];
+ }
+
+ /**
+ * Returns the parent request of the current.
+ *
+ * Be warned that making your code aware of the parent request
+ * might make it un-compatible with other features of your framework
+ * like ESI support.
+ *
+ * If current Request is the master request, it returns null.
+ *
+ * @return Request|null
+ */
+ public function getParentRequest()
+ {
+ $pos = count($this->requests) - 2;
+
+ if (!isset($this->requests[$pos])) {
+ return null;
+ }
+
+ return $this->requests[$pos];
+ }
+}
View
5 src/Symfony/Component/HttpKernel/CHANGELOG.md
@@ -1,6 +1,11 @@
CHANGELOG
=========
+2.4.0
+-----
+
+ * added the KernelEvents::FINISH_REQUEST event
+
2.3.0
-----
View
6 src/Symfony/Component/HttpKernel/DependencyInjection/ContainerAwareHttpKernel.php
@@ -12,6 +12,7 @@
namespace Symfony\Component\HttpKernel\DependencyInjection;
use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\HttpKernel;
use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface;
@@ -35,10 +36,11 @@ class ContainerAwareHttpKernel extends HttpKernel
* @param EventDispatcherInterface $dispatcher An EventDispatcherInterface instance
* @param ContainerInterface $container A ContainerInterface instance
* @param ControllerResolverInterface $controllerResolver A ControllerResolverInterface instance
+ * @param RequestStack $requestStack A stack for master/sub requests
*/
- public function __construct(EventDispatcherInterface $dispatcher, ContainerInterface $container, ControllerResolverInterface $controllerResolver)
+ public function __construct(EventDispatcherInterface $dispatcher, ContainerInterface $container, ControllerResolverInterface $controllerResolver, RequestStack $requestStack = null)
{
- parent::__construct($dispatcher, $controllerResolver);
+ parent::__construct($dispatcher, $controllerResolver, $requestStack);
$this->container = $container;
View
21 src/Symfony/Component/HttpKernel/Event/FinishRequestEvent.php
@@ -0,0 +1,21 @@
+<?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\Component\HttpKernel\Event;
+
+/**
+ * Triggered whenever a request is fully processed.
+ *
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ */
+class FinishRequestEvent extends KernelEvent
+{
+}
View
63 src/Symfony/Component/HttpKernel/EventListener/LocaleListener.php
@@ -12,55 +12,100 @@
namespace Symfony\Component\HttpKernel\EventListener;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
+use Symfony\Component\HttpKernel\Event\FinishRequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;
+use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\RequestContextAwareInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Initializes the locale based on the current request.
*
+ * This listener works in 2 modes:
+ *
+ * * 2.3 compatibility mode where you must call setRequest whenever the Request changes.
+ * * 2.4+ mode where you must pass a RequestStack instance in the constructor.
+ *
* @author Fabien Potencier <fabien@symfony.com>
*/
class LocaleListener implements EventSubscriberInterface
{
private $router;
private $defaultLocale;
+ private $requestStack;
- public function __construct($defaultLocale = 'en', RequestContextAwareInterface $router = null)
+ /**
+ * RequestStack will become required in 3.0.
+ */
+ public function __construct($defaultLocale = 'en', RequestContextAwareInterface $router = null, RequestStack $requestStack = null)
{
$this->defaultLocale = $defaultLocale;
+ $this->requestStack = $requestStack;
$this->router = $router;
}
+ /**
+ * Sets the current Request.
+ *
+ * This method was used to synchronize the Request, but as the HttpKernel
+ * is doing that automatically now, you should never be called it directly.
+ * It is kept public for BC with the 2.3 version.
+ *
+ * @param Request|null $request A Request instance
+ *
+ * @deprecated Deprecated since version 2.4, to be removed in 3.0.
+ */
public function setRequest(Request $request = null)
{
if (null === $request) {
return;
}
- if ($locale = $request->attributes->get('_locale')) {
- $request->setLocale($locale);
- }
-
- if (null !== $this->router) {
- $this->router->getContext()->setParameter('_locale', $request->getLocale());
- }
+ $this->setLocale($request);
+ $this->setRouterContext($request);
}
public function onKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
$request->setDefaultLocale($this->defaultLocale);
- $this->setRequest($request);
+ $this->setLocale($request);
+ $this->setRouterContext($request);
+ }
+
+ public function onKernelFinishRequest(FinishRequestEvent $event)
+ {
+ if (null === $this->requestStack) {
+ throw new \LogicException('You must pass a RequestStack.');
@Tobion
Symfony member
Tobion added a note Sep 17, 2013

this looks like a bc break. when a user uses this listener without passing a request stack, he will automatically get this exception because the event is subscribed automatically as well by the listener.

@Tobion
Symfony member
Tobion added a note Sep 17, 2013
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ }
+
+ if (null !== $parentRequest = $this->requestStack->getParentRequest()) {
+ $this->setRouterContext($parentRequest);
+ }
+ }
+
+ private function setLocale(Request $request)
+ {
+ if ($locale = $request->attributes->get('_locale')) {
+ $request->setLocale($locale);
+ }
+ }
+
+ private function setRouterContext(Request $request)
+ {
+ if (null !== $this->router) {
+ $this->router->getContext()->setParameter('_locale', $request->getLocale());
+ }
}
public static function getSubscribedEvents()
{
return array(
// must be registered after the Router to have access to the _locale
KernelEvents::REQUEST => array(array('onKernelRequest', 16)),
+ KernelEvents::FINISH_REQUEST => array(array('onKernelFinishRequest', 0)),
);
}
}
View
37 src/Symfony/Component/HttpKernel/EventListener/RouterListener.php
@@ -13,9 +13,11 @@
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
+use Symfony\Component\HttpKernel\Event\FinishRequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Routing\Exception\MethodNotAllowedException;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
use Symfony\Component\Routing\Matcher\UrlMatcherInterface;
@@ -28,6 +30,11 @@
/**
* Initializes the context from the request and sets request attributes based on a matching route.
*
+ * This listener works in 2 modes:
+ *
+ * * 2.3 compatibility mode where you must call setRequest whenever the Request changes.
+ * * 2.4+ mode where you must pass a RequestStack instance in the constructor.
+ *
* @author Fabien Potencier <fabien@symfony.com>
*/
class RouterListener implements EventSubscriberInterface
@@ -36,17 +43,20 @@ class RouterListener implements EventSubscriberInterface
private $context;
private $logger;
private $request;
+ private $requestStack;
/**
* Constructor.
*
+ * RequestStack will become required in 3.0.
+ *
* @param UrlMatcherInterface|RequestMatcherInterface $matcher The Url or Request matcher
* @param RequestContext|null $context The RequestContext (can be null when $matcher implements RequestContextAwareInterface)
* @param LoggerInterface|null $logger The logger
*
* @throws \InvalidArgumentException
*/
- public function __construct($matcher, RequestContext $context = null, LoggerInterface $logger = null)
+ public function __construct($matcher, RequestContext $context = null, LoggerInterface $logger = null, RequestStack $requestStack = null)
{
if (!$matcher instanceof UrlMatcherInterface && !$matcher instanceof RequestMatcherInterface) {
throw new \InvalidArgumentException('Matcher must either implement UrlMatcherInterface or RequestMatcherInterface.');
@@ -58,18 +68,20 @@ public function __construct($matcher, RequestContext $context = null, LoggerInte
$this->matcher = $matcher;
$this->context = $context ?: $matcher->getContext();
+ $this->requestStack = $requestStack;
$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).
+ * This method was used to synchronize the Request, but as the HttpKernel
+ * is doing that automatically now, you should never be called it directly.
@ghost
ghost added a note Sep 12, 2013

Typo: This should read "* is doing that automatically now, you should never call it directly"

@fabpot
Symfony member
fabpot added a note Sep 12, 2013

fixed now, thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ * It is kept public for BC with the 2.3 version.
*
* @param Request|null $request A Request instance
+ *
+ * @deprecated Deprecated since version 2.4, to be moved to a private function in 3.0.
*/
public function setRequest(Request $request = null)
{
@@ -79,14 +91,26 @@ public function setRequest(Request $request = null)
$this->request = $request;
}
+ public function onKernelFinishRequest(FinishRequestEvent $event)
+ {
+ if (null === $this->requestStack) {
+ throw new \LogicException('You must pass a RequestStack.');
+ }
+
+ $this->setRequest($this->requestStack->getParentRequest());
+ }
+
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)
// 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);
+ // when we have a RequestStack, no need to do it
+ if (null !== $this->requestStack) {
+ $this->setRequest($request);
+ }
if ($request->attributes->has('_controller')) {
// routing is already done
@@ -139,6 +163,7 @@ public static function getSubscribedEvents()
{
return array(
KernelEvents::REQUEST => array(array('onKernelRequest', 32)),
+ KernelEvents::FINISH_REQUEST => array(array('onKernelFinishRequest', 0)),
);
}
}
View
33 src/Symfony/Component/HttpKernel/Fragment/FragmentHandler.php
@@ -14,6 +14,7 @@
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\StreamedResponse;
+use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\Controller\ControllerReference;
/**
@@ -22,6 +23,11 @@
* This class handles the rendering of resource fragments that are included into
* a main resource. The handling of the rendering is managed by specialized renderers.
*
+ * This listener works in 2 modes:
+ *
+ * * 2.3 compatibility mode where you must call setRequest whenever the Request changes.
+ * * 2.4+ mode where you must pass a RequestStack instance in the constructor.
+ *
* @author Fabien Potencier <fabien@symfony.com>
*
* @see FragmentRendererInterface
@@ -31,15 +37,19 @@ class FragmentHandler
private $debug;
private $renderers;
private $request;
+ private $requestStack;
/**
* Constructor.
*
+ * RequestStack will become required in 3.0.
+ *
* @param FragmentRendererInterface[] $renderers An array of FragmentRendererInterface instances
* @param Boolean $debug Whether the debug mode is enabled or not
*/
- public function __construct(array $renderers = array(), $debug = false)
+ public function __construct(array $renderers = array(), $debug = false, RequestStack $requestStack = null)
{
+ $this->requestStack = $requestStack;
$this->renderers = array();
foreach ($renderers as $renderer) {
$this->addRenderer($renderer);
@@ -60,7 +70,13 @@ public function addRenderer(FragmentRendererInterface $renderer)
/**
* Sets the current Request.
*
- * @param Request $request The current Request
+ * This method was used to synchronize the Request, but as the HttpKernel
+ * is doing that automatically now, you should never be called it directly.
+ * It is kept public for BC with the 2.3 version.
+ *
+ * @param Request|null $request A Request instance
+ *
+ * @deprecated Deprecated since version 2.4, to be removed in 3.0.
*/
public function setRequest(Request $request = null)
{
@@ -93,11 +109,11 @@ public function render($uri, $renderer = 'inline', array $options = array())
throw new \InvalidArgumentException(sprintf('The "%s" renderer does not exist.', $renderer));
}
- if (null === $this->request) {
- throw new \LogicException('Rendering a fragment can only be done when handling a master Request.');
+ if (!$request = $this->getRequest()) {
+ throw new \LogicException('Rendering a fragment can only be done when handling a Request.');
}
- return $this->deliver($this->renderers[$renderer]->render($uri, $this->request, $options));
+ return $this->deliver($this->renderers[$renderer]->render($uri, $request, $options));
}
/**
@@ -115,7 +131,7 @@ public function render($uri, $renderer = 'inline', array $options = array())
protected function deliver(Response $response)
{
if (!$response->isSuccessful()) {
- throw new \RuntimeException(sprintf('Error when rendering "%s" (Status code is %s).', $this->request->getUri(), $response->getStatusCode()));
+ throw new \RuntimeException(sprintf('Error when rendering "%s" (Status code is %s).', $this->getRequest()->getUri(), $response->getStatusCode()));
}
if (!$response instanceof StreamedResponse) {
@@ -124,4 +140,9 @@ protected function deliver(Response $response)
$response->sendContent();
}
+
+ private function getRequest()
+ {
+ return $this->requestStack ? $this->requestStack->getCurrentRequest() : $this->request;
+ }
}
View
35 src/Symfony/Component/HttpKernel/HttpKernel.php
@@ -16,11 +16,13 @@
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
+use Symfony\Component\HttpKernel\Event\FinishRequestEvent;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\Event\PostResponseEvent;
use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
@@ -35,19 +37,22 @@ class HttpKernel implements HttpKernelInterface, TerminableInterface
{
protected $dispatcher;
protected $resolver;
+ protected $requestStack;
/**
* Constructor
*
- * @param EventDispatcherInterface $dispatcher An EventDispatcherInterface instance
- * @param ControllerResolverInterface $resolver A ControllerResolverInterface instance
+ * @param EventDispatcherInterface $dispatcher An EventDispatcherInterface instance
+ * @param ControllerResolverInterface $resolver A ControllerResolverInterface instance
+ * @param RequestStack $requestStack A stack for master/sub requests
*
* @api
*/
- public function __construct(EventDispatcherInterface $dispatcher, ControllerResolverInterface $resolver)
+ public function __construct(EventDispatcherInterface $dispatcher, ControllerResolverInterface $resolver, RequestStack $requestStack = null)
{
$this->dispatcher = $dispatcher;
$this->resolver = $resolver;
+ $this->requestStack = $requestStack ?: new RequestStack();
}
/**
@@ -61,6 +66,8 @@ public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQ
return $this->handleRaw($request, $type);
} catch (\Exception $e) {
if (false === $catch) {
+ $this->finishRequest($request, $type);
+
throw $e;
}
@@ -93,6 +100,8 @@ public function terminate(Request $request, Response $response)
*/
private function handleRaw(Request $request, $type = self::MASTER_REQUEST)
{
+ $this->requestStack->push($request);
+
// request
$event = new GetResponseEvent($this, $request, $type);
$this->dispatcher->dispatch(KernelEvents::REQUEST, $event);
@@ -156,10 +165,28 @@ private function filterResponse(Response $response, Request $request, $type)
$this->dispatcher->dispatch(KernelEvents::RESPONSE, $event);
+ $this->finishRequest($request, $type);
+
return $event->getResponse();
}
/**
+ * Publishes the finish request event, then pop the request from the stack.
+ *
+ * Note that the order of the operations is important here, otherwise
+ * operations such as {@link RequestStack::getParentRequest()} can lead to
+ * weird results.
+ *
+ * @param Request $request
+ * @param int $type
+ */
+ private function finishRequest(Request $request, $type)
+ {
+ $this->dispatcher->dispatch(KernelEvents::FINISH_REQUEST, new FinishRequestEvent($this, $request, $type));
+ $this->requestStack->pop();
+ }
+
+ /**
* Handles an exception by trying to convert it to a Response.
*
* @param \Exception $e An \Exception instance
@@ -179,6 +206,8 @@ private function handleException(\Exception $e, $request, $type)
$e = $event->getException();
if (!$event->hasResponse()) {
+ $this->finishRequest($request, $type);
+
throw $e;
}
View
10 src/Symfony/Component/HttpKernel/KernelEvents.php
@@ -102,4 +102,14 @@
* @var string
*/
const TERMINATE = 'kernel.terminate';
+
+ /**
+ * The REQUEST_FINISHED event occurs when a response was generated for a request.
+ *
+ * This event allows you to reset the global and environmental state of
+ * the application, when it was changed during the request.
+ *
+ * @var string
+ */
+ const FINISH_REQUEST = 'kernel.finish_request';
}
View
172 src/Symfony/Component/HttpKernel/Tests/DependencyInjection/ContainerAwareHttpKernelTest.php
@@ -13,6 +13,7 @@
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\DependencyInjection\ContainerAwareHttpKernel;
+use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\EventDispatcher\EventDispatcher;
@@ -26,59 +27,49 @@ public function testHandle($type)
{
$request = new Request();
$expected = new Response();
+ $controller = function() use ($expected) {
+ return $expected;
+ };
$container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface');
- $container
- ->expects($this->once())
- ->method('enterScope')
- ->with($this->equalTo('request'))
- ;
- $container
- ->expects($this->once())
- ->method('leaveScope')
- ->with($this->equalTo('request'))
- ;
- $container
- ->expects($this->at(0))
- ->method('hasScope')
- ->with($this->equalTo('request'))
- ->will($this->returnValue(false));
- $container
- ->expects($this->at(1))
- ->method('addScope')
- ->with($this->isInstanceOf('Symfony\Component\DependencyInjection\Scope'));
- // enterScope()
- $container
- ->expects($this->at(3))
- ->method('set')
- ->with($this->equalTo('request'), $this->equalTo($request), $this->equalTo('request'))
- ;
- $container
- ->expects($this->at(4))
- ->method('set')
- ->with($this->equalTo('request'), $this->equalTo(null), $this->equalTo('request'))
+ $this
+ ->expectsEnterScopeOnce($container)
+ ->expectsLeaveScopeOnce($container)
+ ->expectsSetRequestWithAt($container, $request, 3)
+ ->expectsSetRequestWithAt($container, null, 4)
;
$dispatcher = new EventDispatcher();
- $resolver = $this->getMock('Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface');
- $kernel = new ContainerAwareHttpKernel($dispatcher, $container, $resolver);
+ $resolver = $this->getResolverMockFor($controller, $request);
+ $stack = new RequestStack();
+ $kernel = new ContainerAwareHttpKernel($dispatcher, $container, $resolver, $stack);
- $controller = function () use ($expected) {
+ $actual = $kernel->handle($request, $type);
+
+ $this->assertSame($expected, $actual, '->handle() returns the response');
+ }
+
+ /**
+ * @dataProvider getProviderTypes
+ */
+ public function testVerifyRequestStackPushPopDuringHandle($type)
+ {
+ $request = new Request();
+ $expected = new Response();
+ $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()));
+ $stack = $this->getMock('Symfony\Component\HttpFoundation\RequestStack', array('push', 'pop'));
+ $stack->expects($this->at(0))->method('push')->with($this->equalTo($request));
+ $stack->expects($this->at(1))->method('pop');
- $actual = $kernel->handle($request, $type);
+ $container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface');
+ $dispatcher = new EventDispatcher();
+ $resolver = $this->getResolverMockFor($controller, $request);
+ $kernel = new ContainerAwareHttpKernel($dispatcher, $container, $resolver, $stack);
- $this->assertSame($expected, $actual, '->handle() returns the response');
+ $kernel->handle($request, $type);
}
/**
@@ -88,51 +79,23 @@ public function testHandleRestoresThePreviousRequestOnException($type)
{
$request = new Request();
$expected = new \Exception();
+ $controller = function() use ($expected) {
+ throw $expected;
+ };
$container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface');
- $container
- ->expects($this->once())
- ->method('enterScope')
- ->with($this->equalTo('request'))
- ;
- $container
- ->expects($this->once())
- ->method('leaveScope')
- ->with($this->equalTo('request'))
- ;
- $container
- ->expects($this->at(0))
- ->method('hasScope')
- ->with($this->equalTo('request'))
- ->will($this->returnValue(true));
- // enterScope()
- $container
- ->expects($this->at(2))
- ->method('set')
- ->with($this->equalTo('request'), $this->equalTo($request), $this->equalTo('request'))
- ;
- $container
- ->expects($this->at(3))
- ->method('set')
- ->with($this->equalTo('request'), $this->equalTo(null), $this->equalTo('request'))
+ $this
+ ->expectsEnterScopeOnce($container)
+ ->expectsLeaveScopeOnce($container)
+ ->expectsSetRequestWithAt($container, $request, 3)
+ ->expectsSetRequestWithAt($container, null, 4)
;
$dispatcher = new EventDispatcher();
$resolver = $this->getMock('Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface');
- $kernel = new ContainerAwareHttpKernel($dispatcher, $container, $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()));
+ $resolver = $this->getResolverMockFor($controller, $request);
+ $stack = new RequestStack();
+ $kernel = new ContainerAwareHttpKernel($dispatcher, $container, $resolver, $stack);
try {
$kernel->handle($request, $type);
@@ -151,4 +114,51 @@ public function getProviderTypes()
array(HttpKernelInterface::SUB_REQUEST),
);
}
+
+ private function getResolverMockFor($controller, $request)
+ {
+ $resolver = $this->getMock('Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface');
+ $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()));
+
+ return $resolver;
+ }
+
+ private function expectsSetRequestWithAt($container, $with, $at)
+ {
+ $container
+ ->expects($this->at($at))
+ ->method('set')
+ ->with($this->equalTo('request'), $this->equalTo($with), $this->equalTo('request'))
+ ;
+ return $this;
+ }
+
+ private function expectsEnterScopeOnce($container)
+ {
+ $container
+ ->expects($this->once())
+ ->method('enterScope')
+ ->with($this->equalTo('request'))
+ ;
+
+ return $this;
+ }
+
+ private function expectsLeaveScopeOnce($container)
+ {
+ $container
+ ->expects($this->once())
+ ->method('leaveScope')
+ ->with($this->equalTo('request'))
+ ;
+
+ return $this;
+ }
}
View
42 src/Symfony/Component/HttpKernel/Tests/EventListener/LocaleListenerTest.php
@@ -11,16 +11,24 @@
namespace Symfony\Component\HttpKernel\Tests\EventListener;
-use Symfony\Component\HttpKernel\EventListener\LocaleListener;
+use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\EventListener\LocaleListener;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
class LocaleListenerTest extends \PHPUnit_Framework_TestCase
{
+ private $requestStack;
+
+ protected function setUp()
+ {
+ $this->requestStack = $this->getMock('Symfony\Component\HttpFoundation\RequestStack', array(), array(), '', false);
+ }
+
public function testDefaultLocaleWithoutSession()
{
- $listener = new LocaleListener('fr');
+ $listener = new LocaleListener('fr', null, $this->requestStack);
$event = $this->getEvent($request = Request::create('/'));
$listener->onKernelRequest($event);
@@ -34,7 +42,7 @@ public function testLocaleFromRequestAttribute()
$request->cookies->set('foo', 'value');
$request->attributes->set('_locale', 'es');
- $listener = new LocaleListener('fr');
+ $listener = new LocaleListener('fr', null, $this->requestStack);
$event = $this->getEvent($request);
$listener->onKernelRequest($event);
@@ -53,15 +61,39 @@ public function testLocaleSetForRoutingContext()
$request = Request::create('/');
$request->attributes->set('_locale', 'es');
- $listener = new LocaleListener('fr', $router);
+ $listener = new LocaleListener('fr', $router, $this->requestStack);
$listener->onKernelRequest($this->getEvent($request));
}
+ public function testRouterResetWithParentRequestOnKernelFinishRequest()
+ {
+ if (!class_exists('Symfony\Component\Routing\Router')) {
+ $this->markTestSkipped('The "Routing" component is not available');
+ }
+
+ // the request context is updated
+ $context = $this->getMock('Symfony\Component\Routing\RequestContext');
+ $context->expects($this->once())->method('setParameter')->with('_locale', 'es');
+
+ $router = $this->getMock('Symfony\Component\Routing\Router', array('getContext'), array(), '', false);
+ $router->expects($this->once())->method('getContext')->will($this->returnValue($context));
+
+ $parentRequest = Request::create('/');
+ $parentRequest->setLocale('es');
+
+ $this->requestStack->expects($this->once())->method('getParentRequest')->will($this->returnValue($parentRequest));
+
+ $event = $this->getMock('Symfony\Component\HttpKernel\Event\FinishRequestEvent', array(), array(), '', false);
+
+ $listener = new LocaleListener('fr', $router, $this->requestStack);
+ $listener->onKernelFinishRequest($event);
+ }
+
public function testRequestLocaleIsNotOverridden()
{
$request = Request::create('/');
$request->setLocale('de');
- $listener = new LocaleListener('fr');
+ $listener = new LocaleListener('fr', null, $this->requestStack);
$event = $this->getEvent($request);
$listener->onKernelRequest($event);
View
18 src/Symfony/Component/HttpKernel/Tests/EventListener/RouterListenerTest.php
@@ -11,14 +11,22 @@
namespace Symfony\Component\HttpKernel\Tests\EventListener;
-use Symfony\Component\HttpKernel\EventListener\RouterListener;
+use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\EventListener\RouterListener;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\Routing\RequestContext;
class RouterListenerTest extends \PHPUnit_Framework_TestCase
{
+ private $requestStack;
+
+ public function setUp()
+ {
+ $this->requestStack = $this->getMock('Symfony\Component\HttpFoundation\RequestStack', array(), array(), '', false);
+ }
+
/**
* @dataProvider getPortData
*/
@@ -34,7 +42,7 @@ public function testPort($defaultHttpPort, $defaultHttpsPort, $uri, $expectedHtt
->method('getContext')
->will($this->returnValue($context));
- $listener = new RouterListener($urlMatcher);
+ $listener = new RouterListener($urlMatcher, null, null, $this->requestStack);
$event = $this->createGetResponseEventForUri($uri);
$listener->onKernelRequest($event);
@@ -72,7 +80,7 @@ private function createGetResponseEventForUri($uri)
*/
public function testInvalidMatcher()
{
- new RouterListener(new \stdClass());
+ new RouterListener(new \stdClass(), null, null, $this->requestStack);
}
public function testRequestMatcher()
@@ -87,7 +95,7 @@ public function testRequestMatcher()
->with($this->isInstanceOf('Symfony\Component\HttpFoundation\Request'))
->will($this->returnValue(array()));
- $listener = new RouterListener($requestMatcher, new RequestContext());
+ $listener = new RouterListener($requestMatcher, new RequestContext(), null, $this->requestStack);
$listener->onKernelRequest($event);
}
@@ -108,7 +116,7 @@ public function testSubRequestWithDifferentMethod()
->method('getContext')
->will($this->returnValue($context));
- $listener = new RouterListener($requestMatcher, new RequestContext());
+ $listener = new RouterListener($requestMatcher, new RequestContext(), null, $this->requestStack);
$listener->onKernelRequest($event);
// sub-request with another HTTP method
View
20 src/Symfony/Component/HttpKernel/Tests/Fragment/FragmentHandlerTest.php
@@ -17,12 +17,27 @@
class FragmentHandlerTest extends \PHPUnit_Framework_TestCase
{
+ private $requestStack;
+
+ public function setUp()
+ {
+ $this->requestStack = $this->getMockBuilder('Symfony\\Component\\HttpFoundation\\RequestStack')
+ ->disableOriginalConstructor()
+ ->getMock()
+ ;
+ $this->requestStack
+ ->expects($this->any())
+ ->method('getCurrentRequest')
+ ->will($this->returnValue(Request::create('/')))
+ ;
+ }
+
/**
* @expectedException \InvalidArgumentException
*/
public function testRenderWhenRendererDoesNotExist()
{
- $handler = new FragmentHandler();
+ $handler = new FragmentHandler(array(), null, $this->requestStack);
$handler->render('/', 'foo');
}
@@ -72,9 +87,8 @@ protected function getHandler($returnValue, $arguments = array())
call_user_func_array(array($e, 'with'), $arguments);
}
- $handler = new FragmentHandler();
+ $handler = new FragmentHandler(array(), null, $this->requestStack);
$handler->addRenderer($renderer);
- $handler->setRequest(Request::create('/'));
return $handler;
}
View
14 src/Symfony/Component/HttpKernel/Tests/HttpKernelTest.php
@@ -238,6 +238,20 @@ public function testTerminate()
$this->assertEquals($response, $capturedResponse);
}
+ public function testVerifyRequestStackPushPopDuringHandle()
+ {
+ $request = new Request();
+
+ $stack = $this->getMock('Symfony\Component\HttpFoundation\RequestStack', array('push', 'pop'));
+ $stack->expects($this->at(0))->method('push')->with($this->equalTo($request));
+ $stack->expects($this->at(1))->method('pop');
+
+ $dispatcher = new EventDispatcher();
+ $kernel = new HttpKernel($dispatcher, $this->getResolver(), $stack);
+
+ $kernel->handle($request, HttpKernelInterface::MASTER_REQUEST);
+ }
+
protected function getResolver($controller = null)
{
if (null === $controller) {
View
2 src/Symfony/Component/HttpKernel/composer.json
@@ -18,7 +18,7 @@
"require": {
"php": ">=5.3.3",
"symfony/event-dispatcher": "~2.1",
- "symfony/http-foundation": "~2.2",
+ "symfony/http-foundation": "~2.4",
"symfony/debug": "~2.3",
"psr/log": "~1.0"
},
View
19 src/Symfony/Component/Security/Http/Firewall.php
@@ -13,6 +13,7 @@
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
+use Symfony\Component\HttpKernel\Event\FinishRequestEvent;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
@@ -30,6 +31,7 @@ class Firewall implements EventSubscriberInterface
{
private $map;
private $dispatcher;
+ private $exceptionListeners;
/**
* Constructor.
@@ -41,6 +43,7 @@ public function __construct(FirewallMapInterface $map, EventDispatcherInterface
{
$this->map = $map;
$this->dispatcher = $dispatcher;
+ $this->exceptionListeners = new \SplObjectStorage();
}
/**
@@ -57,6 +60,7 @@ public function onKernelRequest(GetResponseEvent $event)
// register listeners for this firewall
list($listeners, $exception) = $this->map->getListeners($event->getRequest());
if (null !== $exception) {
+ $this->exceptionListeners[$event->getRequest()] = $exception;
$exception->register($this->dispatcher);
}
@@ -70,8 +74,21 @@ public function onKernelRequest(GetResponseEvent $event)
}
}
+ public function onKernelFinishRequest(FinishRequestEvent $event)
+ {
+ $request = $event->getRequest();
+
+ if (isset($this->exceptionListeners[$request])) {
+ $this->exceptionListeners[$request]->unregister($this->dispatcher);
+ unset($this->exceptionListeners[$request]);
+ }
+ }
+
public static function getSubscribedEvents()
{
- return array(KernelEvents::REQUEST => array('onKernelRequest', 8));
+ return array(
+ KernelEvents::REQUEST => array('onKernelRequest', 8),
+ KernelEvents::FINISH_REQUEST => 'onKernelFinishRequest',
+ );
}
}
View
14 src/Symfony/Component/Security/Http/Firewall/ExceptionListener.php
@@ -70,16 +70,22 @@ public function register(EventDispatcherInterface $dispatcher)
}
/**
+ * Unregisters the dispatcher.
+ *
+ * @param EventDispatcherInterface $dispatcher An EventDispatcherInterface instance
+ */
+ public function unregister(EventDispatcherInterface $dispatcher)
+ {
+ $dispatcher->removeListener(KernelEvents::EXCEPTION, array($this, 'onKernelException'));
+ }
+
+ /**
* Handles security related exceptions.
*
* @param GetResponseForExceptionEvent $event An GetResponseForExceptionEvent instance
*/
public function onKernelException(GetResponseForExceptionEvent $event)
{
- // we need to remove ourselves as the exception listener can be
- // different depending on the Request
- $event->getDispatcher()->removeListener(KernelEvents::EXCEPTION, array($this, 'onKernelException'));
-
$exception = $event->getException();
$request = $event->getRequest();
View
4 src/Symfony/Component/Security/composer.json
@@ -18,8 +18,8 @@
"require": {
"php": ">=5.3.3",
"symfony/event-dispatcher": "~2.1",
- "symfony/http-foundation": "~2.1",
- "symfony/http-kernel": "~2.1"
+ "symfony/http-foundation": "~2.4",
@Tobion
Symfony member
Tobion added a note Sep 17, 2013

the security itself does not require http-foundation 2.4. the changes here did not use a http-foundation 2.4 feature. it only requires http-kernel 2.4 (which itself requires foundation 2.4. anyway, but the security doesn't need it).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ "symfony/http-kernel": "~2.4"
},
"require-dev": {
"symfony/form": "~2.0",