Blog ElCodiguero
08 Jan 2010 PHP

Validar parámetros enteros con PHP

Introducción

¿Cómo hacer para estar seguro de que un parámetro obtenido por GET (o POST) es un número entero? Intentando resolver esta pregunta he intentado varias técnicas a lo largo del tiempo, y haciendo esas pruebas he llegado a entender más sobre cómo funcionan las comparaciones y las conversiones automáticas de tipos en PHP.

Convirtiendo a un valor entero

intval es una función que obtiene el valor entero de lo que se le pase como parámetro. Es equivalente a forzar una conversión anteponiendo (int) a un valor. Es decir, las siguientes dos líneas son equivalentes:

1  <?php
2  $valor = intval($parametro);
3  $valor = (int) $parametro;

De esta forma se obtiene un valor entero en todos los casos, aunque no necesariamente este valor es igual al que queremos validar, ya que cualquier cadena puede ser convertida a un entero, incluso una cadena que no contenga números (si no comienza con números, su valor es cero).

Pero una conversión no es una validación, así que debemos hallar una forma de asegurarnos de que el valor almacenado en $parametro es un número entero.

Primer intento: is_int

La primera idea es comprobar con la función is_int. Esta función, como su nombre lo indica, comprueba que una variable sea de tipo entero, devolviendo true en caso de que lo sea, y false en caso contrario.

Parece ideal, sin embargo hay un problema: los valores que se reciben vía GET o POST son siempre cadenas, sin importar su contenido. El manual de la función lo advierte:

Para probar si una variable es un número o una cadena numérica (como en el caso de la entrada de un formulario, que es siempre una cadena), debe usar is_numeric().

Lo podemos comprobar fácilmente: Supongamos que $_GET['var'] tiene el valor 12, es decir, recibimos una URL conteniendo &var=12.

En estas condiciones,

1  <?php
2  gettype($_GET['var']); // devuelve "string"
3  is_int($_GET['var']); // devuelve false

Segundo intento: is_numeric

La función is_numeric toma cualquier una variable (o valor) de cualquier tipo, y comprueba si tiene formato de número. Esto es, si es una cadena que representa un entero, o un flotante en cualquiera de las notaciones válidas para PHP. Serviría para nuestro propósito si no fuese porque no acepta solamente enteros sino también números con coma (punto, en realidad), y por lo tanto devolverá true para un valor como 10.33, por ejemplo.

Tercer intento: comparación con entero

Otra opción puede ser hacer una comparación como

1 <?php
2 $_GET['var'] == (int) $_GET['var'];

En teoría funcionaría, pero no es así. PHP es un tanto especial en lo que refiere a las conversiones de tipos: si uno de los valores a comparar es entero, convertirá al otro en entero antes de compararlos. Por lo tanto, la comparación propuesta siempre será cierta, ya que el lado izquierdo de la comparación será convertido. El resultado es que el código anterior es equivalente al siguiente, que es siempre verdadero y por lo tanto no nos sirve:

1 <?php
2 (int) $_GET['var'] == (int) $_GET['var'];

Cuarto intento: comparación de idénticos

Uno podría pensar que si PHP convierte a entero ambos lados y por eso no sirve el ejemplo anterior, bastaría con usar el operador === (comparación sin conversión) para saltear el problema. Pero no es así. Los valores siempre serán de tipos diferentes (ya que las variables GET son siempre cadenas) y por lo tanto la comparación sería siempre falsa.

Quinto intento: complicado pero funciona

Finalmente decidí probar algo más complejo pero que evita todos los problemas de los casos anteriores.

¿Qué hacer? La comparación con un entero es necesaria, porque después de todo lo que necesitamos es un entero, pero no podemos comparar una cadena con un entero porque la cadena será convertida. La solución que se me ocurrió es simplemente volver a convertir al entero en una cadena:

1 <?php
2 $_GET['var'] == (string) intval($_GET['var']);

o

1 <?php
2 $_GET['var'] == (string) (int) $_GET['var'];

Por ejemplo, si $_GET['var'] tiene el valor "12", el proceso sería:

1 <?php
2 intval("12") # --> entero 12
3 (string) intval("12") # --> cadena "12"
4 "12" == "12" # --> verdadero, contienen el mismo valor.

Si ahora $_GET['var'] tiene el valor "hola":

1 <?php
2 intval("hola") # --> entero 0
3 (string) intval("hola") # --> cadena "0"
4 "hola" == "0" # --> falso, la cadena pasada no es un entero.

Y finalmente, el caso que hace que las conversiones fallen: una cadena con valores numéricos y letras. Supongamos que $_GET['var'] tiene el valor "12hola".

1 <?php
2 intval("12hola") # --> entero 12
3 (string) intval("12hola") # --> cadena "12"
4 "12hola" == "12" # --> falso. Al evitar la conversión automática, no hay problemas.

Solución correcta: ctype_digit

Esta es una actualización de este artículo. Luego de mucho tiempo, di casi de casualidad con esta función, que sirve justamente para validar si una cadena contiene únicamente dígitos. Es increíble cómo uno puede utilizar un lenguaje durante años, y seguir encontrando cosas que no conocía, en particular esta función que no es para nada nueva, existe desde PHP 4.0.4

Es importante aclarar que esta función sirve solamente para variables de tipo cadena, ya que lo que hace es comprobar que cada caracter en el valor pasado como parámetro está en el rango ASCII que corresponde a los dígitos del 0 al 9 (valores entre 48 y 57 inclusive). No sirve para datos de tipo entero, y esto está claramente especificado en su página del manual. También es preciso tener en cuenta que un enterno negativo no será admitido, debido a que "-" no es un dígito.

Todo el trabajo que pasé intentando validar un entero se reduce, entonces a:

1 <?php
2 if (ctype_digit($variable)) {
3     echo "La variable es entera";
4 }

Enlaces relacionados

Activa Javascript para para cargar los comentarios, basados en DISQUS

El Blog de ElCodiguero funciona sobre Pelican

Inicio | Blog | Acerca de