Dealing with the request in the service container is difficult to say the least. Why is it difficult to inject the request into a service? Because you can have more than one request in a single PHP process (think sub-requests.) So, during the lifetime of the container, the request instance changes.
Let's take this simple service as an example:
1 2 3 4 5 6 7 8 9
use Symfony\Component\HttpFoundation\Request;
class FooService
{
public function __construct(Request $request)
{
$this->request = $request;
}
}
Which request instance the container is going to inject into the foo service when you will ask for it in your application? Well, if you do not use sub-requests, the foo service will consistently get the master request. But if you have some sub-requests, the foo constructor might get one of the sub-request instances depending on when you get the service for the first time. And as soon as the service is created, the request won't change anymore, even when entering or leaving a sub-request. And what if you try to get the service before request handling (like in a CLI command)? You will get an exception.
Before Symfony 2.3, configuring such a service so that it behaves correctly was possible but difficult. In Symfony 2.3, the synchronized services feature makes things much easier. As a matter of fact, some of the most complex features of the container were introduced just to be able to deal correctly with the request service like scopes, strict services, and synchronized services. So many features just to be able to deal with the request service because it is the only one to have a special behavior.
But the real problem is that the request is not a service; the request is a value object... like the response. And as you know, the response instance has never been accessible from the container, and that never caused any problems.
In Symfony 3.0, we will fix the problem once and for all by removing the
request service from the container. But how can a service depend on the request
then? Use the new RequestStack
object.
As of Symfony 2.4, the best practice is to never inject the request
service, but to inject the request_stack
service instead:
1 2 3 4 5 6 7 8 9
use Symfony\Component\HttpFoundation\RequestStack;
class FooService
{
public function __construct(RequestStack $requestStack)
{
$this->requestStack = $requestStack;
}
}
Then, whenever you need to get the current request instance, call
$requestStack->getCurrentRequest()
. Straightforward.
Note
Outside the handling of a request, $requestStack->getCurrentRequest()
returns null
.
Of course, if the framework gives you a request instance, just use it. In a
controller for instance, don't inject the request stack, use the Request
type-hint on an action method argument instead:
1 2 3 4 5 6 7
class FooController
{
public function indexAction(Request $request)
{
$request->...();
}
}
Tip
The request stack also gives you access to the master request via the
getMasterRequest()
method. Please, use this carefully as it makes your
sub-requests incompatible with ESIs/reverse proxies (where a sub-request is
also a master request).
This is great news! I'm often dependant on the Request with service annotated controllers so this a great feature. Not to mention that it also solves the problem of having a non-service as service in there.
Just 1 thing I wonder about, you still use Request as typehint, will that typehint contain the getCurrentRequest() etc? It doesn't really make sense to have that on my Request::getCurrentRequest(), I would prefer to use the RequestStack as typehint due to code completion. Or is this not-done?
#1 In controllers, you can receive the request as a parameter. From this post, what I understand it that you'll still receive the request in those cases. It has only been changed in the DIC, where they added the 'request_stack' service (with the getCurrentRequest() method). In 3.0 it's planned to remove the 'request' service, because it shouldn't be a service, but a container, such as 'request_stack', should.
Exactly. The Request stack is a container for all the Request value objects that may originate during the lifetime of the php process. That stack may contain several Request objects. Before 2.4, it was extremedly difficult to guess which is the right object to inject in your service, but from now on, that's not an issue anymore.
But the Request class will continue to exist, and it will be what you use to extract the data from the user request. The RequestStack it's just the provider for that value object.
Brilliant and clean solution!
@Lynn
no, that part about the typehint keeps the same and is used the same as before, the only thing that changes is the request stack for services.
This is a perfect example on how hard it is to do things right the very first time you implement something complex, no matter how hard you try or smart you are.
Looks like a great solution.
This is such a simple solution, but sooo easy to use.
Thanks a lot for this update which will be appreciated by all Sf2 developers I'm sure ;-)
Nice work. A nice and clean solution for a bunch of problems.
How easy is it to push temporary Requests onto the stack and pop them off?
My use case being a multi-tenant application where multiple emails are generated within a single request. Each recipient's email is customised to their install so the UrlGenerator needs to generate links from multiple Request's
This is wonderful. It will solve a lot of things and connection times would also be faster.
And on second thought, it would probably be better to just clone the UrlGenerator + RequestContext and modify it directly.
Should dived into the code first :)
Amazing, great!
So, will the scope "request" be removed with 3.0, too?