Wednesday, January 6, 2010

Recipe 9.3. Validating Form Input: Numbers










Recipe 9.3. Validating Form Input: Numbers



9.3.1. Problem


You want to make

sure a number is entered in a form
input box. For example, you don't want someone to be able to say that her age is "old enough" or "tangerine," but instead want values such as 13 or 56.




9.3.2. Solution


If you're looking for an integer larger than or equal to zero, use
ctype_digit( ), as shown in Example 9-12.


Validating a number with ctype_digit( )



<?php
if (! ctype_digit($_POST['age'])) {
print 'Your age must be a number bigger than or equal to zero.';
}
?>




If you're looking for a positive or negative integer, compare the submitted value to what you get when casting it to an integer and then back to a string, as in Example 9-8.


Validating an integer with typecasting



<?php
if ($_POST['rating'] != strval(intval($_POST['rating']))) {
print 'Your rating must be an integer.';
}
?>




If you're looking for a positive or negative decimal number, compare the submitted value to what you get when casting it to a floating-point number and then back to a string, as in Example 9-8.


Validating a decimal number with typecasting



<?php
if ($_POST['temperature'] != strval(floatval($_POST['temperature']))) {
print 'Your temperature must be a number.';
}
?>






9.3.3. Discussion


Number validation is one of those things in PHP that seems like it's simple, but is a little trickier than it first appears. A common impulse is to use the built-in i
s_numeric( )
function for number validation. Unforunately, what is_numeric( ) thinks is "numeric" is more in line with how a computer behaves than a human. For example, is_numeric( ) considers hexadecimal number strings such as 0xCAFE and exponentially notated number strings such as 10e40 as numbers.


Something else to keep in mind when validating numbers (and all form input): values in $_GET and $_POST are always strings. That means that if someone submits a form with 06520 typed into a text box named zip_code, the value of $_POST['zip_code'] is the five character string 06520, not the integer 6,520.


So if what you need to validate is "this value consists only of digits," then ctype_digit( ) is the way to go. It is the fastest way to validate a number. ctype_digit( ), like all the ctype functions, requires its input to be a string, but that's taken care of for you when validating form input, since all values in $_GET and $_POST are strings.


Before PHP 5.1, ctype_digit( ) doesn't do what you expect if you give it an empty string (ctype_digit('') returns TRue), so be sure to check an input as described in Recipe 9.2 before passing it to ctype_digit( ). Also, a downside to ctype_digit( ) (in all versions of PHP) is that it's not very flexible. All it knows about are digits. If you want to accept negative numbers or decimal numbers, it can't help you.


In those cases, turn to two of PHP's typecasting functions:
intval( ), which "integerifies" a string, and
floatval( ), which "floatifies" it. Each of these functions, when given a string, do their best to produce a number from what's in the string. If the string just contains a valid number, that's what you get back. For example, intval('06520') returns the integer 6520, intval('-2853') returns the integer -2853, floatval('3.1415') returns the floating-point number 3.1415, and floatval('-473.20') returns the floating-point number -473.2.


Where these functions come in handy in input validation, however, is how they treat strings that aren't valid numbers. Each returns as much number as it can find in the string, starting from the beginning and ignoring initial whitespace. That is, intval('-6 weeks') returns -6, intval('30x bigger') returns 30, intval('3.1415') returns 3, and intval('21+up') returns 21. floatval( ) behaves similarly, but allows decimal points. For example, floatval('127.128.129.130') returns 127.128. When given a string with no valid number characters in it, both functions return 0.


This means that passing the user input through either intval( ) or floatval( ) works as a filter, leaving valid values unmodified, but changing invalid values to just their numerical essence. The resulting comparison with the original input succeeds if the value has passed through the filter without being modified'in other words, the comparison succeeds if the original input is a valid integer or decimal number.


It is necessary to convert what comes out of intval( ) or floatval( ) to a string with strval( ) to make sure PHP does the comparison properly. When PHP compares two strings, the comparison behaves as you'd expect. (The result is true if the strings are the same, and false otherwise.) However, when PHP compares a string and a number (such as the result of intval( ) or floatval( )), it attempts to convert the string to a number (using the rules outlined above). This would counteract the "filter" properties of intval( ) or floatval( ), so we need to prevent it from happening. Ensuring that two strings are compared accomplishes this.


If all of this typecasting has you feeling a bit queasy and you're a fan of
regular expressions, feel free to use those instead. Example 9-9 shows regular expressions that validate an integer and a decimal number.


Validating numbers with regular expressions



<?php
// The pattern matches an optional - sign and then
// at least one digit
if (! preg_match('/^-?\d+$/'$_POST['rating'])) {
print 'Your rating must be an integer.';
}

// The pattern matches an optional - sign and then
// Optional digits to go before a decimal point
// An optional decimal point
// And then at least one digit
if (! preg_match('/^-?\d*\.?\d+$/',$_POST['temperature'])) {
print 'Your temperature must be a number.';
}
?>




It is a common refrain among performance-tuning purists that regular expressions should be avoided because they are comparatively slow. In this case, however, with such simple regular expressions, they are about equally efficient as the typecasting. If you're more comfortable with regular expressions, or you're using them in other validation contexts as well, they can be a handy choice. The regular expression also allows you to consider valid numbers, such as 782364.238723123, that cannot be stored as a PHP float without losing precision. This can be useful with data such as a longitude or latitude that you plan to store as a string. The regular expression also allows you to consider valid numbers, such as 782364.238723123, that cannot be stored as a PHP float without losing precision. This can be useful with data such as a longitude or latitude that you plan to store as a string. That said, the ctype_digit( ) function is much faster than either typecasting or a regular expression, so if that does what you need, use it.




9.3.4. See Also


Recipe 9.2 for information on validating required fields; documentation on ctype_digit( )


at http://www.php.net/ctype_digit.













No comments: