8000 merged branch fabpot/request-stack (PR #8904) · symfony/symfony@599c865 · GitHub
[go: up one dir, main page]

Skip to content

Commit 599c865

Browse files
committed
merged branch fabpot/request-stack (PR #8904)
This PR was merged into the master branch. Discussion ---------- Synchronized Service alternative, backwards compatible This is a rebased version #7707. | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #7707 | License | MIT | Doc PR | symfony/symfony-docs#2956 Todo/Questions - [x] do we deprecate the synchronize feature (and removed it in 3.0)? - [x] deal with BC for listeners - [x] rename RequestContext as we already have a class with the same name in the Routing component? - [x] move RequestStack and RequestContext to HttpFoundation? - [x] update documentation Prototype for introducing a ``RequestContext`` in HttpKernel. This PR keeps the synchronized services feature, however introduces a ``RequestContext`` object additionally, that allows to avoid using synchronized service when injecting ``request_context`` instead of ``request`` into a service. The FrameworkBundle is modified such that it does not depend on synchronized services anymore. Users however can still depend on ``request``, activating the synchronized services feature. Features: * Introduced ``REQUEST_FINSHED`` (name is up for grabs) event to handle global state and environment cleanup that should not be done in ``RESPONSE``. Called in both exception or success case correctly * Changed listeners that were synchronized before to using ``onKernelRequestFinished`` and ``RequestContext`` to reset to the parent request (RouterListener + LocaleListener). * Changed ``FragmentHandler`` to use the RequestContext. Added some more tests for this class. * ``RequestStack`` is injected into the ``HttpKernel`` to handle the request finished event and push/pop the stack with requests. Todos: * RequestContext name clashes with Routing components RequestContext. Keep or make unique? * Name for Kernel Request Finished Event could be improved. Commits ------- 1b2ef74 [Security] made sure that the exception listener is always removed from the event dispatcher at the end of the request b1a062d moved RequestStack to HttpFoundation and removed RequestContext 93e60ea [HttpKernel] modified listeners to be BC with Symfony <2.4 018b719 [HttpKernel] tweaked the code f9b10ba [HttpKernel] renamed the kernel finished event a58a8a6 [HttpKernel] changed request_stack to a private service c55f1ea added a RequestStack class
2 parents 08ec911 + 1b2ef74 commit 599c865

24 files changed

+530
-138
lines changed

src/Symfony/Bridge/Twig/Tests/Extension/HttpKernelExtensionTest.php

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
use Symfony\Bridge\Twig\Extension\HttpKernelExtension;
1515
use Symfony\Bridge\Twig\Tests\TestCase;
1616
use Symfony\Component\HttpFoundation\Request;
17+
use Symfony\Component\HttpFoundation\Response;
18+
use Symfony\Component\HttpFoundation\RequestStack;
1719
use Symfony\Component\HttpKernel\Fragment\FragmentHandler;
1820

1921
class HttpKernelExtensionTest extends TestCase
@@ -23,13 +25,30 @@ class HttpKernelExtensionTest extends TestCase
2325
*/
2426
public function testFragmentWithError()
2527
{
26-
$kernel = $this->getFragmentHandler($this->throwException(new \Exception('foo')));
28+
$renderer = $this->getFragmentHandler($this->throwException(new \Exception('foo')));
2729

28-
$loader = new \Twig_Loader_Array(array('index' => '{{ fragment("foo") }}'));
29-
$twig = new \Twig_Environment($loader, array('debug' => true, 'cache' => false));
30-
$twig->addExtension(new HttpKernelExtension($kernel));
30+
$this->renderTemplate($renderer);
31+
}
32+
33+
public function testRenderFragment()
34+
{
35+
$renderer = $this->getFragmentHandler($this->returnValue(new Response('html')));
36+
37+
$response = $this->renderTemplate($renderer);
3138

32-
$this->renderTemplate($kernel);
39+
$this->assertEquals('html', $response);
40+
}
41+
42+
public function testUnknownFragmentRenderer()
43+
{
44+
$context = $this->getMockBuilder('Symfony\\Component\\HttpFoundation\\RequestStack')
45+
->disableOriginalConstructor()
46+
->getMock()
47+
;
48+
$renderer = new FragmentHandler(array(), false, $context);
49+
50+
$this->setExpectedException('InvalidArgumentException', 'The "inline" renderer does not exist.');
51+
$renderer->render('/foo');
3352
}
3453

3554
protected function getFragmentHandler($return)
@@ -38,8 +57,14 @@ protected function getFragmentHandler($return)
3857
$strategy->expects($this->once())->method('getName')->will($this->returnValue('inline'));
3958
$strategy->expects($this->once())->method('render')->will($return);
4059

41-
$renderer = new FragmentHandler(array($strategy));
42-
$renderer->setRequest(Request::create('/'));
60+
$context = $this->getMockBuilder('Symfony\\Component\\HttpFoundation\\RequestStack')
61+
->disableOriginalConstructor()
62+
->getMock()
63+
;
64+
65+
$context->expects($this->any())->method('getCurrentRequest')->will($this->returnValue(Request::create('/')));
66+
67+
$renderer = new FragmentHandler(array($strategy), false, $context);
4368

4469
return $renderer;
4570
}

src/Symfony/Bundle/FrameworkBundle/Resources/config/fragment_renderer.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
<service id="fragment.handler" class="%fragment.handler.class%">
1818
<argument type="collection" />
1919
<argument>%kernel.debug%</argument>
20-
<call method="setRequest"><argument type="service" id="request" on-invalid="null" strict="false" /></call>
20+
<argument type="service" id="request_stack" />
2121
</service>
2222

2323
<service id="fragment.renderer.inline" class="%fragment.renderer.inline.class%">

src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@
9494
<argument type="service" id="router" />
9595
<argument type="service" id="router.request_context" on-invalid="ignore" />
9696
<argument type="service" id="logger" on-invalid="ignore" />
97-
<call method="setRequest"><argument type="service" id="request" on-invalid="null" strict="false" /></call>
97+
<argument type="service" id="request_stack" />
9898
</service>
9999
</services>
100100
</container>

src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
<parameter key="cache_clearer.class">Symfony\Component\HttpKernel\CacheClearer\ChainCacheClearer</parameter>
1313
<parameter key="file_locator.class">Symfony\Component\HttpKernel\Config\FileLocator</parameter>
1414
<parameter key="uri_signer.class">Symfony\Component\HttpKernel\UriSigner</parameter>
15+
<parameter key="request_stack.class">Symfony\Component\HttpFoundation\RequestStack</parameter>
1516
</parameters>
1617

1718
<services>
@@ -23,8 +24,11 @@
2324
<argument type="service" id="event_dispatcher" />
2425
<argument type="service" id="service_container" />
2526
<argument type="service" id="controller_resolver" />
27+
<argument type="service" id="request_stack" />
2628
</service>
2729

30+
<service id="request_stack" class="%request_stack.class%" />
31+
2832
<service id="cache_warmer" class="%cache_warmer.class%">
2933
<argument type="collection" />
3034
</service>

src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
<tag name="kernel.event_subscriber" />
3939
<argument>%kernel.default_locale%</argument>
4040
<argument type="service" id="router" on-invalid="ignore" />
41-
<call method="setRequest"><argument type="service" id="request" on-invalid="null" strict="false" /></call>
41+
<argument type="service" id="request_stack" />
4242
</service>
4343
</services>
4444
</container>

src/Symfony/Component/HttpFoundation/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ CHANGELOG
44
2.4.0
55
-----
66

7+
* added RequestStack
78
* added Request::getEncodings()
89
* added accessors methods to session handlers
910

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\HttpFoundation;
13+
14+
/**
15+
* Request stack that controls the lifecycle of requests.
16+
*
17+
* @author Benjamin Eberlei <kontakt@beberlei.de>
18+
*/
19+
class RequestStack
20+
{
21+
/**
22+
* @var Request[]
23+
*/
24+
private $requests = array();
25+
26+
/**
27+
* Pushes a Request on the stack.
28+
*
29+
* This method should generally not be called directly as the stack
30+
* management should be taken care of by the application itself.
31+
*/
32+
public function push(Request $request)
33+
{
34+
$this->requests[] = $request;
35+
}
36+
37+
/**
38+
* Pops the current request from the stack.
39+
*
40+
* This operation lets the current request go out of scope.
41+
*
42+
* This method should generally not be called directly as the stack
43+
* management should be taken care of by the application itself.
44+
*
45+
* @return Request
46+
*/
47+
public function pop()
48+
{
49+
if (!$this->requests) {
50+
throw new \LogicException('Unable to pop a Request as the stack is already empty.');
51+
}
52+
53+
return array_pop($this->requests);
54+
}
55+
56+
/**
57+
* @return Request|null
58+
*/
59+
public function getCurrentRequest()
60+
{
61+
return end($this->requests) ?: null;
62+
}
63+
64+
/**
65+
* Gets the master Request.
66+
*
67+
* Be warned that making your code aware of the master request
68+
* might make it un-compatible with other features of your framework
69+
* like ESI support.
70+
*
71+
* @return Request|null
72+
*/
73+
public function getMasterRequest()
74+
{
75+
if (!$this->requests) {
76+
return null;
77+
}
78+
79+
return $this->requests[0];
80+
}
81+
82+
/**
83+
* Returns the parent request of the current.
84+
*
85+
* Be warned that making your code aware of the parent request
86+
* might make it un-compatible with other features of your framework
87+
* like ESI support.
88+
*
89+
* If current Request is the master request, it returns null.
90+
*
91+
* @return Request|null
92+
*/
93+
public function getParentRequest()
94+
{
95+
$pos = count($this->requests) - 2;
96+
97+
if (!isset($this->requests[$pos])) {
98+
return null;
99+
}
100+
101+
return $this->requests[$pos];
102+
}
103+
}

src/Symfony/Component/HttpKernel/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
2.4.0
5+
-----
6+
7+
* added the KernelEvents::FINISH_REQUEST event
8+
49
2.3.0
510
-----
611

src/Symfony/Component/HttpKernel/DependencyInjection/ContainerAwareHttpKernel.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\HttpKernel\DependencyInjection;
1313

1414
use Symfony\Component\HttpFoundation\Request;
15+
use Symfony\Component\HttpFoundation\RequestStack;
1516
use Symfony\Component\HttpKernel\HttpKernelInterface;
1617
use Symfony\Component\HttpKernel\HttpKernel;
1718
use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface;
@@ -35,10 +36,11 @@ class ContainerAwareHttpKernel extends HttpKernel
3536
* @param EventDispatcherInterface $dispatcher An EventDispatcherInterface instance
3637
* @param ContainerInterface $container A ContainerInterface instance
3738
* @param ControllerResolverInterface $controllerResolver A ControllerResolverInterface instance
39+
* @param RequestStack $requestStack A stack for master/sub requests
3840
*/
39-
public function __construct(EventDispatcherInterface $dispatcher, ContainerInterface $container, ControllerResolverInterface $controllerResolver)
41+
public function __construct(EventDispatcherInterface $dispatcher, ContainerInterface $container, ControllerResolverInterface $controllerResolver, RequestStack $requestStack = null)
4042
{
41-
parent::__construct($dispatcher, $controllerResolver);
43+
parent::__construct($dispatcher, $controllerResolver, $requestStack);
4244

4345
$this->container = $container;
4446

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\HttpKernel\Event;
13+
14+
/**
15+
* Triggered whenever a request is fully processed.
16+
*
17+
* @author Benjamin Eberlei <kontakt@beberlei.de>
18+
*/
19+
class FinishRequestEvent extends KernelEvent
20+
{
21+
}

src/Symfony/Component/HttpKernel/EventListener/LocaleListener.php

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,55 +12,100 @@
1212
namespace Symfony\Component\HttpKernel\EventListener;
1313

1414
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
15+
use Symfony\Component\HttpKernel\Event\FinishRequestEvent;
1516
use Symfony\Component\HttpKernel\KernelEvents;
17+
use Symfony\Component\HttpFoundation\RequestStack;
1618
use Symfony\Component\HttpFoundation\Request;
1719
use Symfony\Component\Routing\RequestContextAwareInterface;
1820
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
1921

2022
/**
2123
* Initializes the locale based on the current request.
2224
*
25+
* This listener works in 2 modes:
26+
*
27+
* * 2.3 compatibility mode where you must call setRequest whenever the Request changes.
28+
* * 2.4+ mode where you must pass a RequestStack instance in the constructor.
29+
*
2330
* @author Fabien Potencier <fabien@symfony.com>
2431
*/
2532
class LocaleListener implements EventSubscriberInterface
2633
{
2734
private $router;
2835
private $defaultLocale;
36+
private $requestStack;
2937

30-
public function __construct($defaultLocale = 'en', RequestContextAwareInterface $router = null)
38+
/**
39+
* RequestStack will become required in 3.0.
40+
*/
41+
public function __construct($defaultLocale = 'en', RequestContextAwareInterface $router = null, RequestStack $requestStack = null)
3142
{
3243
$this->defaultLocale = $defaultLocale;
44+
$this->requestStack = $requestStack;
3345
$this->router = $router;
3446
}
3547

48+
/**
49+
* Sets the current Request.
50+
*
51+
* This method was used to synchronize the Request, but as the HttpKernel
52+
* is doing that automatically now, you should never be called it directly.
53+
* It is kept public for BC with the 2.3 version.
54+
*
55+
* @param Request|null $request A Request instance
56+
*
57+
* @deprecated Deprecated since version 2.4, to be removed in 3.0.
58+
*/
3659
public function setRequest(Request $request = null)
3760
{
3861
if (null === $request) {
3962
return;
4063
}
4164

42-
if ($locale = $request->attributes->get('_locale')) {
43-
$request->setLocale($locale);
44-
}
45-
46-
if (null !== $this->router) {
47-
$this->router->getContext()->setParameter('_locale', $request->getLocale());
48-
}
65+
$this->setLocale($request);
66+
$this->setRouterContext($request);
4967
}
5068

5169
public function onKernelRequest(GetResponseEvent $event)
5270
{
5371
$request = $event->getRequest();
5472
$request->setDefaultLocale($this->defaultLocale);
5573

56-
$this->setRequest($request);
74+
$this->setLocale($request);
75+
$this->setRouterContext($request);
76+
}
77+
78+
public function onKernelFinishRequest(FinishRequestEvent $event)
79+
{
80+
if (null === $this->requestStack) {
81+
throw new \LogicException('You must pass a RequestStack.');
82+
}
83+
84+
if (null !== $parentRequest = $this->requestStack->getParentRequest()) {
85+
$this->setRouterContext($parentRequest);
86+
}
87+
}
88+
89+
private function setLocale(Request $request)
90+
{
91+
if ($locale = $request->attributes->get('_locale')) {
92+
$request->setLocale($locale);
93+
}
94+
}
95+
96+
private function setRouterContext(Request $request)
97+
{
98+
if (null !== $this->router) {
99+
$this->router->getContext()->setParameter('_locale', $request->getLocale());
100+
}
57101
}
58102

59103
public static function getSubscribedEvents()
60104
{
61105
return array(
62106
// must be registered after the Router to have access to the _locale
63107
KernelEvents::REQUEST => array(array('onKernelRequest', 16)),
108+
KernelEvents::FINISH_REQUEST => array(array('onKernelFinishRequest', 0)),
64109
);
65110
}
66111
}

0 commit comments

Comments
 (0)
0