Filter URL query variables

Superglobals vs filter_input

$_GET & $_POST superglobals

Snapshot array of the URL query in the GET/ POST response after the HTTP response is completed.

filter_input()

filter_input() gets its value directly from the HTTP response, not from superglobals $_GET or $_POST.

★ They don't modify each other.

Sanitization

If the query ?store_id=... initially returns a corrupt value, then gets sanitized, $_POST['store_id'] still contains the corrupt value.

$store_id = filter_input(INPUT_POST, 'store_id', FILTER_SANITIZE_NUMBER_INT);
var_dump($store_id === $_POST['store_id']);
bool(false);

filter_input and $_POST & $_GET all return 'string' type values (or arrays of strings).

How can you test a corrupt value without using a URL in a browser and risking corrupting your application?

To test in a PHP console without using filter_input() because you don't have an available HTTP response, use filter_var() which processes the provided variable exactly the same.

Replace:

filter_input(INPUT_POST, ‘store_id’, FILTER_SANITIZE_STRING);

with:

filter_var($store_id, FILTER_SANITIZE_STRING);

Example console tests

$store_id = "<script>alert('Yikes!');</script>";
$store_id = filter_var($store_id, FILTER_SANITIZE_STRING);
var_dump($store_id);
string(24) "alert(&#39;Yikes!&#39;);"
$store_id = "<script>alert('Yikes!');</script>";
$store_id = filter_var($store_id, FILTER_SANITIZE_NUMBER_INT);
var_dump($store_id);
string(0) ""
$store_id = "<script>alert('30');</script>";
$store_id = filter_var($store_id, FILTER_SANITIZE_NUMBER_INT);
var_dump($store_id);
string(2) "30"

Future compatibility

FILTER_SANITIZE_STRING and FILTER_SANITIZE_STRIPPED are deprecated as of PHP v8.1.0. The replacement is:

$store_id = "<script>alert('30');</script>";
$store_id = filter_var($store_id, FILTER_CALLBACK, [
    'options' => function ($value) {
        return htmlspecialchars(strip_tags($value), ENT_QUOTES);
    }
]);
// A closure returns a syntax error in the silly console. Try a one-liner:
$store_id = filter_var($store_id, FILTER_CALLBACK, ['options' => function ($value) {return htmlspecialchars(strip_tags($value), ENT_QUOTES);}]);
var_dump($store_id);
string(22) "alert(&#039;30&#039;);"

You can define a custom callback separately:

function sanitize_callback($value) {
    return htmlspecialchars(strip_tags($value), ENT_QUOTES);
}
$store_id = "<script>alert('30');</script>";
$store_id = filter_var($store_id, FILTER_CALLBACK, ['options' => 'sanitize_callback']);
var_dump($store_id);
string(22) "alert(&#039;30&#039;);"

Validation

After sanitization, time for validation of safe values.

That’s when you use filter_var() straight in your code.

Test integer

$store_id = "30";
$store_id = filter_var($store_id, FILTER_VALIDATE_INT, [
    'options' => [
        'min_range' => 1,
        'max_range' => 20000,
      ]
]);
var_dump($store_id);
int(30)

The type is converted to ‘int’ as intended.

Test hexadecimal digits

$invoice_code = "3f4e2210dc87b";
$invoice_code = filter_var($invoice_code, FILTER_CALLBACK, [
    'options' => function ($value) {
        return ctype_xdigit($value) ? $value : false;
    }
]);
// A closure returns a syntax error in the silly console. Try a one-liner:
$invoice_code = filter_var($invoice_code, FILTER_CALLBACK, ['options' => function ($value) {return ctype_xdigit($value) ? $value : false;}]);
var_dump($invoice_code);
string(13) "3f4e2210dc87b"

Invalid value:

$invoice_code = "ZZ3f4e2210dc87b";
$invoice_code = filter_var($invoice_code, FILTER_CALLBACK, ['options' => function ($value) {return ctype_xdigit($value) ? $value : false;}]);
var_dump($invoice_code);
bool(false)

You can define a custom callback separately:

function validate_callback($value) {
    return ctype_xdigit($value) ? strtoupper($value) : false;
}
$invoice_code = "3f4e2210dc87b";
$invoice_code = filter_var($invoice_code, FILTER_CALLBACK, ['options' => 'validate_callback']);
var_dump($invoice_code);
string(13) "3F4E2210DC87B"

Difference in callbacks

Notice the callback return value is different in sanitization vs validation.

Sanitization

To be consistent with all the sanitization filters, return the value itself after processing.

return htmlspecialchars(strip_tags($value), ENT_QUOTES);

Validation

To be consistent with all the validation filters, return the processed value if valid, else return false.

return ctype_xdigit($value) ? $value : false;

Currency example

Sanitize

$amount = "<script>alert('2,000.00');</script>";
$amount = filter_var($amount, FILTER_SANITIZE_NUMBER_FLOAT, [
    'flags' => FILTER_FLAG_ALLOW_THOUSAND | FILTER_FLAG_ALLOW_FRACTION
]);
var_dump($amount);
string(8) "2,000.00"

To display a 'float' type value, you need to keep it formatted as a ‘string’.

Validate

To save in a database, convert it to ‘float’ ('double').

$amount = "2,000.00";
$amount = filter_var($amount, FILTER_VALIDATE_FLOAT);
var_dump($amount);
bool(false)
$amount = "2,000.00";
$amount = filter_var($amount, FILTER_VALIDATE_FLOAT, ['flags' => FILTER_FLAG_ALLOW_THOUSAND]);
var_dump($amount);
double(2000)
$amount = "2,000.01";
$amount = filter_var($amount, FILTER_VALIDATE_FLOAT, ['flags' => FILTER_FLAG_ALLOW_THOUSAND]);
var_dump($amount);
double(2000.01)
// Assume a German amount
$amount = "2.000,01";
$amount = filter_var($amount, FILTER_VALIDATE_FLOAT, [
    'options' => ['decimal' => ','],
    'flags' => FILTER_FLAG_ALLOW_THOUSAND,
]);
var_dump($amount);
double(2000.01)

Array examples

Using filter_input_array() and filter_var_array().

Sanitize

To apply one filter to all values:

$array = ['<a href="#">link</a>', '30', ';go'];
$array = filter_var_array($array, FILTER_SANITIZE_STRING);
var_dump($array);
array(3) {
  [0] =>
  string(4) "link"
  [1] =>
  string(2) "30"
  [2] =>
  string(3) ";go"
}

To apply one filter to each of the query variables, assuming the URL query is:

?store_name=<a href="#">Foxtrot</a>&store_id=30&invoice_code=;go
$url_query = filter_input_array(INPUT_GET, [
    'store_name' => FILTER_SANITIZE_STRING,
    'store_id' => FILTER_SANITIZE_NUMBER_INT,
    'invoice_code'=> FILTER_SANITIZE_STRING,
]);

Test in console:

$url_query = [
    'store_name' => '<a href="#">Foxtrot</a>',
    'store_id' => '30',
    'invoice_code'=> ';go',
];
$url_query = filter_var_array($url_query, [
    'store_name' => FILTER_SANITIZE_STRING,
    'store_id' => FILTER_SANITIZE_NUMBER_INT,
    'invoice_code'=> FILTER_SANITIZE_STRING,
]);
var_dump($url_query);
array(3) {
  'store_name' =>
  string(4) "Foxtrot"
  'store_id' =>
  string(2) "30"
  'invoice_code' =>
  string(3) ";go"
}

Validate

function validate_callback($value) {
    return ctype_xdigit($value) ? strtoupper($value) : false;
}
$url_query = filter_var_array($url_query, [
    'store_name' => [
        'filter' => FILTER_VALIDATE_REGEXP,
        'options' => ['regexp' => '/^[Ff]/'],
    ],
    'store_id' => FILTER_VALIDATE_INT,
    'invoice_code'=> [
        'filter' => FILTER_CALLBACK,
        'options' => 'validate_callback',
    ],
]);
var_dump($url_query);
array(3) {
  'store_name' =>
  string(4) "Foxtrot"
  'store_id' =>
  int(30)
  'invoice_code' =>
  bool(false)
}