Buy Access to Course
11.

MapQueryParameter & Request Payload

|

Share this awesome video!

|

Keep on Learning!

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

The next new stuffs I want to talk about are related to grabbing data from the request. That's normally... kind of boring work. But the new features are pretty darn cool.

The MapQueryParameter Attribute

For example, add a ?query=banana to the URL. To fetch that in our controller, we would historically type-hint an argument with Request then grab it from there. And while that still works, we can now add a ?string $query argument. To tell Symfony that this is something it should grab from a query parameter, add an attribute in front: #[MapQueryParameter].

That's it! Dump $query to prove it works.

64 lines | src/Controller/VinylController.php
// ... lines 1 - 11
use Symfony\Component\HttpKernel\Attribute\MapQueryParameter;
// ... lines 13 - 15
class VinylController extends AbstractController
{
// ... lines 18 - 24
public function homepage(
#[MapQueryParameter] string $query = '',
): Response
{
dump($query);
// ... lines 30 - 42
}
// ... lines 44 - 62
}

Back in the web browser world, refresh. In the web debug toolbar... got it!

Validation from the Type-Hint

The attribute does also have some options. For example, if your query parameter is called something different from your argument, you could put that here.

And beyond just grabbing the value from the request, this system also performs validation. Watch: duplicate this and add an int $page = 1 argument. Oh, and I meant to make the $query argument optional so it doesn't need to be on the URL. Below, dump $page.

65 lines | src/Controller/VinylController.php
// ... lines 1 - 15
class VinylController extends AbstractController
{
// ... lines 18 - 24
public function homepage(
#[MapQueryParameter] string $query = '',
#[MapQueryParameter] int $page = 1,
): Response
{
dump($query, $page);
// ... lines 31 - 43
}
// ... lines 45 - 63
}

Ok, if we add ?page=3 to the URL... no surprise: it dumps 3. But it is nice that we get an integer 3: not a string. Now try page=banana. A 404! The system sees we have an int type and performs validation.

The filter_var() Function

This entire system is handled by something called the QueryParameterValueResolver. So if you really want to dig in, check that class. Internally, it uses a PHP function called filter_var() to do the validation. This is not a function I'm very familiar with, but it's quite powerful. You pass it a value, one or more filters... and it tells you whether that value satisfies those filters. You can also pass options to control the filters.

If you don't do anything extra, the system reads our int type-hint, and passes a filter to filter_var() that requires it to be an int. That's why this fails.

Validating an int is in a Range

But we can get fancier. Add an argument called $limit that defaults to 10. Dump this below. But I want the limit to be between 1 and 10. To force that, pass two options special to filter_var: min_range set to 1 and max_range set to 10.

66 lines | src/Controller/VinylController.php
// ... lines 1 - 15
class VinylController extends AbstractController
{
// ... lines 18 - 24
public function homepage(
#[MapQueryParameter] string $query = '',
#[MapQueryParameter] int $page = 1,
#[MapQueryParameter(options: ['min_range' => 1, 'max_range' => 10])] int $limit = 10,
): Response
{
dump($query, $page, $limit);
// ... lines 32 - 44
}
// ... lines 46 - 64
}

Let's try it! Say ?limit=3. That works like we expect. But when we try limit=13. filter_var() fails and we get a 404! I love that!

Grabbing Array Query Parameters

This can even be used to handle arrays. Copy and create one more argument: an array of $filters that defaults to an empty array. Dump that.

67 lines | src/Controller/VinylController.php
// ... lines 1 - 15
class VinylController extends AbstractController
{
// ... lines 18 - 24
public function homepage(
#[MapQueryParameter] string $query = '',
#[MapQueryParameter] int $page = 1,
#[MapQueryParameter(options: ['min_range' => 1, 'max_range' => 10])] int $limit = 10,
#[MapQueryParameter] array $filters = [],
): Response
{
dump($query, $page, $limit, $filters);
// ... lines 33 - 45
}
// ... lines 47 - 65
}

At the browser, add ?filters[] equals banana, &filters[] equals apple. Check out that array in the web debug toolbar! It also works for associative arrays: add foo and bar between the []. Yup! An associative array.

It's just a really well-designed feature for fetching query parameters.

Request Body

Also, if you need to fetch the body of a request, in Symfony 6.3, there's a new method called $request->getPayload(). Building an API? When your client sends JSON in the body, use $request->getPayload() to decode that into an associative array. That's nice! But also, if your user submits a normal HTML form, $request->getPayload() works there too. It detects that an HTML form is being submitted and decodes the $_POST data to an array. So no matter if you're using an API or a normal form, we have a uniform method to fetch the payload of the request. Small, but nice.

MapRequestPayload

Speaking of JSON, it's also common to use the serializer to deserialize the payload into an object. That relates to another new feature called #[MapRequestPayload].

In this case, __invoke is the controller action. This says: take the JSON from the request and deserialize it into a ProductReviewDto, which is the example class above. After sending the JSON through the serializer, it even performs validation. So another well-thought-out feature.

Ok, that's enough for request stuff! Next up, let's test drive a new feature in 6.4: the ability to profile console commands.