Form values arrive as strings, even when they look like numbers. Before you use one in a calculation, check that it really is a number.
is_numeric()
<?php
var_dump(is_numeric("42"));
var_dump(is_numeric("3.14"));
var_dump(is_numeric("1e3"));
var_dump(is_numeric("12abc"));
var_dump(is_numeric(""));
var_dump(is_numeric(" 5"));
var_dump(is_numeric("5 "));
bool(true)
bool(true)
bool(true)
bool(false)
bool(false)
bool(true)
bool(true)
is_numeric() accepts forms like "1e3" (scientific notation) and leading or trailing spaces. That may be more than you want.
filter_var(): stricter and more precise
filter_var() with FILTER_VALIDATE_INT accepts whole numbers only, and returns the number as an int or false.
<?php
var_dump(filter_var("42", FILTER_VALIDATE_INT));
var_dump(filter_var("42.5", FILTER_VALIDATE_INT));
var_dump(filter_var("abc", FILTER_VALIDATE_INT));
var_dump(filter_var("1e3", FILTER_VALIDATE_INT));
int(42)
bool(false)
bool(false)
bool(false)
Add a range
<?php
$options = ["options" => ["min_range" => 1, "max_range" => 10]];
var_dump(filter_var("7", FILTER_VALIDATE_INT, $options));
var_dump(filter_var("15", FILTER_VALIDATE_INT, $options));
int(7)
bool(false)
The zero problem
filter_var("0", FILTER_VALIDATE_INT) returns 0, which is falsy. Always compare the result with === false.
<?php
$age = filter_var("0", FILTER_VALIDATE_INT);
if ($age === false) {
echo "Not a number";
} else {
echo "Valid: $age";
}
Valid: 0
Decimals
<?php
var_dump(filter_var("19.99", FILTER_VALIDATE_FLOAT));
var_dump(filter_var("19,99", FILTER_VALIDATE_FLOAT));
var_dump(filter_var("1,999.50", FILTER_VALIDATE_FLOAT));
var_dump(filter_var("1,999.50", FILTER_VALIDATE_FLOAT, FILTER_FLAG_ALLOW_THOUSAND));
float(19.99)
bool(false)
bool(false)
float(1999.5)
A comma is not accepted by default, not even as a thousands separator. Decide which number format your form accepts, and tell the user.
Only digits: ctype_digit()
<?php
var_dump(ctype_digit("12345"));
var_dump(ctype_digit("-5"));
var_dump(ctype_digit("12.5"));
bool(true)
bool(false)
bool(false)
ctype_digit() accepts only the characters 0 to 9. That is exactly right for things like a PIN or an ID number.
A reusable function
<?php
function readQuantity(string $input): ?int
{
$options = ["options" => ["min_range" => 1, "max_range" => 99]];
$value = filter_var(trim($input), FILTER_VALIDATE_INT, $options);
return $value === false ? null : $value;
}
var_dump(readQuantity(" 5 "));
var_dump(readQuantity("0"));
var_dump(readQuantity("ten"));
int(5)
NULL
NULL
Pick by need
is_numeric() for “any number”, filter_var() for “an integer in this range”, ctype_digit() for “only digits”.