diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/HttpKernelExtensionTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/HttpKernelExtensionTest.php index 077927cd6d5fc..b7d44188b1757 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/HttpKernelExtensionTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/HttpKernelExtensionTest.php @@ -14,7 +14,9 @@ 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\HttpKernel\Fragment\FragmentHandler; +use Symfony\Component\HttpKernel\RequestContext; class HttpKernelExtensionTest extends TestCase { @@ -36,13 +38,30 @@ protected function setUp() */ 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\\HttpKernel\\RequestContext') + ->disableOriginalConstructor() + ->getMock() + ; + $renderer = new FragmentHandler($context, array()); + + $this->setExpectedException('InvalidArgumentException', 'The "inline" renderer does not exist.'); + $renderer->render('/foo'); } protected function getFragmentHandler($return) @@ -51,8 +70,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\\HttpKernel\\RequestContext') + ->disableOriginalConstructor() + ->getMock() + ; + + $context->expects($this->any())->method('getCurrentRequest')->will($this->returnValue(Request::create('/'))); + + $renderer = new FragmentHandler($context, array($strategy)); 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 595db6d2741c6..78a04dee888f1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/fragment_renderer.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/fragment_renderer.xml @@ -14,9 +14,9 @@ + %kernel.debug% - diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml index 9e21db4519151..34eb332147490 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml @@ -92,9 +92,9 @@ + - diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml index 674e28f1c98a0..607634d1a7420 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml @@ -12,6 +12,8 @@ Symfony\Component\HttpKernel\CacheClearer\ChainCacheClearer Symfony\Component\HttpKernel\Config\FileLocator Symfony\Component\HttpKernel\UriSigner + Symfony\Component\HttpKernel\RequestStack + Symfony\Component\HttpKernel\RequestContext @@ -23,6 +25,14 @@ + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml index 177821a5afb24..d2984f5eb4602 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml @@ -37,8 +37,8 @@ %kernel.default_locale% + - diff --git a/src/Symfony/Component/HttpKernel/DependencyInjection/ContainerAwareHttpKernel.php b/src/Symfony/Component/HttpKernel/DependencyInjection/ContainerAwareHttpKernel.php index da917fd32b9ab..3ea8d87f960c5 100644 --- a/src/Symfony/Component/HttpKernel/DependencyInjection/ContainerAwareHttpKernel.php +++ b/src/Symfony/Component/HttpKernel/DependencyInjection/ContainerAwareHttpKernel.php @@ -15,6 +15,7 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\HttpKernel\HttpKernel; +use Symfony\Component\HttpKernel\RequestStack; use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -29,6 +30,7 @@ class ContainerAwareHttpKernel extends HttpKernel { protected $container; + protected $requestStack; /** * Constructor. @@ -36,12 +38,14 @@ 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) { - parent::__construct($dispatcher, $controllerResolver); + parent::__construct($dispatcher, $controllerResolver, $requestStack); $this->container = $container; + $container->addScope(new Scope('request')); } diff --git a/src/Symfony/Component/HttpKernel/Event/RequestFinishedEvent.php b/src/Symfony/Component/HttpKernel/Event/RequestFinishedEvent.php new file mode 100644 index 0000000000000..c9d3c39a72cb0 --- /dev/null +++ b/src/Symfony/Component/HttpKernel/Event/RequestFinishedEvent.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 RequestFinishedEvent extends KernelEvent +{ +} diff --git a/src/Symfony/Component/HttpKernel/EventListener/LocaleListener.php b/src/Symfony/Component/HttpKernel/EventListener/LocaleListener.php index 0b864c02f2bc4..7b6f2ad208ccd 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\RequestFinishedEvent; use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\RequestContext; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\RequestContextAwareInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; @@ -26,41 +28,64 @@ class LocaleListener implements EventSubscriberInterface { private $router; private $defaultLocale; + private $requestContext; - public function __construct($defaultLocale = 'en', RequestContextAwareInterface $router = null) + public function __construct($defaultLocale = 'en', RequestContext $requestContext, RequestContextAwareInterface $router = null) { $this->defaultLocale = $defaultLocale; + $this->requestContext = $requestContext; $this->router = $router; } - public function setRequest(Request $request = null) + public function onKernelRequest(GetResponseEvent $event) + { + $request = $event->getRequest(); + $request->setDefaultLocale($this->defaultLocale); + + $this->setLocale($request); + $this->setRouterContext($request); + } + + public function onKernelRequestFinished(RequestFinishedEvent $event) { - if (null === $request) { + $this->resetRouterContext(); + } + + private function resetRouterContext() + { + if ($this->requestContext === null) { + return; + } + + $parentRequest = $this->requestContext->getParentRequest(); + + if ($parentRequest === null) { return; } + $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 function onKernelRequest(GetResponseEvent $event) - { - $request = $event->getRequest(); - $request->setDefaultLocale($this->defaultLocale); - - $this->setRequest($request); - } - 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::REQUEST_FINISHED => array(array('onKernelRequestFinished', 0)), ); } } diff --git a/src/Symfony/Component/HttpKernel/EventListener/RouterListener.php b/src/Symfony/Component/HttpKernel/EventListener/RouterListener.php index 58ea6f0a5436d..c8b425c9696be 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\RequestFinishedEvent; use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\HttpKernel\RequestContext as KernelRequestContext; use Symfony\Component\Routing\Exception\MethodNotAllowedException; use Symfony\Component\Routing\Exception\ResourceNotFoundException; use Symfony\Component\Routing\Matcher\UrlMatcherInterface; @@ -35,6 +37,7 @@ class RouterListener implements EventSubscriberInterface private $matcher; private $context; private $logger; + private $kernelContext; /** * Constructor. @@ -45,7 +48,7 @@ class RouterListener implements EventSubscriberInterface * * @throws \InvalidArgumentException */ - public function __construct($matcher, RequestContext $context = null, LoggerInterface $logger = null) + public function __construct($matcher, KernelRequestContext $kernelContext, RequestContext $context = null, LoggerInterface $logger = null) { if (!$matcher instanceof UrlMatcherInterface && !$matcher instanceof RequestMatcherInterface) { throw new \InvalidArgumentException('Matcher must either implement UrlMatcherInterface or RequestMatcherInterface.'); @@ -57,6 +60,7 @@ public function __construct($matcher, RequestContext $context = null, LoggerInte $this->matcher = $matcher; $this->context = $context ?: $matcher->getContext(); + $this->kernelContext = $kernelContext; $this->logger = $logger; } @@ -70,13 +74,18 @@ public function __construct($matcher, RequestContext $context = null, LoggerInte * * @param Request|null $request A Request instance */ - public function setRequest(Request $request = null) + private function populateRoutingContext(Request $request = null) { if (null !== $request) { $this->context->fromRequest($request); } } + public function onKernelRequestFinished(RequestFinishedEvent $event) + { + $this->populateRoutingContext($this->kernelContext->getParentRequest()); + } + public function onKernelRequest(GetResponseEvent $event) { $request = $event->getRequest(); @@ -84,7 +93,7 @@ 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); + $this->populateRoutingContext($request); if ($request->attributes->has('_controller')) { // routing is already done @@ -133,6 +142,7 @@ public static function getSubscribedEvents() { return array( KernelEvents::REQUEST => array(array('onKernelRequest', 32)), + KernelEvents::REQUEST_FINISHED => array(array('onKernelRequestFinished', 0)), ); } } diff --git a/src/Symfony/Component/HttpKernel/Fragment/FragmentHandler.php b/src/Symfony/Component/HttpKernel/Fragment/FragmentHandler.php index af9b9ba98b744..099f35a306bf2 100644 --- a/src/Symfony/Component/HttpKernel/Fragment/FragmentHandler.php +++ b/src/Symfony/Component/HttpKernel/Fragment/FragmentHandler.php @@ -15,6 +15,7 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\StreamedResponse; use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpKernel\RequestContext; /** * Renders a URI that represents a resource fragment. @@ -30,7 +31,7 @@ class FragmentHandler { private $debug; private $renderers; - private $request; + private $context; /** * Constructor. @@ -38,8 +39,9 @@ class FragmentHandler * @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(RequestContext $context, array $renderers = array(), $debug = false) { + $this->context = $context; $this->renderers = array(); foreach ($renderers as $renderer) { $this->addRenderer($renderer); @@ -57,16 +59,6 @@ public function addRenderer(FragmentRendererInterface $renderer) $this->renderers[$renderer->getName()] = $renderer; } - /** - * Sets the current Request. - * - * @param Request $request The current Request - */ - public function setRequest(Request $request = null) - { - $this->request = $request; - } - /** * Renders a URI and returns the Response content. * @@ -93,11 +85,7 @@ 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.'); - } - - return $this->deliver($this->renderers[$renderer]->render($uri, $this->request, $options)); + return $this->deliver($this->renderers[$renderer]->render($uri, $this->context->getCurrentRequest(), $options)); } /** @@ -115,7 +103,8 @@ 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())); + $request = $this->context->getCurrentRequest(); + throw new \RuntimeException(sprintf('Error when rendering "%s" (Status code is %s).', $request->getUri(), $response->getStatusCode())); } if (!$response instanceof StreamedResponse) { diff --git a/src/Symfony/Component/HttpKernel/HttpKernel.php b/src/Symfony/Component/HttpKernel/HttpKernel.php index 0001a49495b77..24fd973897894 100644 --- a/src/Symfony/Component/HttpKernel/HttpKernel.php +++ b/src/Symfony/Component/HttpKernel/HttpKernel.php @@ -16,6 +16,7 @@ use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; use Symfony\Component\HttpKernel\Event\FilterControllerEvent; use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\Event\RequestFinishedEvent; use Symfony\Component\HttpKernel\Event\GetResponseEvent; use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent; use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; @@ -35,19 +36,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(); } /** @@ -72,6 +76,8 @@ public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQ try { return $this->handleRaw($request, $type); } catch (\Exception $e) { + $this->finishRequest($request, $type); + if (false === $catch) { throw $e; } @@ -105,6 +111,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); @@ -168,9 +176,29 @@ private function filterResponse(Response $response, Request $request, $type) $this->dispatcher->dispatch(KernelEvents::RESPONSE, $event); + $this->finishRequest($request, $type); + return $event->getResponse(); } + /** + * Publish event finished event, then pop the request from the stack. + * + * Note: 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 + * + * @return void + */ + private function finishRequest(Request $request, $type) + { + $this->dispatcher->dispatch(KernelEvents::REQUEST_FINISHED, new RequestFinishedEvent($this, $request, $type)); + $this->requestStack->pop(); + } + /** * Handles an exception by trying to convert it to a Response. * diff --git a/src/Symfony/Component/HttpKernel/KernelEvents.php b/src/Symfony/Component/HttpKernel/KernelEvents.php index fce48ac3a6ed8..e1ad0fa004fe3 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 REQUEST_FINISHED = 'kernel.request_finished'; } diff --git a/src/Symfony/Component/HttpKernel/RequestContext.php b/src/Symfony/Component/HttpKernel/RequestContext.php new file mode 100644 index 0000000000000..f6fdaf3bbe834 --- /dev/null +++ b/src/Symfony/Component/HttpKernel/RequestContext.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel; + +/** + * Registry for Requests. + * + * Facade for RequestStack that prevents modification of the stack, + * so that users don't accidentally push()/pop() from the stack and + * mess up the request cycle. + * + * @author Benjamin Eberlei + */ +class RequestContext +{ + private $stack; + + public function __construct(RequestStack $stack) + { + $this->stack = $stack; + } + + /** + * @return Request + */ + public function getCurrentRequest() + { + return $this->stack->getCurrentRequest(); + } + + /** + * @return Request + */ + public function getMasterRequest() + { + return $this->stack->getMasterRequest(); + } + + /** + * @return Request|null + */ + public function getParentRequest() + { + return $this->stack->getParentRequest(); + } +} diff --git a/src/Symfony/Component/HttpKernel/RequestStack.php b/src/Symfony/Component/HttpKernel/RequestStack.php new file mode 100644 index 0000000000000..c8de54981d937 --- /dev/null +++ b/src/Symfony/Component/HttpKernel/RequestStack.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel; + +use Symfony\Component\HttpFoundation\Request; + +/** + * Request stack that controls the lifecycle of requests. + * + * Notifies services of changes in the stack. + * + * @author Benjamin Eberlei + */ +class RequestStack +{ + /** + * @var Request[] + */ + private $requests = array(); + + public function push(Request $request) + { + $this->requests[] = $request; + } + + /** + * Pop the current request from the stack. + * + * This operation lets the current request go out of scope. + * + * @return Request + */ + public function pop() + { + return array_pop($this->requests); + } + + /** + * @return Request|null + */ + public function getCurrentRequest() + { + return end($this->requests) ?: null; + } + + /** + * @return Request|null + */ + public function getMasterRequest() + { + if (!$this->requests) { + return null; + } + + return $this->requests[0]; + } + + /** + * Return the parent request of the current. + * + * If current Request is the master request, method returns null. + * + * @return Request + */ + 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/Tests/DependencyInjection/ContainerAwareHttpKernelTest.php b/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/ContainerAwareHttpKernelTest.php index 0b61a039f25d3..ccfa89fc9c214 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\HttpKernel\RequestStack; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\EventDispatcher\EventDispatcher; @@ -41,50 +42,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')) - ; - // first call is for addScope() - $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, 2) + ->expectsSetRequestWithAt($container, null, 3) ; $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); + + $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\HttpKernel\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); } /** @@ -94,45 +94,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(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, 2) + ->expectsSetRequestWithAt($container, null, 3) ; $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); @@ -149,4 +127,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 e5e4e3a286dfc..61caee9e2d7fe 100644 --- a/src/Symfony/Component/HttpKernel/Tests/EventListener/LocaleListenerTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/EventListener/LocaleListenerTest.php @@ -12,22 +12,27 @@ namespace Symfony\Component\HttpKernel\Tests\EventListener; use Symfony\Component\HttpKernel\EventListener\LocaleListener; +use Symfony\Component\HttpKernel\RequestContext; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\HttpKernel\Event\GetResponseEvent; class LocaleListenerTest extends \PHPUnit_Framework_TestCase { + private $context; + protected function setUp() { if (!class_exists('Symfony\Component\EventDispatcher\EventDispatcher')) { $this->markTestSkipped('The "EventDispatcher" component is not available'); } + + $this->context = $this->getMock('Symfony\Component\HttpKernel\RequestContext', array(), array(), '', false); } public function testDefaultLocaleWithoutSession() { - $listener = new LocaleListener('fr'); + $listener = new LocaleListener('fr', $this->context); $event = $this->getEvent($request = Request::create('/')); $listener->onKernelRequest($event); @@ -41,7 +46,7 @@ public function testLocaleFromRequestAttribute() $request->cookies->set('foo', 'value'); $request->attributes->set('_locale', 'es'); - $listener = new LocaleListener('fr'); + $listener = new LocaleListener('fr', $this->context); $event = $this->getEvent($request); $listener->onKernelRequest($event); @@ -64,15 +69,39 @@ public function testLocaleSetForRoutingContext() $request = Request::create('/'); $request->attributes->set('_locale', 'es'); - $listener = new LocaleListener('fr', $router); + $listener = new LocaleListener('fr', $this->context, $router); $listener->onKernelRequest($this->getEvent($request)); } + public function testRouterResetWithParentRequestOnKernelRequestFinished() + { + 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->context->expects($this->once())->method('getParentRequest')->will($this->returnValue($parentRequest)); + + $event = $this->getMock('Symfony\Component\HttpKernel\Event\RequestFinishedEvent', array(), array(), '', false); + + $listener = new LocaleListener('fr', $this->context, $router); + $listener->onKernelRequestFinished($event); + } + public function testRequestLocaleIsNotOverridden() { $request = Request::create('/'); $request->setLocale('de'); - $listener = new LocaleListener('fr'); + $listener = new LocaleListener('fr', $this->context); $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 3ba56a8da78b8..37b00ccf7c993 100644 --- a/src/Symfony/Component/HttpKernel/Tests/EventListener/RouterListenerTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/EventListener/RouterListenerTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\HttpKernel\Tests\EventListener; use Symfony\Component\HttpKernel\EventListener\RouterListener; +use Symfony\Component\HttpKernel\RequestContext as KernelRequestContext; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\HttpKernel\Event\GetResponseEvent; @@ -19,6 +20,8 @@ class RouterListenerTest extends \PHPUnit_Framework_TestCase { + private $kernelContext; + protected function setUp() { if (!class_exists('Symfony\Component\EventDispatcher\EventDispatcher')) { @@ -28,6 +31,8 @@ protected function setUp() if (!class_exists('Symfony\Component\Routing\Router')) { $this->markTestSkipped('The "Routing" component is not available'); } + + $this->kernelContext = $this->getMock('Symfony\Component\HttpKernel\RequestContext', array(), array(), '', false); } /** @@ -45,7 +50,7 @@ public function testPort($defaultHttpPort, $defaultHttpsPort, $uri, $expectedHtt ->method('getContext') ->will($this->returnValue($context)); - $listener = new RouterListener($urlMatcher); + $listener = new RouterListener($urlMatcher, $this->kernelContext); $event = $this->createGetResponseEventForUri($uri); $listener->onKernelRequest($event); @@ -83,7 +88,7 @@ private function createGetResponseEventForUri($uri) */ public function testInvalidMatcher() { - new RouterListener(new \stdClass()); + new RouterListener(new \stdClass(), $this->kernelContext); } public function testRequestMatcher() @@ -98,7 +103,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, $this->kernelContext, new RequestContext()); $listener->onKernelRequest($event); } @@ -119,7 +124,7 @@ public function testSubRequestWithDifferentMethod() ->method('getContext') ->will($this->returnValue($context)); - $listener = new RouterListener($requestMatcher, new RequestContext()); + $listener = new RouterListener($requestMatcher, $this->kernelContext, new RequestContext()); $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 cec8ae98403a7..33e01d54b3970 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 $context; + + public function setUp() + { + $this->context = $this->getMockBuilder('Symfony\\Component\\HttpKernel\\RequestContext') + ->disableOriginalConstructor() + ->getMock() + ; + $this->context + ->expects($this->any()) + ->method('getCurrentRequest') + ->will($this->returnValue(Request::create('/'))) + ; + } + /** * @expectedException \InvalidArgumentException */ public function testRenderWhenRendererDoesNotExist() { - $handler = new FragmentHandler(); + $handler = new FragmentHandler($this->context); $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($this->context); $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 367e3e2d41d69..9312a562a7783 100644 --- a/src/Symfony/Component/HttpKernel/Tests/HttpKernelTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/HttpKernelTest.php @@ -249,6 +249,20 @@ public function testTerminate() $this->assertEquals($response, $capturedResponse); } + public function testVerifyRequestStackPushPopDuringHandle() + { + $request = new Request(); + + $stack = $this->getMock('Symfony\Component\HttpKernel\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/Tests/RequestStackTest.php b/src/Symfony/Component/HttpKernel/Tests/RequestStackTest.php new file mode 100644 index 0000000000000..6192d146cf15a --- /dev/null +++ b/src/Symfony/Component/HttpKernel/Tests/RequestStackTest.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests; + +use Symfony\Component\HttpKernel\RequestStack; +use Symfony\Component\HttpFoundation\Request; + +class RequestStackTest extends \PHPUnit_Framework_TestCase +{ + public function testPushThenPopReturnsSameRequest() + { + $request = Request::create('/'); + + $stack = new RequestStack(); + $stack->push($request); + + $this->assertSame($request, $stack->pop()); + $this->assertNull($stack->pop()); + } + + public function testPopEmptyStackReturnsNull() + { + $stack = new RequestStack(); + + $this->assertNull($stack->pop()); + } + + public function testGetCurrentRequest() + { + $request = Request::create('/'); + $stack = new RequestStack(); + + $this->assertNull($stack->getCurrentRequest()); + + $stack->push($request); + + $this->assertSame($request, $stack->getCurrentRequest()); + $this->assertSame($request, $stack->getCurrentRequest()); + } + + public function testGetMasterRequest() + { + $request = Request::create('/'); + $stack = new RequestStack(); + + $this->assertNull($stack->getMasterRequest()); + + $stack->push($request); + + $this->assertSame($request, $stack->getMasterRequest()); + $this->assertSame($request, $stack->getMasterRequest()); + } + + public function testGetParentRequest() + { + $request = Request::create('/'); + $subrequest = Request::create('/'); + + $stack = new RequestStack(); + $stack->push($request); + $stack->push($subrequest); + + $this->assertSame($request, $stack->getParentRequest()); + $this->assertSame($subrequest, $stack->pop()); + $this->assertSame($request, $stack->pop()); + } +}