When validating a whole PHP form with filter_input_array() or filter_var_array() and a definition array, what does the result contain, and which pitfalls must you handle?
answer
- only keys named in the definition
- missing null, invalid false
- scalars required unless REQUIRE_ARRAY
- filter_input_array ignores changes to $_POST
- null when the input source is empty
basics
~20 sThe result holds only the defined keys: a typed value if valid, false if invalid, null if missing. Pitfalls: null-on-failure blurs invalid and missing, arrays fail scalar filters, and filter_input_array() reads the original request input, not $_POST.
solid answer
~50 sA definition maps field names to a filter constant or to `['filter' => ..., 'flags' => ..., 'options' => ...]`. The result contains exactly those keys: extra submitted fields are dropped, a valid field holds its typed value, an invalid one `false`, and a missing one `null` (the third argument, `add_empty`, defaults to `true`; set it to `false` to omit missing keys). Pitfalls: adding `FILTER_NULL_ON_FAILURE` makes invalid and missing both `null`; each field requires a scalar unless you pass `FILTER_REQUIRE_ARRAY` or `FILTER_FORCE_ARRAY`, so `age[]=18` yields `false`; and PHP 8.5's `FILTER_THROW_ON_FAILURE` aborts on the first bad field. `filter_input_array(INPUT_POST, ...)` reads PHP's own snapshot of the parsed request body, so it ignores later changes to `$_POST`, never sees a JSON body, and returns `null` when no POST data arrived. `filter_var_array($_POST, ...)` avoids those surprises and is easy to test.
code
php · 19 lines<?php
declare(strict_types=1);
$definition = [
'age' => ['filter' => FILTER_VALIDATE_INT,
'options' => ['min_range' => 16, 'max_range' => 99]],
'email' => FILTER_VALIDATE_EMAIL,
'website' => FILTER_VALIDATE_URL,
'can_drive' => ['filter' => FILTER_VALIDATE_BOOL, 'flags' => FILTER_NULL_ON_FAILURE],
'days' => ['filter' => FILTER_VALIDATE_INT, 'flags' => FILTER_REQUIRE_ARRAY,
'options' => ['min_range' => 1, 'max_range' => 7]],
];
$input = filter_var_array($_POST, $definition); // extra fields are dropped
foreach (['age', 'email'] as $field) {
if ($input[$field] === null) { $errors[$field] = 'Required.'; }
if ($input[$field] === false) { $errors[$field] = 'Invalid value.'; }
}go deeper
Recall that filter_var_array() validates several fields at once from a definition array, returning false for invalid and null for missing fields.
Explain the definition format, the add_empty argument, the scalar requirement and the REQUIRE_ARRAY and FORCE_ARRAY flags, and how NULL_ON_FAILURE blurs missing and invalid.
Show why you prefer filter_var_array() on an explicit array over filter_input_array() for testability and JSON bodies, and how you turn the result into per-field errors.
Decide whether the filter extension's definitions or a dedicated validation layer should own request validation, weighing error reporting, reuse and testability.
## One call, one definition Validating a volunteer sign-up form field by field quickly repeats itself. The filter extension offers two batch functions: - `filter_var_array(array $array, array|int $options = FILTER_DEFAULT, bool $add_empty = true): array|false|null` filters an array you pass in; - `filter_input_array(int $type, array|int $options = FILTER_DEFAULT, bool $add_empty = true): array|false|null` filters one of the request inputs, `INPUT_GET`, `INPUT_POST`, `INPUT_COOKIE`, `INPUT_SERVER` or `INPUT_ENV`. The `$options` argument is usually a **definition array**: each key is a field name, and each value is either a filter constant or an array with `filter`, `flags` and `options` keys, exactly as you would pass to `filter_var()`. ## What comes back For every key in the definition, the result holds one of three things: | Field state | Result value | |---|---| | present and valid | the filtered value, typed (`int`, `bool`, the validated string) | | present and invalid | `false` (or `null` with `FILTER_NULL_ON_FAILURE`) | | missing from the input | `null`, if `add_empty` is `true` (the default); absent otherwise | Fields that were submitted but are **not** named in the definition are dropped. That makes the definition a field allow-list: a tampered request that adds `is_admin=1` cannot carry it through. Definition keys must be non-empty strings; a numeric key throws a `TypeError` and an empty one a `ValueError`. ## Pitfall 1: invalid and missing can collide By default invalid is `false` and missing is `null`, so you can tell them apart. Add `FILTER_NULL_ON_FAILURE` to a field and both become `null`. That is exactly the case where the user needs different messages ("required" versus "not a valid number"). Either keep the default for such fields, or check `array_key_exists()` on the input first. ## Pitfall 2: arrays and scalars Each field requires a **scalar** unless its flags include `FILTER_REQUIRE_ARRAY` (must be an array, each element filtered) or `FILTER_FORCE_ARRAY` (a scalar is wrapped into an array). So: - a tampered `age[]=18` gives `false` for a scalar `age` field, which is the behaviour you want; - a legitimate multi-select, such as the days a volunteer is available, must declare `FILTER_REQUIRE_ARRAY` or it will always fail. ## Pitfall 3: filter_input_array() does not read $_POST `filter_input_array()` and `filter_input()` read the filter extension's own copy of the request variables, captured when PHP parsed the request. Consequences: 1. Changes a script or middleware makes to `$_POST` are invisible to them. 2. A JSON request body read from `php://input` is not part of `INPUT_POST` at all. 3. When the request carried no variables of that type (a GET request asking for `INPUT_POST`, or the CLI), the function returns `null`, not an empty array, and code indexing the result triggers "Trying to access array offset on null" warnings. 4. Tests that populate `$_POST` directly do not affect them. `filter_var_array($_POST, $definition)`, or the same call on a decoded JSON array, behaves predictably in all four cases. ## Pitfall 4: the throw flag and error collection PHP 8.5's `FILTER_THROW_ON_FAILURE` can be set per field. The batch call then throws `Filter\FilterFailedException` at the first failing field and returns nothing, so the form cannot report all its errors at once. Keep the throw flag for code where the first failure should stop processing. ## Rules the definition cannot express A definition validates each field on its own. It cannot say "a driver's licence number is required when `can_drive` is true" or "the end date must follow the start date". Those **cross-field rules** run afterwards, on the typed result: - they can rely on the types the filters produced, `bool` for `can_drive`, `int` for `age`; - they add their errors to the same per-field error array; - they run only when the fields they depend on passed, so the user is not told that an invalid age is too young to drive. ## After the call - Loop over the definition keys, not the input, to build error messages. - Treat `false` as invalid and `null` as missing, per the table. - Use only the result array from here on; the raw input has served its purpose.
- Why can a test that sets $_POST['age'] not change what filter_input(INPUT_POST, 'age') returns?`filter_input()` reads the filter extension's snapshot of the request variables taken when PHP parsed the request, not the `$_POST` array. Assignments to `$_POST` never reach that snapshot. Code that must be testable, or that works on JSON bodies, should call `filter_var()` or `filter_var_array()` on an array it receives.
- What does add_empty = false change in filter_var_array()?Missing fields are left out of the result instead of appearing as `null`. That suits partial updates, where an absent field means "leave unchanged", but it means you must use `array_key_exists()` or `??` when reading the result, or you get undefined-key warnings.
saying these in an interview costs you the question
- filter_var_array() keeps submitted fields that the definition does not name
- A missing field comes back as false
- filter_input_array() sees changes made to $_POST earlier in the script
- A multi-select field validates as an array without any extra flag
- filter_input_array() returns an empty array when there is no POST body