46

This isn't a big issue for me (as far as I'm aware), it's more of something that's interested me. But what is the main difference, if any, of using is_numeric over preg_match (or vice versa) to validate user input values.

Example One:

<?php
    $id = $_GET['id'];
    if (!preg_match('/^[0-9]*$/', $id)) {
        // Error
    } else {
        // Continue
    }
?>

Example Two:

<?php
    $id = $_GET['id'];
    if (!is_numeric($id)) {
        // Error
    } else {
        // Continue
    }
?>

I assume both do exactly the same but is there any specific differences which could cause problems later somehow? Is there a "best way" or something I'm not seeing which makes them different.

Bas Slagter
  • 9,831
  • 7
  • 47
  • 78
no.
  • 2,356
  • 3
  • 27
  • 42

12 Answers12

73

is_numeric() tests whether a value is a number. It doesn't necessarily have to be an integer though - it could a decimal number or a number in scientific notation.

The preg_match() example you've given only checks that a value contains the digits zero to nine; any number of them, and in any sequence.

Note that the regular expression you've given also isn't a perfect integer checker, the way you've written it. It doesn't allow for negatives; it does allow for a zero-length string (ie with no digits at all, which presumably shouldn't be valid?), and it allows the number to have any number of leading zeros, which again may not be the intended.

[EDIT]

As per your comment, a better regular expression might look like this:

/^[1-9][0-9]*$/

This forces the first digit to only be between 1 and 9, so you can't have leading zeros. It also forces it to be at least one digit long, so solves the zero-length string issue.

You're not worried about negatives, so that's not an issue.

You might want to restrict the number of digits, because as things stand, it will allow strings that are too big to be stored as integers. To restrict this, you would change the star into a length restriction like so:

/^[1-9][0-9]{0,15}$/

This would allow the string to be between 1 and 16 digits long (ie the first digit plus 0-15 further digits). Feel free to adjust the numbers in the curly braces to suit your own needs. If you want a fixed length string, then you only need to specify one number in the braces.

starball
  • 20,030
  • 7
  • 43
  • 238
Spudley
  • 166,037
  • 39
  • 233
  • 307
  • So with the Regex how would you rectify the problem of the leading zeros etc? This could cause problems for me after all, since this is the method I've been using. If I just want to validate that it was a whole number (4,8,15,16,23,42,108 etc), no leading zeros, no decimal points, no negatives, how would this be achieved? – no. Oct 04 '11 at 14:59
  • Thank you Spudley, that is a very clear explanation - definitely have a better understanding of their differences. Looks like I'll be going back and adjusting some of my code! :) – no. Oct 04 '11 at 15:15
  • @Spudley, what should use for decimal values? – er.irfankhan11 Feb 20 '18 at 13:42
  • @er.irfankhan11: the answer depends on things like how many decimal places you want to allow, etc. A clue: Use `\.` or `[.]` for the decimal dot, and similar rules to the above for the digits after it. If you need more detail, please ask it as a separate question. – Spudley Feb 25 '18 at 18:47
  • Probably one of the best explanations I ever seen in a forum. To this day I suck with regular expressions. It's gibberish to me! I've seen people help others with regex, but they usually lack explanation. Thanks for this solid answer. Now I don't have to feel intimidated by them anymore. Thanks! – DavidG Jun 23 '18 at 14:31
  • PHP's `is_numeric` will return `true` for `1337e0` which is *not* a number. It might be considered a number in a more niche field though for programmers it's not only overkill it forces people to ask this question. – John Sep 06 '21 at 08:11
12

According to http://www.php.net/manual/en/function.is-numeric.php, is_numeric alows something like "+0123.45e6" or "0xFF". I think this not what you expect.

preg_match can be slow, and you can have something like 0000 or 0051.

I prefer using ctype_digit (works only with strings, it's ok with $_GET).

<?php
  $id = $_GET['id'];
  if (ctype_digit($id)) {
      echo 'ok';
  } else {
      echo 'nok';
  }
?>
rap-2-h
  • 30,204
  • 37
  • 167
  • 263
  • So `ctype_digit` just checks for rounded numbers, what about if it's a positive or negative number? How does it work with that? – no. Oct 04 '11 at 15:01
  • 1
    It works only with positive numbers, I think it can be useful for an id (I don't have negative Ids in my code): var_dump(ctype_digit('-5')); // returns false – rap-2-h Oct 04 '11 at 15:15
  • I, too, would probably use `ctype_digit()` for this task. I should note, though, that `ctype_digit()` will allow `00000004`; so if you don't want zero left-padding to be possible, you can cast as an int explicitly `$id = (int)$id` (after the `ctype_digit()` check). – mickmackusa Jan 04 '22 at 22:43
7

is_numeric() allows any form of number. so 1, 3.14159265, 2.71828e10 are all "numeric", while your regex boils down to the equivalent of is_int()

Marc B
  • 356,200
  • 43
  • 426
  • 500
5

is_numeric checks whether it is any sort of number, while your regex checks whether it is an integer, possibly with leading 0s. For an id, stored as an integer, it is quite likely that we will want to not have leading 0s. Following Spudley's answer, we can do:

/^[1-9][0-9]*$/

However, as Spudley notes, the resulting string may be too large to be stored as a 32-bit or 64-bit integer value. The maximum value of an signed 32-bit integer is 2,147,483,647 (10 digits), and the maximum value of an signed 64-bit integer is 9,223,372,036,854,775,807 (19 digits). However, many 10 and 19 digit integers are larger than the maximum 32-bit and 64-bit integers respectively. A simple regex-only solution would be:

/^[1-9][0-9]{0-8}$/ 

or

/^[1-9][0-9]{0-17}$/

respectively, but these "solutions" unhappily restrict each to 9 and 19 digit integers; hardly a satisfying result. A better solution might be something like:

$expr = '/^[1-9][0-9]*$/';
if (preg_match($expr, $id) && filter_var($id, FILTER_VALIDATE_INT)) {
    echo 'ok';
} else {
    echo 'nok';
}
Jacob Lee
  • 425
  • 1
  • 7
  • 12
5

is_numeric would accept "-0.5e+12" as a valid ID.

user187291
  • 53,363
  • 19
  • 95
  • 127
5

Not exactly the same.

From the PHP docs of is_numeric:

'42' is numeric
'1337' is numeric
'1e4' is numeric
'not numeric' is NOT numeric
'Array' is NOT numeric
'9.1' is numeric

With your regex you only check for 'basic' numeric values.

Also is_numeric() should be faster.

PeeHaa
  • 71,436
  • 58
  • 190
  • 262
  • So using `is_numeric` to check against say a post entry on a forum which all have unique IDs assigned by `auto_increment`, it may cause problems since the database just has integers, no decimals, symbols etc – no. Oct 04 '11 at 14:56
  • @Joe Mottershaw: if that's the case you would use `is_int()` – PeeHaa Oct 04 '11 at 14:57
  • @JoeMottershaw: or perhaps better you would type cast it to an int: `$id = (int) $_REQUEST['id'];` – PeeHaa Oct 04 '11 at 15:00
3

is_numeric checks more:

Finds whether the given variable is numeric. Numeric strings consist of optional sign, any number of digits, optional decimal part and optional exponential part. Thus +0123.45e6 is a valid numeric value. Hexadecimal notation (0xFF) is allowed too but only without sign, decimal and exponential part.

Tobias
  • 9,170
  • 3
  • 24
  • 30
2

You can use this code for number validation:

if (!preg_match("/^[0-9]+$/i", $phone)) {
        $errorMSG = 'Invalid Number!';
        $error    = 1;
    }
Vicky Mahale
  • 1,209
  • 11
  • 18
  • Why in the world would you need a case-insensitive flag for a completely numeric character class? Can you show me an example of an uppercase number and a lower case number? – mickmackusa Jan 04 '22 at 22:40
  • Stop being so rude and scientist. Just say numbers don't have any case, so, please remove the `i`. – Stack Overflow Dec 02 '22 at 20:30
1

PHP's is_numeric function allows for floats as well as integers. At the same time, the is_int function is too strict if you want to validate form data (strings only). Therefore, you had usually best use regular expressions for this.

Strictly speaking, integers are whole numbers positive and negative, and also including zero. Here is a regular expression for this:

/^0$|^[-]?[1-9][0-9]*$/

OR, if you want to allow leading zeros:

/^[-]?[0]|[1-9][0-9]$/

Note that this will allow for values such as -0000, which does not cause problems in PHP, however. (MySQL will also cast such values as 0.)

You may also want to confine the length of your integer for considerations of 32/64-bit PHP platform features and/or database compatibility. For instance, to limit the length of your integer to 9 digits (excluding the optional - sign), you could use:

/^0$|^[-]?[1-9][0-9]{0,8}$/

Community
  • 1
  • 1
mixabo
  • 11
  • 2
1

Meanwhile, all the values above will only restrict the values to integer, so i use

/^[1-9][0-9\.]{0,15}$/

to allow float values too.

tormuto
  • 587
  • 5
  • 16
1

If you're only checking if it's a number, is_numeric() is much much better here. It's more readable and a bit quicker than regex.

The issue with your regex here is that it won't allow decimal values, so essentially you've just written is_int() in regex. Regular expressions should only be used when there is a non-standard data format in your input; PHP has plenty of built in validation functions, even an email validator without regex.

Bojangles
  • 99,427
  • 50
  • 170
  • 208
  • 2
    I don't think `is_int()` does what you think it does -- it actually checks the type of variable being checked; it isn't a string validator. – Spudley Oct 04 '11 at 14:56
0

You can use filter_var() to check for integers in strings

<?php
$intnum = "1000022";
if (filter_var($intnum, FILTER_VALIDATE_INT) !== false){
    echo $intnum.' is an int now';
}else{
    echo "$intnum is not an int.";
}
// will output 1000022 is an int now
relipse
  • 1,730
  • 1
  • 18
  • 24