PHP Filters Advanced
PHP Filters introduced the filter API: filter_var() , filter_var_array() , filter_input() , the options structure, default , FILTER_CALLBACK , and array handling. This lesson goes deeper into the flag
PHP Filters introduced the filter API: filter_var(), filter_var_array(), filter_input(), the options structure, default, FILTER_CALLBACK, and array handling. This lesson goes deeper into the flags and options of individual filters — number formats, IP address ranges, host names, low-level character stripping — and shows how to combine them into a schema that validates a complete request payload.
Integer Flags: Hexadecimal and Octal
By default, FILTER_VALIDATE_INT accepts only decimal notation without leading zeros:
<?php
var_dump(filter_var('0x1A', FILTER_VALIDATE_INT));
var_dump(filter_var('0x1A', FILTER_VALIDATE_INT, FILTER_FLAG_ALLOW_HEX));
var_dump(filter_var('0755', FILTER_VALIDATE_INT));
var_dump(filter_var('0755', FILTER_VALIDATE_INT, FILTER_FLAG_ALLOW_OCTAL));
Output
bool(false)
int(26)
bool(false)
int(493)
FILTER_FLAG_ALLOW_HEXaccepts0x-prefixed hexadecimal (0x1A= 26).FILTER_FLAG_ALLOW_OCTALaccepts numbers with a leading0as octal (0755= 493) — typical for Unix file permissions.- Without these flags,
0755is rejected rather than silently interpreted. This is usually what you want for user input: a PIN code or quantity typed with a leading zero should not suddenly become an octal number.
Flags combine with options:
filter_var($input, FILTER_VALIDATE_INT, [
'flags' => FILTER_FLAG_ALLOW_HEX,
'options' => ['min_range' => 0, 'max_range' => 0xFFFFFF],
]);
Float Options and Flags
<?php
var_dump(filter_var('1,299.50', FILTER_VALIDATE_FLOAT));
var_dump(filter_var('1,299.50', FILTER_VALIDATE_FLOAT, FILTER_FLAG_ALLOW_THOUSAND));
var_dump(filter_var('12,5', FILTER_VALIDATE_FLOAT, ['options' => ['decimal' => ',']]));
var_dump(filter_var('1e3', FILTER_VALIDATE_FLOAT));
var_dump(filter_var('105.5', FILTER_VALIDATE_FLOAT, ['options' => ['min_range' => 0, 'max_range' => 100]]));
Output
bool(false)
float(1299.5)
float(12.5)
float(1000)
bool(false)
| Setting | Effect |
|---|---|
FILTER_FLAG_ALLOW_THOUSAND | Accepts thousands separators (, by default) |
'decimal' option | Sets the decimal separator (for example , for many European formats) |
'min_range' / 'max_range' options | Range check for floats |
Scientific notation such as 1e3 is accepted by default. If a form field should accept only plain decimal numbers, add a regular expression check or use FILTER_VALIDATE_REGEXP.
IP Address Flags
<?php
$addresses = ['8.8.8.8', '192.168.1.10', '127.0.0.1', '::1', '2001:4860:4860::8888'];
foreach ($addresses as $ip) {
$any = filter_var($ip, FILTER_VALIDATE_IP) !== false ? 'ip' : '-';
$v4 = filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false ? 'v4' : '-';
$public = filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false ? 'public' : '-';
printf("%-22s %-3s %-3s %s\n", $ip, $any, $v4, $public);
}
Output
8.8.8.8 ip v4 public
192.168.1.10 ip v4 -
127.0.0.1 ip v4 -
::1 ip - -
2001:4860:4860::8888 ip - public
| Flag | Rejects |
|---|---|
FILTER_FLAG_IPV4 / FILTER_FLAG_IPV6 | Addresses of the other version |
FILTER_FLAG_NO_PRIV_RANGE | Private ranges such as 10.x.x.x, 172.16–31.x.x, 192.168.x.x (and IPv6 equivalents) |
FILTER_FLAG_NO_RES_RANGE | Reserved ranges such as 127.0.0.0/8 (loopback) and 0.0.0.0/8 |
FILTER_FLAG_GLOBAL_RANGE (PHP 8.2+) | Everything that is not a globally routable address |
Why This Matters
When a server fetches URLs or connects to addresses provided by users (webhooks, image imports, link previews), an attacker may supply an internal address such as 127.0.0.1 or 10.0.0.5 to reach services that are not supposed to be public. Checking the resolved IP address with FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE (or FILTER_FLAG_GLOBAL_RANGE) is one of the defenses against this Server-Side Request Forgery (SSRF).
Domain Names: FILTER_VALIDATE_DOMAIN
<?php
foreach (['gocourse.in', 'exa_mple.com', '-bad.example', 'localhost'] as $domain) {
$basic = filter_var($domain, FILTER_VALIDATE_DOMAIN) !== false ? 'valid' : 'invalid';
$host = filter_var($domain, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME) !== false ? 'valid' : 'invalid';
printf("%-14s domain: %-8s hostname: %s\n", $domain, $basic, $host);
}
Output
gocourse.in domain: valid hostname: valid
exa_mple.com domain: valid hostname: invalid
-bad.example domain: valid hostname: invalid
localhost domain: valid hostname: valid
- Without flags, the filter checks only length rules for domain names, which are quite permissive (DNS itself allows underscores in some records).
FILTER_FLAG_HOSTNAMEapplies the stricter rules for host names: letters, digits, and hyphens only, with no hyphen at the start or end of a label. Use it for values that will be used as server or website names.- Neither check confirms that the domain exists; use DNS lookups (
checkdnsrr(),dns_get_record()) if that is required.
MAC Addresses
<?php
var_dump(filter_var('00:1A:2B:3C:4D:5E', FILTER_VALIDATE_MAC));
var_dump(filter_var('00-1A-2B-3C-4D-5E', FILTER_VALIDATE_MAC, ['options' => ['separator' => ':']]));
Output
string(17) "00:1A:2B:3C:4D:5E"
bool(false)
By default, colons, hyphens, and the dotted Cisco format are accepted. The separator option restricts the separator to one character.
Low-Level Character Flags
FILTER_UNSAFE_RAW (the default filter) changes nothing unless flags are given. Its flags remove or encode control characters and non-ASCII bytes:
<?php
$input = "Hello\x07World\n";
var_dump(filter_var($input, FILTER_UNSAFE_RAW, FILTER_FLAG_STRIP_LOW));
var_dump(strlen(filter_var("Café", FILTER_UNSAFE_RAW, FILTER_FLAG_STRIP_HIGH)));
Output
string(10) "HelloWorld"
int(3)
| Flag | Effect |
|---|---|
FILTER_FLAG_STRIP_LOW | Removes characters with ASCII code below 32 (including tabs and newlines) |
FILTER_FLAG_STRIP_HIGH | Removes bytes above 127 — this destroys UTF-8 text (here, é disappeared) |
FILTER_FLAG_STRIP_BACKTICK | Removes backticks |
FILTER_FLAG_ENCODE_LOW / ENCODE_HIGH / ENCODE_AMP | Encodes characters as HTML numeric entities instead of removing them |
FILTER_FLAG_STRIP_LOW is useful for single-line fields (names, titles) where control characters are never legitimate. Avoid STRIP_HIGH for any text that may contain non-English characters.
Failing Loudly: FILTER_THROW_ON_FAILURE (PHP 8.5+)
PHP 8.5 adds a flag that makes validation filters throw an exception instead of returning false:
<?php
try {
$age = filter_var('abc', FILTER_VALIDATE_INT, FILTER_THROW_ON_FAILURE);
} catch (Filter\FilterFailedException $e) {
echo "Invalid input: " . $e->getMessage() . "\n";
}
This is convenient when invalid input should simply abort processing. On earlier versions, check for false and throw your own exception.
Practical Example: Validating an API Payload With a Schema
<?php
function validatePayload(array $payload): array
{
$schema = [
'name' => ['filter' => FILTER_UNSAFE_RAW, 'flags' => FILTER_FLAG_STRIP_LOW],
'age' => ['filter' => FILTER_VALIDATE_INT, 'options' => ['min_range' => 18, 'max_range' => 120]],
'server_ip' => ['filter' => FILTER_VALIDATE_IP, 'flags' => FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE],
'newsletter' => ['filter' => FILTER_VALIDATE_BOOL, 'flags' => FILTER_NULL_ON_FAILURE],
'tags' => [
'filter' => FILTER_VALIDATE_REGEXP,
'flags' => FILTER_REQUIRE_ARRAY,
'options' => ['regexp' => '/^[a-z0-9-]{1,20}$/'],
],
];
$data = filter_var_array($payload, $schema);
$errors = [];
foreach ($schema as $field => $rule) {
$value = $data[$field];
$isBool = $rule['filter'] === FILTER_VALIDATE_BOOL;
if ($value === null && !array_key_exists($field, $payload)) {
$errors[] = "$field: missing";
} elseif ($isBool ? $value === null : $value === false) {
$errors[] = "$field: invalid";
} elseif (is_array($value)) {
foreach ($value as $index => $item) {
if ($item === false) {
$errors[] = "{$field}[$index]: invalid";
}
}
}
}
return [$data, $errors];
}
[$data, $errors] = validatePayload([
'name' => "Asha\x00",
'age' => '17',
'server_ip' => '10.0.0.5',
'newsletter' => 'maybe',
'tags' => ['php', '<b>x</b>'],
]);
echo implode("\n", $errors) . "\n";
var_dump($data['name']);
Output
age: invalid
server_ip: invalid
newsletter: invalid
tags[1]: invalid
string(4) "Asha"
Step-by-Step Explanation
- The schema describes every field with a filter, flags, and options — one place to read and change the rules.
filter_var_array()applies all filters at once.- The error loop distinguishes three situations:
- missing fields (
nulland absent from the payload), - invalid values (
false, ornullfor the boolean filter, which usesFILTER_NULL_ON_FAILUREbecausefalseis a valid result), - invalid elements in arrays, reported with their index.
- missing fields (
- The name's invisible NUL byte (
\x00) is stripped byFILTER_FLAG_STRIP_LOW. - The private IP address, the out-of-range age, the ambiguous
maybe, and the HTML tag in the tags list are all reported.
For large applications, validation libraries or framework validators offer richer rules and messages, but they rely on the same building blocks.
Common Mistakes
Enabling Octal or Hex Unintentionally
Effect: User input like 010 becomes 8.
Fix: Use FILTER_FLAG_ALLOW_OCTAL/ALLOW_HEX only where these formats are expected.
Validating User-Provided IPs Without Range Flags
Effect: Internal services can be targeted through your server (SSRF).
Fix: Reject private and reserved ranges for addresses your server connects to.
Using STRIP_HIGH on Text
Effect: Non-English characters are removed.
Fix: Use STRIP_LOW only, or no flags at all.
Treating false as Failure for Boolean Filters
Effect: A valid "no" answer is reported as invalid.
Fix: Use FILTER_NULL_ON_FAILURE and check for null.
Confusing Domain and Host Name Rules
Effect: Invalid host names such as exa_mple.com are accepted.
Fix: Add FILTER_FLAG_HOSTNAME.
Best Practices
- Choose flags deliberately for each filter; defaults are strict for numbers and permissive for domains.
- Protect server-side connections by rejecting private and reserved IP ranges.
- Use
FILTER_NULL_ON_FAILUREfor boolean input. - Describe payload validation as a schema and report errors per field (and per array element).
- On PHP 8.5+, consider
FILTER_THROW_ON_FAILUREwhere invalid input should stop processing.
Official reference: https://www.php.net/manual/en/filter.filters.php