Filter Class Arguments

This Chapter isn't
quite ready...

Rest assured, the gnomes are hard at work
completing this video!

Browse Tutorials

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

Login Subscribe

If we enter an invalid from date in the URL, it's simply ignored and we return everything. We did that on purpose in DailyStatsDateFilter. But another option is to return a 400 status code so that the user knows they messed up. How could we do that?

Returning a 400 any time you want

It's pretty simple actually! Symfony has a bunch of built-in exception classes that map to various status codes. For example, before this, we could say, if not $fromDate, then throw new BadRequestHttpException.

That exception - which you can throw whenever you want - maps to a 400 status code. And there are a bunch of other ones in that same directory for other status codes. Pass this a message:

Invalid from date format.

Cool! I know, I don't need this other if statement down here, but I'll leave.

Anyways, let's see what this looks like. Refresh with the bad date and... cool! A nice JSON error message with: "Invalid from date format" and a 400 status code. On production, the stack trace wouldn't be here, but the user would see this message.

ApiFilter arguments Option

Let's do an extra challenge. Pretend that we want to make this behavior - whether to throw a 400 error on an invalid date format - something that is configurable when we activate the filter.

Okay, this may not be needed unless you're building a reusable filter, but it will reveal some cool stuff about how filters work.

The @ApiFilter() annotation has several options that we can pass to it. Hold Command or Control and click to jump into that core annotation class. Yep! All of these public properties are options that we can technically pass to this annotation. But for the purposes of building a custom filter, the only options that really matter are arguments and properties. We'll talk about $properties later.

Close that class. Try this: add arguments={} and then pass a new argument called throwOnInvalid set to true.

What does this do? I don't know! Let's refresh and see what happens. Ah, error!

Class DailyStatsDateFilter does not have argument $throwOnInvalid

arguments Option Maps to Constructor Arguments

API platform does a cool, but kind of strange thing: if you pass an arguments option to a filter, it tries to pass that argument - by name - to the constructor of your filter.

Check it out: in DailyStatsDateFilter, add a constructor: public function __construct() with bool and then copy the name of the argument and paste it: $throwOnInvalid. Default this to false in case someone uses the filter without that option.

Next, hit Alt+Enter and go to initialize properties to create that property and set it. Finally, in the if statement, add if not $fromDate and $this->throwOnInvalid, then we want to throw that exception.

Let's try it! Move back over, refresh and.... got it! We're back to the 400 status code.

The properties Option

So it's kind of weird, but any arguments on the annotation map to constructor arguments by name. Oh, and the one other option that we could pass to the annotation is properties={} if you want to configure that this is supposed to use a certain set of properties. And if you ever put @ApiFilter above a property instead of on top of the class, this properties option is automatically set for you.

Either way, if the properties option is set, it's passed to your filter as an argument called $properties.

So... cool! We now know how we can pass configuration to a filter. But there's more going on than it seems. Next: we'll reveal something that will make our filters a lot more powerful.

Leave a comment!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.2.5",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "api-platform/core": "^2.1", // v2.5.7
        "doctrine/annotations": "^1.0", // 1.10.4
        "doctrine/doctrine-bundle": "^2.0", // 2.1.2
        "doctrine/doctrine-migrations-bundle": "^3.0", // 3.0.1
        "doctrine/orm": "^2.4.5", // v2.7.3
        "nelmio/cors-bundle": "^2.1", // 2.1.0
        "nesbot/carbon": "^2.17", // 2.39.1
        "phpdocumentor/reflection-docblock": "^3.0 || ^4.0", // 4.3.4
        "ramsey/uuid-doctrine": "^1.6", // 1.6.0
        "symfony/asset": "5.1.*", // v5.1.5
        "symfony/console": "5.1.*", // v5.1.5
        "symfony/debug-bundle": "5.1.*", // v5.1.5
        "symfony/dotenv": "5.1.*", // v5.1.5
        "symfony/expression-language": "5.1.*", // v5.1.5
        "symfony/flex": "^1.1", // v1.9.6
        "symfony/framework-bundle": "5.1.*", // v5.1.5
        "symfony/http-client": "5.1.*", // v5.1.5
        "symfony/monolog-bundle": "^3.4", // v3.5.0
        "symfony/security-bundle": "5.1.*", // v5.1.5
        "symfony/twig-bundle": "5.1.*", // v5.1.5
        "symfony/validator": "5.1.*", // v5.1.5
        "symfony/webpack-encore-bundle": "^1.6", // v1.7.3
        "symfony/yaml": "5.1.*" // v5.1.5
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.3", // 3.3.2
        "symfony/browser-kit": "5.1.*", // v5.1.5
        "symfony/css-selector": "5.1.*", // v5.1.5
        "symfony/maker-bundle": "^1.11", // v1.21.1
        "symfony/phpunit-bridge": "5.1.*", // v5.1.5
        "symfony/stopwatch": "5.1.*", // v5.1.5
        "symfony/twig-bundle": "5.1.*", // v5.1.5
        "symfony/web-profiler-bundle": "5.1.*", // v5.1.5
        "zenstruck/foundry": "^1.1" // v1.1.2
    }
}