¿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.
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.
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
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.
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'];
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.
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.
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 }
Activa Javascript para para cargar los comentarios, basados en DISQUS
El Blog de ElCodiguero funciona sobre Pelican