How to Get The Request / Query Parameters in Symfony?
When you first start with any new framework / language it can be extremely frustrating to not be able to do the things you usually take for granted.
This is a common source of annoyance for developers new to Symfony, who I have worked with before. They are perfectly capable of doing a given task, just not the "Symfony way".
One such task would be to get 'things' from the URL.
Let's imagine for the moment that we have a URL such as:
http://mysite.com/?user=chris&age=33
Depending on how long you've been working with PHP, you may be used to grabbing these values by doing something like:
$user = $_GET["user"];
And this works in Symfony too.
But Symfony aims to make your development life that much easier by providing you with an object representing the superglobal variables ($_GET
, $_POST
, etc) that you may be used to, wrapping them in the Request
class.
Now, I totally get it: you already know one way of doing things, and here's Symfony changing something that already works.
However, hopefully, after this short tour through getting information from Symfony's request (and query) parameters, you will see how this can make your life easier.
Ok, so back to our URL:
http://mysite.com/?user=chris&age=33
We now know that we shouldn't be using $_GET['user']
, but if we don't do it that way, how should we do it?
This one's easy enough:
<?php
namespace AppBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
class DemoController extends Controller
{
/**
* @Route("/", name="index")
*/
public function indexAction(Request $request)
{
// GET http://mysite.com/?user=tim&age=44
dump($request->query->get('user'));
dump($request->query->get('age'));
return $this->render('::base.html.twig');
}
/**
* @Route("/demo", name="demo")
*/
public function demoAction(Request $request)
{
// GET http://mysite.com/demo?user=chris&age=33
dump($request->query->get('user'));
dump($request->query->get('age'));
return $this->render('::base.html.twig');
}
Ok, so two examples, both showing the same thing.
I wanted to show that it doesn't matter if you have something after the slash (/
vs /demo
), this still behaves the same way.
We need to inject the Request
object into your controller action method signature:
public function demoAction(Request $request)
And to use this, we must use
this:
use Symfony\Component\HttpFoundation\Request;
After that, we want query parameters, so we use $request->query
.
The syntax here may be a little confusing.
$request
- fair enough, it looks like a typical variable (starts with a $
sign).
->query
- hmm, why is this not a function (->query()
)? Well, it's a public
property of the Request
class, so it can be accessed directly.
Then, why do we have all these methods available such as:
all
get
has
And a whole bunch more? Because query
is a ParameterBag
object.
Nicer URLs
The thing is, http://mysite.com/?user=chris&age=33
looks... ugly.
It would be much nicer if we could have a fancy looking URL, something like:
http://mysite.com/demo/user/chris/age/33
Again, Symfony makes this easy for us.
We define the URL in our Route
annotation to look how we want it, and use {placeholder}
values to indicate parts of the URL that are wildcards - things that will change.
Changing our URL is easy enough:
http://mysite.com/demo/user/{user}/age/{age}
And by adding parameters of the same name to our controller action, we can immediately gain access to whatever is submitted in place of the placeholders:
class DemoController extends Controller
{
/**
* @Route("/user/{user}/age/{age}", name="index")
*/
public function indexAction(Request $request, $user, $age)
{
dump([$user, $age]);
return $this->render('::base.html.twig');
}
The interesting thing here is that we are no longer using $request->query
.
Instead, we have now started using $request->attributes
.
The $attributes
property of the Request
class is also public
. Therefore the syntax is identical to the $request->query
setup we used earlier.
However, there is no direct correlation between attributes
and the PHP superglobals. This is something that is specific to the Request
class. This is just another way that Symfony tries to make your life as a developer that little bit easier.
How To Get The Current Route In Symfony?
Interestingly, the $request->attributes
also holds information about your route, controller action, and more. If you have ever wondered - how to get the current route in Symfony - then this is where you would look.
Another way to get access to the current route is to simply inject it:
class DemoController extends Controller
{
/**
* @Route("/anything/here", name="your_route_name")
*/
public function indexAction(Request $request, $_route)
{
dump($_route); // 'your_route_name'
return $this->render('::base.html.twig');
}
This covers two very common use cases, but there are more.
Request Request
Probably the most confusingly named thing that we use most frequently in Symfony is $request->request
.
From the Request
class (or the $request
object), we want to access the request body parameters, or what you may simply know as the $_POST
superglobal.
Honestly, the first few times you try to verbally explain $request->request
to someone new to Symfony and they will 99/100 times simply type in $request
and think you have needlessly repeated yourself.
Once you get your head around the naming, the good news is this behaves entirely the same as what we have seen already - it is public property of type ParameterBag
, named request
, that lives on the Request
class... or:
$request->request->all()
For example. And of course, all the methods from the ParameterBag
are available, as already discussed.
Form Gotcha
Whilst it's not the normal way of working with Symfony's form, it is worth pointing out that if you were to try to get access to the form submission using $request->request
, that the form data itself will all be held under one key.
An example illustrates this better:
class DemoController extends Controller
{
/**
* @Route("/", name="index")
*/
public function indexAction(Request $request)
{
$form = $this->createFormBuilder()
->add('someInput')
->add('anotherInput')
->add('Submit', SubmitType::class)
->getForm();
// snip
}
If you were to submit some data from this form, you may try to grab the input something like:
$request->request->get('anotherInput');
But that wouldn't work.
Forms will submit all their data under one key.
Thankfully, gaining accesss is still a one liner, using PHP5.4's array dereferencing syntax:
dump($request->request->get('form')['anotherInput']);
When accepting form input you will implictly gain a CSRF token. The docs have a good article on How To Implement CSRF Protection.
My Friend JSON
Probably the most common stumbling block for developers new to Symfony is how to handle JSON.
There's a bunch of reasons for this, and what I am about to show you is not a real world solution. But stick with me for the moment, then I will offer some alternatives.
Let's say we have some JSON:
{ "user": "chris", "age": 33 }
Do we need to do anything special to our controller action or route to handle this?
Thankfully, no.
However, it won't quite work as expected. Sadly.
If we send in a POST
request with the Content-type
header set to application/json
, and the request body set to our JSON string, then our Symfony controller will receive the data. Good news.
It won't be immediately usable though. Which is bad news.
The data will arrive as a raw string. Why bother setting the Content-type
then? Ha, you got me, there's no real need - other than it is technically correct. You could send text/plain
instead. It doesn't make any difference.
Assuming we sent in some valid JSON, we can do the plain old PHP way of decoding it:
class DemoController extends Controller
{
/**
* @Route("/", name="index")
*/
public function indexAction(Request $request)
{
$data = json_decode($request->getContent(), true);
// snip
}
In case you aren't a regular json_decode
r, the true
argument tells PHP to return us an associative array. In other words, convert the JSON to a typical PHP array.
Once we have the data we can do with it whatever we like.
Now, I did say that this way isn't good for the real world.
If you have more than one controller action where you accept JSON then you will want a more robust solution. One such solution is described here, but in truth, I'd recommend you consider a more robust solution.
Taking This Further
This is all well and good but it's also fairly naive.
We haven't covered any cases where input may be invalid. And if you ever let your applications out into the world wild web, you'll know my WWW was no typo.
We'll take a look at adding a little validation in the next video.
Before I wrap this up, if you ever get confused I suggest to dump
out the $request
object and poke around until you find what you are after. Also, as shown in the video, the profiler is your friend.