diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/HttpKernelExtensionTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/HttpKernelExtensionTest.php index ce22481921f5..8ce564265ec9 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/HttpKernelExtensionTest.php +++ b/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; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/fragment_renderer.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/fragment_renderer.xml index 4773339906a4..a1beee30a1fb 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/fragment_renderer.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/fragment_renderer.xml @@ -17,7 +17,7 @@ %kernel.debug% - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml index 9e21db451915..6b2f7c968869 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml @@ -94,7 +94,7 @@ - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml index 674e28f1c98a..608bf42d5253 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml @@ -12,6 +12,7 @@ Symfony\Component\HttpKernel\CacheClearer\ChainCacheClearer Symfony\Component\HttpKernel\Config\FileLocator Symfony\Component\HttpKernel\UriSigner + Symfony\Component\HttpFoundation\RequestStack @@ -23,8 +24,11 @@ + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml index 177821a5afb2..6c1dd73e2e61 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml @@ -38,7 +38,7 @@ %kernel.default_locale% - + diff --git a/src/Symfony/Component/HttpFoundation/CHANGELOG.md b/src/Symfony/Component/HttpFoundation/CHANGELOG.md index 954b66ac43c0..061d47e7eec9 100644 --- a/src/Symfony/Component/HttpFoundation/CHANGELOG.md +++ b/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 diff --git a/src/Symfony/Component/HttpFoundation/RequestStack.php b/src/Symfony/Component/HttpFoundation/RequestStack.php new file mode 100644 index 000000000000..71bfd106991f --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/RequestStack.php @@ -0,0 +1,103 @@ + + * + * 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 + */ +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]; + } +} diff --git a/src/Symfony/Component/HttpKernel/CHANGELOG.md b/src/Symfony/Component/HttpKernel/CHANGELOG.md index c06dd3fa57e0..b36e9358ff13 100644 --- a/src/Symfony/Component/HttpKernel/CHANGELOG.md +++ b/src/Symfony/Component/HttpKernel/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +2.4.0 +----- + + * added the KernelEvents::FINISH_REQUEST event + 2.3.0 ----- diff --git a/src/Symfony/Component/HttpKernel/DependencyInjection/ContainerAwareHttpKernel.php b/src/Symfony/Component/HttpKernel/DependencyInjection/ContainerAwareHttpKernel.php index c9b8a211d429..69e7937d18c1 100644 --- a/src/Symfony/Component/HttpKernel/DependencyInjection/ContainerAwareHttpKernel.php +++ b/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; diff --git a/src/Symfony/Component/HttpKernel/Event/FinishRequestEvent.php b/src/Symfony/Component/HttpKernel/Event/FinishRequestEvent.php new file mode 100644 index 000000000000..ee724843cd84 --- /dev/null +++ b/src/Symfony/Component/HttpKernel/Event/FinishRequestEvent.php @@ -0,0 +1,21 @@ + + * + * 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 + */ +class FinishRequestEvent extends KernelEvent +{ +} diff --git a/src/Symfony/Component/HttpKernel/EventListener/LocaleListener.php b/src/Symfony/Component/HttpKernel/EventListener/LocaleListener.php index 0b864c02f2bc..7cab6aa39b56 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/LocaleListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/LocaleListener.php @@ -12,7 +12,9 @@ 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; @@ -20,32 +22,48 @@ /** * 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 */ 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) @@ -53,7 +71,33 @@ 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.'); + } + + 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() @@ -61,6 +105,7 @@ 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)), ); } } diff --git a/src/Symfony/Component/HttpKernel/EventListener/RouterListener.php b/src/Symfony/Component/HttpKernel/EventListener/RouterListener.php index 777fd11bf40a..d122388b4b08 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/RouterListener.php +++ b/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 */ 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. + * 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,6 +91,15 @@ 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(); @@ -86,7 +107,10 @@ public function onKernelRequest(GetResponseEvent $event) // 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)), ); } } diff --git a/src/Symfony/Component/HttpKernel/Fragment/FragmentHandler.php b/src/Symfony/Component/HttpKernel/Fragment/FragmentHandler.php index af9b9ba98b74..05b6a086e970 100644 --- a/src/Symfony/Component/HttpKernel/Fragment/FragmentHandler.php +++ b/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 * * @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; + } } diff --git a/src/Symfony/Component/HttpKernel/HttpKernel.php b/src/Symfony/Component/HttpKernel/HttpKernel.php index 837a16ff370e..0be8e1b4dbb7 100644 --- a/src/Symfony/Component/HttpKernel/HttpKernel.php +++ b/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,9 +165,27 @@ 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. * @@ -179,6 +206,8 @@ private function handleException(\Exception $e, $request, $type) $e = $event->getException(); if (!$event->hasResponse()) { + $this->finishRequest($request, $type); + throw $e; } diff --git a/src/Symfony/Component/HttpKernel/KernelEvents.php b/src/Symfony/Component/HttpKernel/KernelEvents.php index fce48ac3a6ed..5e6ebcb8d992 100644 --- a/src/Symfony/Component/HttpKernel/KernelEvents.php +++ b/src/Symfony/Component/HttpKernel/KernelEvents.php @@ -102,4 +102,14 @@ final class KernelEvents * @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'; } diff --git a/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/ContainerAwareHttpKernelTest.php b/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/ContainerAwareHttpKernelTest.php index 28901dafdd64..2c8a6a28ec14 100644 --- a/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/ContainerAwareHttpKernelTest.php +++ b/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; + } } diff --git a/src/Symfony/Component/HttpKernel/Tests/EventListener/LocaleListenerTest.php b/src/Symfony/Component/HttpKernel/Tests/EventListener/LocaleListenerTest.php index 36859baa16b0..d128753ffab0 100644 --- a/src/Symfony/Component/HttpKernel/Tests/EventListener/LocaleListenerTest.php +++ b/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); diff --git a/src/Symfony/Component/HttpKernel/Tests/EventListener/RouterListenerTest.php b/src/Symfony/Component/HttpKernel/Tests/EventListener/RouterListenerTest.php index 66fe6bd55d50..ac742b35e08c 100644 --- a/src/Symfony/Component/HttpKernel/Tests/EventListener/RouterListenerTest.php +++ b/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 diff --git a/src/Symfony/Component/HttpKernel/Tests/Fragment/FragmentHandlerTest.php b/src/Symfony/Component/HttpKernel/Tests/Fragment/FragmentHandlerTest.php index cec8ae98403a..dbf6b20b424e 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Fragment/FragmentHandlerTest.php +++ b/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; } diff --git a/src/Symfony/Component/HttpKernel/Tests/HttpKernelTest.php b/src/Symfony/Component/HttpKernel/Tests/HttpKernelTest.php index 1bfa0e5fa822..2f69a9457547 100644 --- a/src/Symfony/Component/HttpKernel/Tests/HttpKernelTest.php +++ b/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) { diff --git a/src/Symfony/Component/HttpKernel/composer.json b/src/Symfony/Component/HttpKernel/composer.json index 0e8aac54c6af..a09b0013271c 100644 --- a/src/Symfony/Component/HttpKernel/composer.json +++ b/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" }, diff --git a/src/Symfony/Component/Security/Http/Firewall.php b/src/Symfony/Component/Security/Http/Firewall.php index 36df81a80e62..5a1e9d564749 100644 --- a/src/Symfony/Component/Security/Http/Firewall.php +++ b/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', + ); } } diff --git a/src/Symfony/Component/Security/Http/Firewall/ExceptionListener.php b/src/Symfony/Component/Security/Http/Firewall/ExceptionListener.php index abbb4606a6e8..0cca0c46cc39 100644 --- a/src/Symfony/Component/Security/Http/Firewall/ExceptionListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/ExceptionListener.php @@ -69,6 +69,16 @@ public function register(EventDispatcherInterface $dispatcher) $dispatcher->addListener(KernelEvents::EXCEPTION, array($this, 'onKernelException')); } + /** + * 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. * @@ -76,10 +86,6 @@ public function register(EventDispatcherInterface $dispatcher) */ 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(); diff --git a/src/Symfony/Component/Security/composer.json b/src/Symfony/Component/Security/composer.json index b6bbae451502..fe1299c170e1 100644 --- a/src/Symfony/Component/Security/composer.json +++ b/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", + "symfony/http-kernel": "~2.4" }, "require-dev": { "symfony/form": "~2.0", pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy