added a RequestStack class

This commit is contained in:
Benjamin Eberlei 2013-04-18 10:33:03 +02:00 committed by Fabien Potencier
parent 08ec911c8e
commit c55f1ea8af
19 changed files with 487 additions and 149 deletions

View File

@ -14,7 +14,9 @@ namespace Symfony\Bridge\Twig\Tests\Extension;
use Symfony\Bridge\Twig\Extension\HttpKernelExtension; use Symfony\Bridge\Twig\Extension\HttpKernelExtension;
use Symfony\Bridge\Twig\Tests\TestCase; use Symfony\Bridge\Twig\Tests\TestCase;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Fragment\FragmentHandler; use Symfony\Component\HttpKernel\Fragment\FragmentHandler;
use Symfony\Component\HttpKernel\RequestContext;
class HttpKernelExtensionTest extends TestCase class HttpKernelExtensionTest extends TestCase
{ {
@ -23,13 +25,30 @@ class HttpKernelExtensionTest extends TestCase
*/ */
public function testFragmentWithError() 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") }}')); $this->renderTemplate($renderer);
$twig = new \Twig_Environment($loader, array('debug' => true, 'cache' => false)); }
$twig->addExtension(new HttpKernelExtension($kernel));
$this->renderTemplate($kernel); public function testRenderFragment()
{
$renderer = $this->getFragmentHandler($this->returnValue(new Response('html')));
$response = $this->renderTemplate($renderer);
$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) protected function getFragmentHandler($return)
@ -38,8 +57,14 @@ class HttpKernelExtensionTest extends TestCase
$strategy->expects($this->once())->method('getName')->will($this->returnValue('inline')); $strategy->expects($this->once())->method('getName')->will($this->returnValue('inline'));
$strategy->expects($this->once())->method('render')->will($return); $strategy->expects($this->once())->method('render')->will($return);
$renderer = new FragmentHandler(array($strategy)); $context = $this->getMockBuilder('Symfony\\Component\\HttpKernel\\RequestContext')
$renderer->setRequest(Request::create('/')); ->disableOriginalConstructor()
->getMock()
;
$context->expects($this->any())->method('getCurrentRequest')->will($this->returnValue(Request::create('/')));
$renderer = new FragmentHandler($context, array($strategy));
return $renderer; return $renderer;
} }

View File

@ -15,9 +15,9 @@
<services> <services>
<service id="fragment.handler" class="%fragment.handler.class%"> <service id="fragment.handler" class="%fragment.handler.class%">
<argument type="service" id="request_context" />
<argument type="collection" /> <argument type="collection" />
<argument>%kernel.debug%</argument> <argument>%kernel.debug%</argument>
<call method="setRequest"><argument type="service" id="request" on-invalid="null" strict="false" /></call>
</service> </service>
<service id="fragment.renderer.inline" class="%fragment.renderer.inline.class%"> <service id="fragment.renderer.inline" class="%fragment.renderer.inline.class%">

View File

@ -92,9 +92,9 @@
<tag name="kernel.event_subscriber" /> <tag name="kernel.event_subscriber" />
<tag name="monolog.logger" channel="request" /> <tag name="monolog.logger" channel="request" />
<argument type="service" id="router" /> <argument type="service" id="router" />
<argument type="service" id="request_context" />
<argument type="service" id="router.request_context" on-invalid="ignore" /> <argument type="service" id="router.request_context" on-invalid="ignore" />
<argument type="service" id="logger" on-invalid="ignore" /> <argument type="service" id="logger" on-invalid="ignore" />
<call method="setRequest"><argument type="service" id="request" on-invalid="null" strict="false" /></call>
</service> </service>
</services> </services>
</container> </container>

View File

@ -12,6 +12,8 @@
<parameter key="cache_clearer.class">Symfony\Component\HttpKernel\CacheClearer\ChainCacheClearer</parameter> <parameter key="cache_clearer.class">Symfony\Component\HttpKernel\CacheClearer\ChainCacheClearer</parameter>
<parameter key="file_locator.class">Symfony\Component\HttpKernel\Config\FileLocator</parameter> <parameter key="file_locator.class">Symfony\Component\HttpKernel\Config\FileLocator</parameter>
<parameter key="uri_signer.class">Symfony\Component\HttpKernel\UriSigner</parameter> <parameter key="uri_signer.class">Symfony\Component\HttpKernel\UriSigner</parameter>
<parameter key="request_stack.class">Symfony\Component\HttpKernel\RequestStack</parameter>
<parameter key="request_context.class">Symfony\Component\HttpKernel\RequestContext</parameter>
</parameters> </parameters>
<services> <services>
@ -23,6 +25,14 @@
<argument type="service" id="event_dispatcher" /> <argument type="service" id="event_dispatcher" />
<argument type="service" id="service_container" /> <argument type="service" id="service_container" />
<argument type="service" id="controller_resolver" /> <argument type="service" id="controller_resolver" />
<argument type="service" id="request_stack" />
</service>
<service id="request_stack" class="%request_stack.class%">
</service>
<service id="request_context" class="%request_context.class%">
<argument type="service" id="request_stack" />
</service> </service>
<service id="cache_warmer" class="%cache_warmer.class%"> <service id="cache_warmer" class="%cache_warmer.class%">

View File

@ -37,8 +37,8 @@
<service id="locale_listener" class="%locale_listener.class%"> <service id="locale_listener" class="%locale_listener.class%">
<tag name="kernel.event_subscriber" /> <tag name="kernel.event_subscriber" />
<argument>%kernel.default_locale%</argument> <argument>%kernel.default_locale%</argument>
<argument type="service" id="request_context" />
<argument type="service" id="router" on-invalid="ignore" /> <argument type="service" id="router" on-invalid="ignore" />
<call method="setRequest"><argument type="service" id="request" on-invalid="null" strict="false" /></call>
</service> </service>
</services> </services>
</container> </container>

View File

@ -14,6 +14,7 @@ namespace Symfony\Component\HttpKernel\DependencyInjection;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\HttpKernel; use Symfony\Component\HttpKernel\HttpKernel;
use Symfony\Component\HttpKernel\RequestStack;
use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface; use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\ContainerInterface;
@ -28,6 +29,7 @@ use Symfony\Component\DependencyInjection\Scope;
class ContainerAwareHttpKernel extends HttpKernel class ContainerAwareHttpKernel extends HttpKernel
{ {
protected $container; protected $container;
protected $requestStack;
/** /**
* Constructor. * Constructor.
@ -35,10 +37,11 @@ class ContainerAwareHttpKernel extends HttpKernel
* @param EventDispatcherInterface $dispatcher An EventDispatcherInterface instance * @param EventDispatcherInterface $dispatcher An EventDispatcherInterface instance
* @param ContainerInterface $container A ContainerInterface instance * @param ContainerInterface $container A ContainerInterface instance
* @param ControllerResolverInterface $controllerResolver A ControllerResolverInterface 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; $this->container = $container;

View File

@ -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 RequestFinishedEvent extends KernelEvent
{
}

View File

@ -12,7 +12,9 @@
namespace Symfony\Component\HttpKernel\EventListener; namespace Symfony\Component\HttpKernel\EventListener;
use Symfony\Component\HttpKernel\Event\GetResponseEvent; use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\Event\RequestFinishedEvent;
use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\RequestContext;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\RequestContextAwareInterface; use Symfony\Component\Routing\RequestContextAwareInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface;
@ -26,34 +28,56 @@ class LocaleListener implements EventSubscriberInterface
{ {
private $router; private $router;
private $defaultLocale; 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->defaultLocale = $defaultLocale;
$this->requestContext = $requestContext;
$this->router = $router; $this->router = $router;
} }
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());
}
}
public function onKernelRequest(GetResponseEvent $event) public function onKernelRequest(GetResponseEvent $event)
{ {
$request = $event->getRequest(); $request = $event->getRequest();
$request->setDefaultLocale($this->defaultLocale); $request->setDefaultLocale($this->defaultLocale);
$this->setRequest($request); $this->setLocale($request);
$this->setRouterContext($request);
}
public function onKernelRequestFinished(RequestFinishedEvent $event)
{
$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 static function getSubscribedEvents() public static function getSubscribedEvents()
@ -61,6 +85,7 @@ class LocaleListener implements EventSubscriberInterface
return array( return array(
// must be registered after the Router to have access to the _locale // must be registered after the Router to have access to the _locale
KernelEvents::REQUEST => array(array('onKernelRequest', 16)), KernelEvents::REQUEST => array(array('onKernelRequest', 16)),
KernelEvents::REQUEST_FINISHED => array(array('onKernelRequestFinished', 0)),
); );
} }
} }

View File

@ -13,9 +13,11 @@ namespace Symfony\Component\HttpKernel\EventListener;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent; use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\Event\RequestFinishedEvent;
use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\RequestContext as KernelRequestContext;
use Symfony\Component\Routing\Exception\MethodNotAllowedException; use Symfony\Component\Routing\Exception\MethodNotAllowedException;
use Symfony\Component\Routing\Exception\ResourceNotFoundException; use Symfony\Component\Routing\Exception\ResourceNotFoundException;
use Symfony\Component\Routing\Matcher\UrlMatcherInterface; use Symfony\Component\Routing\Matcher\UrlMatcherInterface;
@ -36,6 +38,7 @@ class RouterListener implements EventSubscriberInterface
private $context; private $context;
private $logger; private $logger;
private $request; private $request;
private $kernelContext;
/** /**
* Constructor. * Constructor.
@ -46,7 +49,7 @@ class RouterListener implements EventSubscriberInterface
* *
* @throws \InvalidArgumentException * @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) { if (!$matcher instanceof UrlMatcherInterface && !$matcher instanceof RequestMatcherInterface) {
throw new \InvalidArgumentException('Matcher must either implement UrlMatcherInterface or RequestMatcherInterface.'); throw new \InvalidArgumentException('Matcher must either implement UrlMatcherInterface or RequestMatcherInterface.');
@ -58,6 +61,7 @@ class RouterListener implements EventSubscriberInterface
$this->matcher = $matcher; $this->matcher = $matcher;
$this->context = $context ?: $matcher->getContext(); $this->context = $context ?: $matcher->getContext();
$this->kernelContext = $kernelContext;
$this->logger = $logger; $this->logger = $logger;
} }
@ -71,7 +75,7 @@ class RouterListener implements EventSubscriberInterface
* *
* @param Request|null $request A Request instance * @param Request|null $request A Request instance
*/ */
public function setRequest(Request $request = null) private function populateRoutingContext(Request $request = null)
{ {
if (null !== $request && $this->request !== $request) { if (null !== $request && $this->request !== $request) {
$this->context->fromRequest($request); $this->context->fromRequest($request);
@ -79,6 +83,11 @@ class RouterListener implements EventSubscriberInterface
$this->request = $request; $this->request = $request;
} }
public function onKernelRequestFinished(RequestFinishedEvent $event)
{
$this->populateRoutingContext($this->kernelContext->getParentRequest());
}
public function onKernelRequest(GetResponseEvent $event) public function onKernelRequest(GetResponseEvent $event)
{ {
$request = $event->getRequest(); $request = $event->getRequest();
@ -86,7 +95,7 @@ class RouterListener implements EventSubscriberInterface
// initialize the context that is also used by the generator (assuming matcher and generator share the same context instance) // 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 // 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 // with frameworks which do not use the Symfony service container
$this->setRequest($request); $this->populateRoutingContext($request);
if ($request->attributes->has('_controller')) { if ($request->attributes->has('_controller')) {
// routing is already done // routing is already done
@ -139,6 +148,7 @@ class RouterListener implements EventSubscriberInterface
{ {
return array( return array(
KernelEvents::REQUEST => array(array('onKernelRequest', 32)), KernelEvents::REQUEST => array(array('onKernelRequest', 32)),
KernelEvents::REQUEST_FINISHED => array(array('onKernelRequestFinished', 0)),
); );
} }
} }

View File

@ -15,6 +15,7 @@ use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\StreamedResponse; use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpKernel\Controller\ControllerReference; use Symfony\Component\HttpKernel\Controller\ControllerReference;
use Symfony\Component\HttpKernel\RequestContext;
/** /**
* Renders a URI that represents a resource fragment. * Renders a URI that represents a resource fragment.
@ -30,7 +31,7 @@ class FragmentHandler
{ {
private $debug; private $debug;
private $renderers; private $renderers;
private $request; private $context;
/** /**
* Constructor. * Constructor.
@ -38,8 +39,9 @@ class FragmentHandler
* @param FragmentRendererInterface[] $renderers An array of FragmentRendererInterface instances * @param FragmentRendererInterface[] $renderers An array of FragmentRendererInterface instances
* @param Boolean $debug Whether the debug mode is enabled or not * @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(); $this->renderers = array();
foreach ($renderers as $renderer) { foreach ($renderers as $renderer) {
$this->addRenderer($renderer); $this->addRenderer($renderer);
@ -57,16 +59,6 @@ class FragmentHandler
$this->renderers[$renderer->getName()] = $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. * Renders a URI and returns the Response content.
* *
@ -93,11 +85,7 @@ class FragmentHandler
throw new \InvalidArgumentException(sprintf('The "%s" renderer does not exist.', $renderer)); throw new \InvalidArgumentException(sprintf('The "%s" renderer does not exist.', $renderer));
} }
if (null === $this->request) { return $this->deliver($this->renderers[$renderer]->render($uri, $this->context->getCurrentRequest(), $options));
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));
} }
/** /**
@ -115,7 +103,8 @@ class FragmentHandler
protected function deliver(Response $response) protected function deliver(Response $response)
{ {
if (!$response->isSuccessful()) { 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) { if (!$response instanceof StreamedResponse) {

View File

@ -16,6 +16,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent; use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent; use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\Event\RequestFinishedEvent;
use Symfony\Component\HttpKernel\Event\GetResponseEvent; use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent; use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
@ -35,19 +36,22 @@ class HttpKernel implements HttpKernelInterface, TerminableInterface
{ {
protected $dispatcher; protected $dispatcher;
protected $resolver; protected $resolver;
protected $requestStack;
/** /**
* Constructor * Constructor
* *
* @param EventDispatcherInterface $dispatcher An EventDispatcherInterface instance * @param EventDispatcherInterface $dispatcher An EventDispatcherInterface instance
* @param ControllerResolverInterface $resolver A ControllerResolverInterface instance * @param ControllerResolverInterface $resolver A ControllerResolverInterface instance
* @param RequestStack $requestStack A stack for master/sub requests
* *
* @api * @api
*/ */
public function __construct(EventDispatcherInterface $dispatcher, ControllerResolverInterface $resolver) public function __construct(EventDispatcherInterface $dispatcher, ControllerResolverInterface $resolver, RequestStack $requestStack = null)
{ {
$this->dispatcher = $dispatcher; $this->dispatcher = $dispatcher;
$this->resolver = $resolver; $this->resolver = $resolver;
$this->requestStack = $requestStack ?: new RequestStack();
} }
/** /**
@ -60,6 +64,8 @@ class HttpKernel implements HttpKernelInterface, TerminableInterface
try { try {
return $this->handleRaw($request, $type); return $this->handleRaw($request, $type);
} catch (\Exception $e) { } catch (\Exception $e) {
$this->finishRequest($request, $type);
if (false === $catch) { if (false === $catch) {
throw $e; throw $e;
} }
@ -93,6 +99,8 @@ class HttpKernel implements HttpKernelInterface, TerminableInterface
*/ */
private function handleRaw(Request $request, $type = self::MASTER_REQUEST) private function handleRaw(Request $request, $type = self::MASTER_REQUEST)
{ {
$this->requestStack->push($request);
// request // request
$event = new GetResponseEvent($this, $request, $type); $event = new GetResponseEvent($this, $request, $type);
$this->dispatcher->dispatch(KernelEvents::REQUEST, $event); $this->dispatcher->dispatch(KernelEvents::REQUEST, $event);
@ -156,9 +164,29 @@ class HttpKernel implements HttpKernelInterface, TerminableInterface
$this->dispatcher->dispatch(KernelEvents::RESPONSE, $event); $this->dispatcher->dispatch(KernelEvents::RESPONSE, $event);
$this->finishRequest($request, $type);
return $event->getResponse(); 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. * Handles an exception by trying to convert it to a Response.
* *

View File

@ -102,4 +102,14 @@ final class KernelEvents
* @var string * @var string
*/ */
const TERMINATE = 'kernel.terminate'; 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';
} }

View File

@ -0,0 +1,55 @@
<?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;
/**
* 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 <kontakt@beberlei.de>
*/
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();
}
}

View File

@ -0,0 +1,84 @@
<?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;
use Symfony\Component\HttpFoundation\Request;
/**
* Request stack that controls the lifecycle of requests.
*
* Notifies services of changes in the stack.
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
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];
}
}

View File

@ -13,6 +13,7 @@ namespace Symfony\Component\HttpKernel\Tests\DependencyInjection;
use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\DependencyInjection\ContainerAwareHttpKernel; use Symfony\Component\HttpKernel\DependencyInjection\ContainerAwareHttpKernel;
use Symfony\Component\HttpKernel\RequestStack;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\EventDispatcher\EventDispatcher;
@ -26,61 +27,51 @@ class ContainerAwareHttpKernelTest extends \PHPUnit_Framework_TestCase
{ {
$request = new Request(); $request = new Request();
$expected = new Response(); $expected = new Response();
$controller = function() use ($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'))
;
$dispatcher = new EventDispatcher();
$resolver = $this->getMock('Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface');
$kernel = new ContainerAwareHttpKernel($dispatcher, $container, $resolver);
$controller = function () use ($expected) {
return $expected; return $expected;
}; };
$resolver->expects($this->once()) $container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface');
->method('getController') $this
->with($request) ->expectsEnterScopeOnce($container)
->will($this->returnValue($controller)); ->expectsLeaveScopeOnce($container)
$resolver->expects($this->once()) ->expectsSetRequestWithAt($container, $request, 3)
->method('getArguments') ->expectsSetRequestWithAt($container, null, 4)
->with($request, $controller) ;
->will($this->returnValue(array()));
$dispatcher = new EventDispatcher();
$resolver = $this->getResolverMockFor($controller, $request);
$stack = new RequestStack();
$kernel = new ContainerAwareHttpKernel($dispatcher, $container, $resolver, $stack);
$actual = $kernel->handle($request, $type); $actual = $kernel->handle($request, $type);
$this->assertSame($expected, $actual, '->handle() returns the response'); $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;
};
$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');
$container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface');
$dispatcher = new EventDispatcher();
$resolver = $this->getResolverMockFor($controller, $request);
$kernel = new ContainerAwareHttpKernel($dispatcher, $container, $resolver, $stack);
$kernel->handle($request, $type);
}
/** /**
* @dataProvider getProviderTypes * @dataProvider getProviderTypes
*/ */
@ -88,51 +79,23 @@ class ContainerAwareHttpKernelTest extends \PHPUnit_Framework_TestCase
{ {
$request = new Request(); $request = new Request();
$expected = new \Exception(); $expected = new \Exception();
$controller = function() use ($expected) {
throw $expected;
};
$container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface'); $container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface');
$container $this
->expects($this->once()) ->expectsEnterScopeOnce($container)
->method('enterScope') ->expectsLeaveScopeOnce($container)
->with($this->equalTo('request')) ->expectsSetRequestWithAt($container, $request, 3)
; ->expectsSetRequestWithAt($container, null, 4)
$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'))
; ;
$dispatcher = new EventDispatcher(); $dispatcher = new EventDispatcher();
$resolver = $this->getMock('Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface'); $resolver = $this->getMock('Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface');
$kernel = new ContainerAwareHttpKernel($dispatcher, $container, $resolver); $resolver = $this->getResolverMockFor($controller, $request);
$stack = new RequestStack();
$controller = function () use ($expected) { $kernel = new ContainerAwareHttpKernel($dispatcher, $container, $resolver, $stack);
throw $expected;
};
$resolver->expects($this->once())
->method('getController')
->with($request)
->will($this->returnValue($controller));
$resolver->expects($this->once())
->method('getArguments')
->with($request, $controller)
->will($this->returnValue(array()));
try { try {
$kernel->handle($request, $type); $kernel->handle($request, $type);
@ -151,4 +114,51 @@ class ContainerAwareHttpKernelTest extends \PHPUnit_Framework_TestCase
array(HttpKernelInterface::SUB_REQUEST), 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 File

@ -12,15 +12,23 @@
namespace Symfony\Component\HttpKernel\Tests\EventListener; namespace Symfony\Component\HttpKernel\Tests\EventListener;
use Symfony\Component\HttpKernel\EventListener\LocaleListener; use Symfony\Component\HttpKernel\EventListener\LocaleListener;
use Symfony\Component\HttpKernel\RequestContext;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent; use Symfony\Component\HttpKernel\Event\GetResponseEvent;
class LocaleListenerTest extends \PHPUnit_Framework_TestCase class LocaleListenerTest extends \PHPUnit_Framework_TestCase
{ {
private $context;
protected function setUp()
{
$this->context = $this->getMock('Symfony\Component\HttpKernel\RequestContext', array(), array(), '', false);
}
public function testDefaultLocaleWithoutSession() public function testDefaultLocaleWithoutSession()
{ {
$listener = new LocaleListener('fr'); $listener = new LocaleListener('fr', $this->context);
$event = $this->getEvent($request = Request::create('/')); $event = $this->getEvent($request = Request::create('/'));
$listener->onKernelRequest($event); $listener->onKernelRequest($event);
@ -34,7 +42,7 @@ class LocaleListenerTest extends \PHPUnit_Framework_TestCase
$request->cookies->set('foo', 'value'); $request->cookies->set('foo', 'value');
$request->attributes->set('_locale', 'es'); $request->attributes->set('_locale', 'es');
$listener = new LocaleListener('fr'); $listener = new LocaleListener('fr', $this->context);
$event = $this->getEvent($request); $event = $this->getEvent($request);
$listener->onKernelRequest($event); $listener->onKernelRequest($event);
@ -53,15 +61,39 @@ class LocaleListenerTest extends \PHPUnit_Framework_TestCase
$request = Request::create('/'); $request = Request::create('/');
$request->attributes->set('_locale', 'es'); $request->attributes->set('_locale', 'es');
$listener = new LocaleListener('fr', $router); $listener = new LocaleListener('fr', $this->context, $router);
$listener->onKernelRequest($this->getEvent($request)); $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() public function testRequestLocaleIsNotOverridden()
{ {
$request = Request::create('/'); $request = Request::create('/');
$request->setLocale('de'); $request->setLocale('de');
$listener = new LocaleListener('fr'); $listener = new LocaleListener('fr', $this->context);
$event = $this->getEvent($request); $event = $this->getEvent($request);
$listener->onKernelRequest($event); $listener->onKernelRequest($event);

View File

@ -12,6 +12,7 @@
namespace Symfony\Component\HttpKernel\Tests\EventListener; namespace Symfony\Component\HttpKernel\Tests\EventListener;
use Symfony\Component\HttpKernel\EventListener\RouterListener; use Symfony\Component\HttpKernel\EventListener\RouterListener;
use Symfony\Component\HttpKernel\RequestContext as KernelRequestContext;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent; use Symfony\Component\HttpKernel\Event\GetResponseEvent;
@ -19,6 +20,13 @@ use Symfony\Component\Routing\RequestContext;
class RouterListenerTest extends \PHPUnit_Framework_TestCase class RouterListenerTest extends \PHPUnit_Framework_TestCase
{ {
private $kernelContext;
public function setUp()
{
$this->kernelContext = $this->getMock('Symfony\Component\HttpKernel\RequestContext', array(), array(), '', false);
}
/** /**
* @dataProvider getPortData * @dataProvider getPortData
*/ */
@ -34,7 +42,7 @@ class RouterListenerTest extends \PHPUnit_Framework_TestCase
->method('getContext') ->method('getContext')
->will($this->returnValue($context)); ->will($this->returnValue($context));
$listener = new RouterListener($urlMatcher); $listener = new RouterListener($urlMatcher, $this->kernelContext);
$event = $this->createGetResponseEventForUri($uri); $event = $this->createGetResponseEventForUri($uri);
$listener->onKernelRequest($event); $listener->onKernelRequest($event);
@ -72,7 +80,7 @@ class RouterListenerTest extends \PHPUnit_Framework_TestCase
*/ */
public function testInvalidMatcher() public function testInvalidMatcher()
{ {
new RouterListener(new \stdClass()); new RouterListener(new \stdClass(), $this->kernelContext);
} }
public function testRequestMatcher() public function testRequestMatcher()
@ -87,7 +95,7 @@ class RouterListenerTest extends \PHPUnit_Framework_TestCase
->with($this->isInstanceOf('Symfony\Component\HttpFoundation\Request')) ->with($this->isInstanceOf('Symfony\Component\HttpFoundation\Request'))
->will($this->returnValue(array())); ->will($this->returnValue(array()));
$listener = new RouterListener($requestMatcher, new RequestContext()); $listener = new RouterListener($requestMatcher, $this->kernelContext, new RequestContext());
$listener->onKernelRequest($event); $listener->onKernelRequest($event);
} }
@ -108,7 +116,7 @@ class RouterListenerTest extends \PHPUnit_Framework_TestCase
->method('getContext') ->method('getContext')
->will($this->returnValue($context)); ->will($this->returnValue($context));
$listener = new RouterListener($requestMatcher, new RequestContext()); $listener = new RouterListener($requestMatcher, $this->kernelContext, new RequestContext());
$listener->onKernelRequest($event); $listener->onKernelRequest($event);
// sub-request with another HTTP method // sub-request with another HTTP method

View File

@ -17,12 +17,27 @@ use Symfony\Component\HttpFoundation\Response;
class FragmentHandlerTest extends \PHPUnit_Framework_TestCase 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 * @expectedException \InvalidArgumentException
*/ */
public function testRenderWhenRendererDoesNotExist() public function testRenderWhenRendererDoesNotExist()
{ {
$handler = new FragmentHandler(); $handler = new FragmentHandler($this->context);
$handler->render('/', 'foo'); $handler->render('/', 'foo');
} }
@ -72,9 +87,8 @@ class FragmentHandlerTest extends \PHPUnit_Framework_TestCase
call_user_func_array(array($e, 'with'), $arguments); call_user_func_array(array($e, 'with'), $arguments);
} }
$handler = new FragmentHandler(); $handler = new FragmentHandler($this->context);
$handler->addRenderer($renderer); $handler->addRenderer($renderer);
$handler->setRequest(Request::create('/'));
return $handler; return $handler;
} }

View File

@ -238,6 +238,20 @@ class HttpKernelTest extends \PHPUnit_Framework_TestCase
$this->assertEquals($response, $capturedResponse); $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) protected function getResolver($controller = null)
{ {
if (null === $controller) { if (null === $controller) {