filter_var? filter_input? No, Use Filter Input Array.

I’ve been a real nut for filter_var() for years, and have come up with concise ways to use it, but totally missed this other function, which, at first look, seemed a little too specialized.

filter_input_array()

Well, I was so wrong. This is a great way to filter inputs. I figured this out when I had to fix up some code because it was throwing a zillion “Notice, index foobar not defined.” in the error logs.

That’s what happens when your legacy code looks like this:

$bar = $_GET['bar'];

all over the place, and it’s not defined. One way to fix this is with filter input:

$bar = filter_input(INPUT_GET, 'bar', FILTER_SANITIZE_STRING);

There good info at this old article Never use $_GET again.

The point of filtering is to make the input data a little bit safer. It’s not completely safe, but it’s safer. To make it more safe, you need to stop using string concatenation to write database queries.

There’s a certain irony here – we have few articles pushing filter_input because many programmers are using frameworks like Laravel that come with object-relational mappers, and also Request and Response objects, so there’s less need to know about filter_input() and it’s sibling filter_input_array().

Consequently, there’s going to be a lot of existing legacy code with some “worst practices” use of $_GET and $_POST, and these problems won’t get fixed.

They should be fixed.

Remember when people wrote code like this:

mysql_query("SELECT * FROM users WHERE pass='$_POST[password]'");

OH THE HORROR!

In case you didn’t know, you could easily hack this via a “SQL injection attack.” Pass the page input with password set to:

' OR ''='

The concatenated string would look like this:

SELECT * FROM users WHERE pass='' OR ''=''

If you didn’t already know this, stop right now and read up about SQL injection attacks. Just search. There’s a lot of info. You should not write web apps if you don’t know this stuff.

Let’s get back on track. The quick fix to avoid these injection attacks is to use PHP Data Objects (PDO) or mysqli prepared statements.

Here’s PDO code:

$db = new PDO ( $dsn, $user, $password, $options ) ;
$sth = $db->prepare("SELECT * FROM users WHERE pass=?");
$sth->execute($password);

It’s one extra line, to prepare() then execute(), but the
execute method escapes the parameter correctly, and then inserts into the SQL string, with quotes. SQL injection is averted.

Filtering input still has a role to play. It can help avoid some logic errors you can make in PHP. All data comes in as strings, but you sometimes want numbers. In PHP, it’s common to see this pattern:

$foo = $_GET['foo'];
if ($foo == 1) ...

THE HORROR.

It works, but it makes us lazy about data types.

What if you had a function like this:

function f($d) {
   if (is_string($d)) { ... }
   else if (is_int($d)) { ... }
}

Then passing a value of “1” as $d causes one branch of code to execute, while passing the number 1 causes the other branch to execute.

With filter_input, you can do this:

$foo = filter_input(INPUT_GET, 'foo', FILTER_VALIDATE_INT);

Then, if foo’s value is a string that looks like an int, the filter_input() return value will be the int value of the string representation.

filter_input_array() is even cooler. Imagine you have foo, the int, and bar, the string. You can filter for both at once:

$get = filter_input_array(INPUT_GET, [
    'foo' => FILTER_VALIDATE_INT,
    'bar' => FILTER_SANITIZE_STRING
]);

Look at that! You can take all your inputs, and validate them in one place, right at the top of your code.

You should put this code right at the top of any method
that uses the input. If the old code is OO, you can go the
next step and separate out santizing and loading values into
a method.

Then, later, you eliminate all uses of $_GET, replacing it with $get.

$foo = $_GET['foo'];

Becomes:

$foo = $get['foo'];

Or, in OO code:

$foo = $this->get('foo');

Usage of $_GET and $_POST and other input arrays should be deprecated and flagged for fixing.

Then, further down, you can fix up the database code with PDO. PDO has a nice feature where you can name parameters, like this:

$sth = $db->prepare("SELECT * FROM users WHERE foo=:foo");
$sth->execute(['foo'=>$password]);

Let’s try this with foo and bar, and we’ll use $get instead of $_GET.

$sth = $db->prepare("INSERT INTO glorp COLUMNS(foo,bar) VALUES(:foo,:bar)");
$sth->execute($get);

Well, look at that!

The code isn’t just safer, it’s also shorter. Concatenate the two parts together, and you have:

$get = filter_input_array(INPUT_GET, [
    'foo' => FILTER_VALIDATE_INT,
    'bar' => FILTER_SANITIZE_STRING
]);
$sth = $db->prepare("INSERT INTO glorp COLUMNS(foo,bar) VALUES(:foo,:bar)");
$sth->execute($get);

The db link setup isn’t there, the FILTER_* are not strict enough, and there are no views, but you get the idea. This is an easy, if not quick, way to take old scripts and make them safer.