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_decoder, 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.

Episodes

# Title Duration
1 How to Get The Request / Query Parameters in Symfony? 07:06
2 Straightforward Routing Requirements 04:36
3 Can Query Parameters Use Annotations? 06:42