skip to content

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?

level: seniorimportance: should knowfreq 32%

answer

  1. only keys named in the definition
  2. missing null, invalid false
  3. scalars required unless REQUIRE_ARRAY
  4. filter_input_array ignores changes to $_POST
  5. null when the input source is empty

basics

~20 s

The 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 s

A 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
<?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

for a junior

Recall that filter_var_array() validates several fields at once from a definition array, returning false for invalid and null for missing fields.

for a middle

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.

for a senior

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.

for a principal

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