In PHP, how do FILTER_NULL_ON_FAILURE and PHP 8.5's FILTER_THROW_ON_FAILURE change what a failed filter_var() check returns?
answer
- default failure value is false
- false is a valid FILTER_VALIDATE_BOOL result
- null on failure makes bool tri-state
- Filter\FilterFailedException in 8.5
- both flags together: ValueError
basics
~10 sBy default a failed filter returns false. FILTER_NULL_ON_FAILURE returns null instead, which matters when false is a valid result; PHP 8.5's FILTER_THROW_ON_FAILURE throws Filter\FilterFailedException. The two flags cannot be combined.
solid answer
~40 sOut of the box a failed validation returns `false`. That is ambiguous for `FILTER_VALIDATE_BOOL`, whose job is to return `false` for `"no"`, `"off"`, `"0"` or `"false"`. With `FILTER_NULL_ON_FAILURE`, failure returns `null` instead, so the bool filter becomes tri-state: `true`, `false`, or `null` for anything unrecognised. PHP 8.5 added `FILTER_THROW_ON_FAILURE`, which throws `Filter\FilterFailedException`, a subclass of `Filter\FilterException` and of `Exception`, instead of returning a failure value. Passing both flags throws a `ValueError`. Flags go in the third argument, either alone or under the `'flags'` key next to `'options'`. One trap sits in `filter_input()`: a missing variable returns `null`, but with `FILTER_NULL_ON_FAILURE` the return values swap, so missing becomes `false`.
code
php · 12 lines<?php
declare(strict_types=1);
var_dump(filter_var('no', FILTER_VALIDATE_BOOL)); // bool(false)
var_dump(filter_var('maybe', FILTER_VALIDATE_BOOL)); // bool(false): ambiguous
var_dump(filter_var('maybe', FILTER_VALIDATE_BOOL, FILTER_NULL_ON_FAILURE)); // NULL
try {
filter_var('abc', FILTER_VALIDATE_INT, FILTER_THROW_ON_FAILURE); // PHP 8.5
} catch (Filter\FilterFailedException $e) {
echo 'Rejected', PHP_EOL;
}go deeper
Recall that validation failure returns false by default and that FILTER_NULL_ON_FAILURE switches it to null, which matters for booleans.
Explain the bool filter's accepted words, the tri-state result with the null flag, how flags sit beside options, and the 8.5 throw flag and its exception classes.
Show how you pick a failure mode per layer, forms collecting errors versus services throwing, and how you avoid the filter_input() missing-versus-invalid inversion.
Set one convention for failure signalling across the codebase so reviewers never have to guess whether false, null or an exception means invalid.
## The default: false means failure Every validate filter signals failure the same way unless told otherwise: it returns `false`. For `FILTER_VALIDATE_INT`, `FILTER_VALIDATE_EMAIL` or `FILTER_VALIDATE_URL` that works, because `false` can never be a successful result. It breaks down in one place. ## The boolean problem `FILTER_VALIDATE_BOOL` (the alias added in PHP 8.0 for `FILTER_VALIDATE_BOOLEAN`) maps words to booleans, case-insensitively and after trimming whitespace: | Input | Result | |---|---| | `"1"`, `"true"`, `"on"`, `"yes"` | `true` | | `"0"`, `"false"`, `"off"`, `"no"`, `""` | `false` | | anything else, e.g. `"maybe"` | failure | Without extra flags, failure is also `false`, so `"no"` and `"maybe"` produce the same result. A volunteer form field such as `can_drive` cannot tell a deliberate "no" from garbage. Note also that the empty string is a *valid* `false`, not a failure. ## FILTER_NULL_ON_FAILURE This flag changes the failure value to `null`: - `filter_var('no', FILTER_VALIDATE_BOOL, FILTER_NULL_ON_FAILURE)` returns `false`; - `filter_var('maybe', FILTER_VALIDATE_BOOL, FILTER_NULL_ON_FAILURE)` returns `null`. The flag works with every validator, so it also suits code that prefers `null` as "no valid value" and uses `??` for fallbacks. ## FILTER_THROW_ON_FAILURE (PHP 8.5) PHP 8.5 added a third failure mode. With `FILTER_THROW_ON_FAILURE`, a failed validation throws `Filter\FilterFailedException`. The hierarchy is: - `Filter\FilterException extends Exception` - `Filter\FilterFailedException extends Filter\FilterException` The exception message includes the rejected input, so treat it as user-controlled text when logging or displaying it. Two rules apply: 1. Combining `FILTER_THROW_ON_FAILURE` with `FILTER_NULL_ON_FAILURE` throws a `ValueError` saying both cannot be used. 2. Throwing suits code where one bad value should abort the operation, such as a service method receiving an ID. For a form, where you want every field's error at once, a failure value you can collect is usually more convenient. ## How to pass the flags The third argument accepts either an integer of flags or an array: - `filter_var($v, FILTER_VALIDATE_BOOL, FILTER_NULL_ON_FAILURE)` - `filter_var($v, FILTER_VALIDATE_INT, ['options' => ['min_range' => 16], 'flags' => FILTER_NULL_ON_FAILURE])` A frequent mistake is putting the flag inside `'options'`, where it is ignored. ## The default option interacts with the failure value An `'options' => ['default' => ...]` value replaces the failure result: `false` normally, `null` with `FILTER_NULL_ON_FAILURE`. With the bool filter and no null flag, that means a legitimate `false` is also replaced by the default, because PHP cannot tell it from failure. Use `FILTER_NULL_ON_FAILURE` whenever a default is combined with a filter that can legitimately return `false`. ## filter_input() and missing variables `filter_input(INPUT_POST, 'can_drive', FILTER_VALIDATE_BOOL)` has two non-success outcomes: the field was **missing**, or it was **invalid**. By default missing returns `null` and invalid returns `false`. With `FILTER_NULL_ON_FAILURE` the pair is inverted: missing returns `false` and invalid returns `null`. The source comments call this out explicitly. With `FILTER_THROW_ON_FAILURE`, a missing variable throws too, with a message saying the input value was not found, and the check runs before any `default` option is consulted, so a default does not prevent the exception. | Situation | no flag | `FILTER_NULL_ON_FAILURE` | `FILTER_THROW_ON_FAILURE` | |---|---|---|---| | valid `"no"` | `false` | `false` | `false` | | invalid `"maybe"` | `false` | `null` | throws | | field missing (`filter_input`) | `null` | `false` | throws | ## Choosing a failure mode - **Default `false`**: fine for filters that can never return `false` on success (`INT`, `EMAIL`, `URL`, `IP`), and it is what most existing code expects. - **`FILTER_NULL_ON_FAILURE`**: required for `FILTER_VALIDATE_BOOL` whenever "invalid" must differ from "no", and convenient when `null` already means "no valid value" in your types (`?int`, `?bool`). - **`FILTER_THROW_ON_FAILURE`** (8.5): suits service and domain code where an invalid value is a programming or contract error, and an exception with a clear type is easier to handle centrally than a scattered `false` check. Whatever you choose, pick it per layer and keep it consistent; mixing modes inside one form handler is how a `false` gets treated as `null` or the reverse.
- What does filter_var('', FILTER_VALIDATE_BOOL, FILTER_NULL_ON_FAILURE) return?`false`, not `null`. The bool filter treats an empty string as a valid false, alongside "0", "false", "off" and "no". So an empty checkbox-like field reads as a deliberate no; if empty must mean "not answered", check for it before filtering.
- Why might you avoid FILTER_THROW_ON_FAILURE when validating a sign-up form?It throws on the first failing field, so the user learns about one error per submission, and each field needs its own try/catch to collect them all. Return values let you gather every error in one pass. The throw flag fits code where a single invalid value should stop the operation.
- Where do flags go when you also pass options to filter_var()?Next to them, under a top-level `'flags'` key: `['options' => ['min_range' => 1], 'flags' => FILTER_NULL_ON_FAILURE]`. Flags placed inside the `'options'` array are ignored, so the call silently keeps the default false-on-failure behaviour.
saying these in an interview costs you the question
- FILTER_VALIDATE_BOOL returns false only for invalid input
- An empty string fails FILTER_VALIDATE_BOOL
- You can combine FILTER_NULL_ON_FAILURE with FILTER_THROW_ON_FAILURE
- FILTER_THROW_ON_FAILURE throws a ValueError when validation fails
- filter_input() always returns null for missing fields, whatever the flags