01 · THE IDEAREAD → TRY → LEARN
Why validate at all?
Data that arrives from a form, a query string or a file is just text. A user can type abc where you expect a number, or leave a field empty. If you use the value without checking it, the bug appears later, far from its cause. Validation is the habit of checking a value at the boundary of your code and either accepting it in a known shape or rejecting it.
Meet filter_var()
PHP's filter_var() takes a value, a filter and optional options. For validation you pick one of the FILTER_VALIDATE_* constants, such as FILTER_VALIDATE_EMAIL or FILTER_VALIDATE_INT. On success it returns the value in its filtered form. On failure it returns false, unless you pass the FILTER_NULL_ON_FAILURE flag, in which case it returns null.
Scalar values are converted to strings internally before filtering, so 42 and '42' are treated alike.
Here is an email check. The first value passes and comes back unchanged. The second fails and gives false.
$values = ['bob@example.com', 'invalidemail'];
foreach ($values as $value) {
echo json_encode(filter_var($value, FILTER_VALIDATE_EMAIL)), "\n";
}"bob@example.com" false
Notice that a valid email returns the string itself, not true. The return value is the cleaned data, and false is the only signal of failure for this filter.
The valid-zero trap
Now consider FILTER_VALIDATE_INT. The string '0' is a perfectly good integer, and the filter returns the integer 0. But 0 is falsy in PHP, so a check like if ($result) or the shorthand $result ?: null treats a valid zero as a failure. This is a very common bug in quantity, page and offset fields, where zero is a legitimate answer.
The fix is a strict comparison with false:
$result = filter_var('0', FILTER_VALIDATE_INT);
echo json_encode($result), "\n";
echo $result === false ? "rejected" : "valid", "\n";
$bad = filter_var('zero', FILTER_VALIDATE_INT);
echo $bad === false ? "rejected" : "valid", "\n";0 valid rejected
The === operator compares type as well as value, so 0 === false is false while false === false is true. Use it whenever a filter's success value could itself be falsy.
Adding limits with options
Many filters accept options. For integers, min_range and max_range go inside an inner options array, and flags go in a flags field. Combining a range with FILTER_NULL_ON_FAILURE gives you a tidy result: an int or null, never false. That matches a ?int return type nicely.
function parse_age(string $input): ?int {
return filter_var($input, FILTER_VALIDATE_INT, [
'options' => ['min_range' => 0, 'max_range' => 120],
'flags' => FILTER_NULL_ON_FAILURE,
]);
}
echo json_encode(array_map('parse_age', ['0', '121', 'x'])), "\n";[0,null,null]
The zero survives, the out-of-range value is rejected, and the non-number is rejected. The caller now only has to test === null.
Validation versus sanitization
Validation answers "is this acceptable as it is?". Sanitization (FILTER_SANITIZE_*) changes a value to strip unwanted characters. For most input checks, prefer validating and rejecting. Silently rewriting what a user typed can hide mistakes.
Worked reasoning for the exercise
You will write parse_quantity(string $input): ?int. It should accept whole numbers from 0 to 100 inclusive and return them as integers. Anything else returns null.
- Call
filter_var()withFILTER_VALIDATE_INTand a range of 0 to 100. - Capture the result in a variable.
- Compare strictly against
false. If it isfalse, returnnull. Otherwise return the integer.
The boundary cases matter: '0' and '100' must be accepted, '101' must be rejected, and '12.5' is not a whole number so it must be rejected too. Casting with (int) would turn '12.5' into 12 and 'abc' into 0, which is exactly the silent acceptance validation is meant to prevent.
Recap
filter_var()with aFILTER_VALIDATE_*filter returns the filtered value orfalse.FILTER_NULL_ON_FAILUREswitches the failure value tonull.- Use
===to separate failure from a valid0. - Prefer rejecting bad input over casting it into something that looks valid.
Functions · Conditionals
- Validate an email or integer with filter_var
- Distinguish rejected input from a valid zero