You know there is an entire extension dedicated to validating and sanitising inputs right?
All your type checking and regexes and double cast comparisons could be replaced with:
if (($value = filter_var($value, FILTER_VALIDATE_INT)) !== false) { doStuff(); }
> You could try to blame PHP, but really it's the developersAt least we can agree on one thing.
Well, ctype_digit takes strings, not integers. So don't be surprised if you pass the wrong type to a function and it doesn't work as you expected.
Some of your criticism is valid, but you can't go around talking about how PHP isn't rigorous enough, and then complain tha some functions don't work as you'd like when you give them a wrong argument type.
Your other arguments are more about bad developers as you say it yourself, anyone who actually cares about what he does knows you have to check equality with ===, while the array argument problem is less well known, but actually almost unrelated to PHP: POST or GET is user data that can be any type and should be checked. Only the last of your examples is actually a problem to me.
The problem is that the behavior is not consistant. Php is some parts c, some parts java and some part perl. That is the problem. It takes a encyclopedic knowledge of the documentation to know what part you are dealing with. And even that might not help you sometimes, because the documentation can be plain wrong at places...
>anyone who actually cares about what he does knows you have to check equality with ===
Can you write php code to store some string to string mapping in a php array and further down, check if a particular key exist in that array?
So can you expect that it will behave like the c function, accepting strings only? No! It now accept both strings and integers. So you have part perl there.
Now take another function. ctype_digit(). I don't know where the name come from. You expect it to behave like strlen() accepting both strings and numbers. But no!
If you pass it a number, it won't even bat an eye (throw an exception or error), but it will just return gibberish...
Hope my point, that these influences are mixed together in a haphazard fashion, is a bit more clear now...
If ctype_digit(100) magically got converted to ctype_digit('100'), that would not be a problem.
$id = intval(@$_POST['id']);
if (!empty($id)) { ... }
Note: 0 shouldn't be a valid value for something called 'id', since it's likely a db id; if it is, use something other than an empty() check.That said, input is a problem. In a dynamically typed language, it's easy for beginners to expect HTTP and requests in PHP work the same way. In reality, you will be coercing from string to wherever you are working with, which could also be an array of strings, or vice versa.
Input rules would be nice. For example, we always want id to always be an unsigned integer in this context and email will always be... and so on.
Dynamic typing makes, in this case, two types look like either plain old dynamic typing or leads to believing input has a homogeneous type.
In any case, I'm going to take a mag glass to some of our code today. Thanks!
That is hilarious! I love how even stuff that is supposed to fix other stuff itself end up being completely broken. But hey, it is documented.
Learning Php is like taking a massive loan. It get you started easily, but causes eternal suffering in the long run...
> if(!ctype_digit($_POST['ID'])) { // throw exception here }
ctype_digit is broken. Try passing integer values. ctype_digit(50) === true, but ctype_digit(100) === false. And "0000001" passes as true, which most people in the majority of scenarios would prefer not to pass. I can't remember what the even-worse-bug is with ctype_digit is, but even if you cast the value to string [ex: ctype_digit((string)$var))], there is some value that still passes for true when it shouldn't - do not use ctype_digit. is_numeric() is also unusable for validation [is_numeric("123e4") === true]. is_int() is a strict type-check so can't solely be used to validate request variables which are always strings (...or arrays, more below).
The only correct ways to verify that a variable contains either a valid numeric string or integer is by comparing type, and then using a regex or a double string-then-int cast.
ex: unsigned database ids: if ((is_int($var) || is_string($var)) && preg_match('/^[1-9]\d*\z/', $var)) { // definitely an int > 0 }
ex: signed integer: if (is_int($var) || (is_string($var) && (string)(int)$var === $var)) { // valid int (including negative values) }
Frankly, developers who don't understand how request variables are handled in PHP have zero chance of properly validating input. Find any site/app written in php, even if built on any of the major frameworks. You can instantly break 30-50% of them by passing an array where a string is expected.
Find an app that takes "?query=hello+world". Instead pass in "?query[]=hello+world". Want an example? Log in to Facebook, then visit this search page[1]. Look at the query string and then what was searched for - and the contents of the search box. Bam, even Facebook gets it wrong! Same thing with Symfony's search[2]. Or Packagist (composer's package manager repository)[3]. More seriously at Yii[4], which exposes an internal error to users as they try to string-trim an array ("Error - trim() expects parameter 1 to be string, array given").
Most developers - including many seniors who have been exclusively coding in php for years - have no clue. You will either cause a 500 Internal Server Error, or your input array will result in an output string of "Array" if they typecast your array to the string they expected. Even the major frameworks, when you pull user-submitted values, simply passthrough the value submitted. Your app expects a string (or a string that contains a numeric value), and instead any user who knows the "[]" syntax can pass in an array.
Really reflect on this fact. Most applications start handling a submitted array value as if it's a string. The bugs this produces are astronomical in some cases.
If you think your framework protects you, think again. The frameworks' request objects also do not have strict type checking. The same goes for their form and model validation classes; if you're using the built-in "integer" or "numeric" validators, you're probably doing things wrong.
It's a nightmare. You could try to blame PHP, but really it's the developers - including the developers of every major well-known framework I've ever touched - that have absolutely no clue.
Related tangent: comparing password and password confirmation fields. Many developers do if ($password == $passwordConfirm) {}. In PHP 5.x, "10" == "0xA" (so type "10" in password field and "0xA" in the confirmation field, and it passes validation). This changed in PHP 7 though. There are only two correct ways to verify that two strings are exact: $password === $passwordConfirm (triple equals), or strcmp($password, $passwordConfirm) === 0.
[1] https://www.facebook.com/search/top/?q[]=hello
[2] https://symfony.com/search?q[]=hello
[3] https://packagist.org/search/?q[]=hello
[4] http://www.yiiframework.com/search/?q[]=hello