Desarrollo

Cómo validar datos de un formulario en PHP – utilidades

Desarrollo PHP

En el artículo anterior vimos cómo validar datos de un formulario utilizando funciones básicas que ya vienen incorporadas en PHP. Gracias a ellas pudimos ver cómo controlar que los datos recibidos fueran del tipo que queríamos y unos cuantos comportamientos más. En este artículo, sin embargo, vamos a ver una serie de funciones de ejemplo, que nos servirán como utilidades para validar datos de un formulario.

Estas funciones están creadas por los propios programadores (muchas las podéis encontrar fácilmente) pero que nos ayudarán para otras tareas como, por ejemplo, comprobar si una fecha o un email son válidos u otro tipo de cosas como comprobar si un IBAN de una cuenta corriente bancaria es válido.

Estad atentos, pues en algunas ocasiones estas funciones utilizarán algunas instrucciones y procedimientos que todavía no hemos visto, por lo que igual os echan algo para atrás. No os preocupéis, las podéis utilizar sin problemas y, pese a no haberlas visto aún, es un buen momento ahora para verlas en general, pues preferimos tener ahora mismo sus utilidades para validar datos de un formulario.

Comprobar si una variable está totalmente vacía

Empezamos con algo sencillo aunque, desgraciadamente, utiliza recursividad, algo que veremos dentro de un par de semanas desde que se publique este artículo. Pero podemos usarla mientras tanto aunque no entremos en detalle y guardarla en nuestra mochila de utilidades para validar datos de un formulario. Veámosla:

function isEmpty( $value, $recursive = false ) {
	if ( $recursive && is_array( $value ) ) {
		$empty = true;
		foreach ( $value as $item ) {
			$empty = $empty && isEmpty( $item, $recursive );
		}
			return $empty;
	}

	if ( is_string( $value ) ) {
		return '' === trim( $value );
	}

	return ! $value;
}

Esta función recibe un $value que puede ser tanto una variable como un array y nos indica si la variable está vacía o no.

Un array vacío

Cuando queramos comprobar si un array está vacío deberemos pasarle $recursive como valor true, aunque después veremos una forma de “empaquetarlo” que nos será más útil.

El primer IF comprueba que, si le hemos indicado recursividad y, además, $value es un array después de comprobarlo con la función predefinida is_array, que ya vimos en su momento. Si entramos por aquí, es decir, si queremos comprobar que un array está vacío, inicialmente colocamos $empty como verdadero y luego recorremos todos los valores del array para comprobar, uno a uno, si están vacíos.

Por tanto, esta función puede que reciba un array de 10 elementos pero que todos estén vacíos y, si es así, decirnos que el array está vacío. Así que cuidado con ésto, pues el array sí tiene 10 elementos pero están todos vacíos. Este detalle, si queréis, se puede modificar fácilmente.

En definitiva, que recorremos los elementos y nos guardamos en $empty el resultado de calcular el propio $empty y (operador &&) si el valor que estamos recorriendo del array está vacío. Ésto se hace volviendo a llamar a la propia función que hemos construido, y es lo que se llama recursividad, lo cual lo veremos, como hemos dicho, dentro de un par de semanas. La utilización del operador && sirve para que compruebe que todos los valores del array están vacíos. Si hubiera uno que no lo está, ya tendríamos un $empty como falso y todas esas operaciones AND devolverían falso.

Un string vacío

Si no entramos en el primer IF es que no queremos comprobar si un array está vacío. Pasemos a comprobar si un string está vacío y, para ello, primero nos aseguramos con la función is_string si la variable recibida es un string. Si lo es, sólo tenemos que ver si es igual a la cadena vacía, es decir, a ”.

Para ello utilizamos primero la función trim, la cual elimina los espacios en blanco que pueda haber delante y detrás del string. Así, un string definido como ‘ ‘ será, a nuestros efectos, vacío. Para todos los demás casos, el string no estará vacío así que el return de este caso devolverá un false.

Un entero vacío

¿Y si $value contiene un entero u otro tipo de datos? No hay problema, pues nunca será vacío excepto si es un 0. Con la última línea, ese return ! $value le indicamos que devuelva la negación del valor. Cualquier valor entero tiene, implícitamente, el valor verdadero excepto si se trata del 0 que se define como un valor falso, así que esta función sí nos indicará que el 0 es un valor vacío.

Pequeño resumen de isEmpty

Por tanto, nuestra función isEmpty nos indicará que la variable está vacía si le pasamos un array con todos sus elementos vacíos, le pasamos un string que contiene la cadena vacía o una cadena solo de espacios en blanco, o si le pasamos el número cero.

Si os interesa otro tipo de resultado, como que por ejemplo el 0 no lo considere vacío, simplemente podéis modificar esta función a vuestro gusto. Todo para guardarlo como utilidades para validar datos de un formulario que podamos usar cuando sea necesario.

Comprobar si un array está vacío

¿Cómo podemos evitar el tener que recordar que, si utilizamos un array en la función anterior, tenemos que ponerle la variable $recursive como true? Creando una nueva función que utilice a la anterior:

function isArrayEmpty( $array ) {
	return isEmpty( $array, true );
}

Así, siempre podemos utilizar isArrayEmpty y olvidarnos de colocarle ese true en el segundo argumento de isEmpty, pues ya lo incorpora “de serie”. Recordad que un array que, por ejemplo, fuera array(0, 0, 0) hemos quedado que lo interpretaría como array vacío, pues todos sus elementos son 0. Repetimos, ésto debe ser al gusto de cada uno y podéis modificarlo, pero para comprobar si un array está vacío de verdad, basta con, por ejemplo, mirar si el número de elementos que contiene es 0. Sería sencillo.

Comprobar si un email es válido

Actualmente HTML tiene en sus <input> un tipo para los emails que ya comprueba por sí mismo si el email introducido es válido o no. Sin embargo, podemos tenerlo alguna vez como texto o podemos recibirlo desde otro lado y queremos comprobar si es válido o no. Para ello, podemos utilizar la siguiente función:

function isEmailValid( $value ) {
	$value = strtolower( $value );

	// Valida si el email es válido, pero permite emails del tipo info@localhost
	$regex1 = '/^[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/i';

	// Comprueba que existe un dominio para paliar el defecto de la expresión anterior
	$regex2 = '/^.+@.+\.[a-z]{2,4}$/i';

	return ( preg_match( $regex1, $value ) && preg_match( $regex2, $value ) );
}

Vayamos por partes. En primer lugar utilizamos strtolower para transformar todos los caracteres en minúsculas, por comodidad.

Luego, en $regex1 nos guardaremos toda esa cadena de caracteres que parecen extrañísimos que pudimos ver cómo funcionaban en el artículo anterior. En realidad, son los patrones para comprobar que tanto la primera parte de un email como la segunda, existen y contienen los caracteres válidos. Fijáos que, al final, devolveremos true si la primera parte y la segunda son válidas.

Comprobar si una URL es válida

También podemos hacer la comprobación sobre si una URL que nos han indicado es válida o no, incluyendo los protocolos permitidos (que no son otros que http y https en principio). Así pues, la función quedaría así:

function isUrlValid( $value, $allowedProtocols = [ 'http', 'https' ] ) {
	if ( filter_var( $value, FILTER_VALIDATE_URL ) === false ) {
		return false;
	}

	foreach ( $allowedProtocols as $protocol ) {
		if ( preg_match( "#^$protocol://#", $value ) ) {
			return true;
		}
	}

	return false;
}

Inicialmente utilizamos la función filter_var que vimos en el artículo anterior pasándole el valor constante FILTER_VALIDATE_URL para que haga una primera validación. Si este filtro falla, entonces no estamos en una URL. Si no fallara, pasamos a recorrer los protocolos permitidos que hemos indicado, por defecto, que serán http y https, pero que se pueden modificar. De nuevo con preg_match buscamos que el patrón habitual (con los dos puntos y las dos barras inclinadas) esté y devolvemos true si es así. Si no fuera así, la URL que nos han indicado no es válida.

Comprobar si un NIF o NIE es válido

En España todo el mundo tiene un NIF, siglas de Número de Identificación Fiscal. Básicamente es el número que se indica en el DNI español, pero también sirve para personas jurídicas actualmente. Para los extranjeros, este número se llama NIE, de Número de Identificación de Extranjero. En fin, que este número responde a una serie de reglas y patrones definidos y, por tanto, podemos validar si el que nos indican es válido. Veamos cómo:

function isNifValid( $value ) {
	$value = strtoupper( $value );

	$nifRegex = '/^[0-9]{8}[A-Z]$/i';
	$nieRegex = '/^[XYZ][0-9]{7}[A-Z]$/i';
	$letters  = "TRWAGMYFPDXBNJZSQVHLCKE";

	// Si es un NIF
	if ( preg_match( $nifRegex, $value ) ) {
		$letterIndex = substr( $value, 0, 8 ) % 23;
		$letter      = $letters[ $letterIndex ];

		return $letter === $value[8];
	}

	// Si es un NIE
	if ( preg_match( $nieRegex, $value ) ) {
		$value[0]    = $value[0] === 'X' ? '0' : ( $value[0] === 'Y' ? '1' : 2 );
		$letterIndex = substr( $value, 0, 8 ) % 23;
		$letter      = $letters[ $letterIndex ];

		return $letter === $value[8];
	}

	/// Si no es ni NIF ni NIE
	return false;
}

Vayamos paso por paso. Tenemos tres bloques principales en esta función. En el primero pasamos todos los caracteres a mayúsculas para facilitar los pasos. Posteriormente, guardamos en sendas variables los patrones para el NIF y NIE respectivamente, así como las 23 letras que se utilizan para ellos con ese orden establecido de antemano.

Comprobar si es un NIF

Después, en primer lugar, vamos a comprobar si es un NIF. Para ello, primero utilizamos la ya típica preg_match para ver si el valor que nos otorgan tiene el patrón definido (ocho números y una letra). Si es así, gracias a substr extraemos únicamente el número y obtenemos el resto, con la operación módulo, de la división por 23, que son las letras utilizadas. ¿Qué conseguimos con ésto? Calculando el índice de dónde está la letra que queremos obtener, y con él obtendríamos qué letra le corresponde a ese número en concreto. Una vez tenemos esa letra, comprobamos al final si coincide la letra que nos han dado ($value[8] contiene la posición donde está la letra) con la que hemos calculado. Si es cierto, entonces tenemos un NIF válido.

Comprobar si es un NIE

Si no lo es, entonces vamos a ver si es un NIE. Los NIE actuales tienen al principio una letra X o una letra Y, pero ésto podría cambiar (de hecho, primero sólo era una X y más tarde tuvieron que usar la Y para no quedarse sin números). Comprobamos con preg_match si el patrón es correcto y luego tenemos que hacer alguna cosita más. Para obtener la letra, se cambia la X por un 0 y la Y por un 1, así que eso hacemos en la primera línea mediante una estructura de control ternaria, que todavía no hemos visto pero a la que ya llegaremos. La cuestión es que obtenemos un número como si fuera el del NIF, así que procedemos a calcular su letra y hacer los mismos pasos que hemos hecho antes.

¿Que al final encontramos que todo encaja? Entonces devolvemos true pues estamos ante un NIF o NIE válido. ¿Que no? Pues finalmente, si llegamos a esa instrucción final, devolvemos false porque lo que nos han pasado no es válido.

Obviamente, podríamos separar esta función en dos distintas si quisiéramos comprobar por separado si es un NIF o un NIE. Eso ya al gusto o necesidades del programador y así enriquecer las utilidades para validar datos de un formulario.

Comprobar si un CIF es válido

No vamos a pararnos mucho a explicar paso por paso la siguiente función. En ella comprobaremos si un CIF es válido. Pese a que, en España, hace años que todo se trata como NIF, todavía se usa coloquialmente el CIF y, además, tiene unas reglas un poco distintas. Empieza por letra, puede acabar en número o letra y otras cositas que no nos vamos a parar ahora a desarrollar porque no es objeto de este artículo. En fin, que para los que la necesiten, la función para comprobar si un CIF es válido es la siguiente:

function isCifValid( $value ) {
	$value = strtoupper( $value );

	$cifRegex1 = '/^[ABEH][0-9]{8}$/i';
	$cifRegex2 = '/^[KPQS][0-9]{7}[A-J]$/i';
	$cifRegex3 = '/^[CDFGJLMNRUVW][0-9]{7}[0-9A-J]$/i';
	$letters   = "JABCDEFGHI";

	if ( preg_match( $cifRegex1, $value ) || preg_match( $cifRegex2, $value ) || preg_match( $cifRegex3, $value ) ) {
		$control = $value[ - 1 ];
		$sumA    = 0;
		$sumB    = 0;

		for ( $i = 1; $i < 8; $i ++ ) {
			if ( $i % 2 == 0 ) {
				$sumA += intval( $value[ $i ] );
			} else {
				$t = ( intval( $value[ $i ] ) * 2 );
				$p = 0;

				for ( $j = 0; $j < strlen( $t ); $j ++ ) {
					$p += substr( $t, $j, 1 );
				}
				$sumB += $p;
			}
		}

		$sumC = ( intval( $sumA + $sumB ) ) . "";
		$sumD = ( 10 - intval( $sumC[ strlen( $sumC ) - 1 ] ) ) % 10;

		if ( $control >= "0" && $control <= "9" ) {
			return ( $control == $sumD );
		}

		return ( strtoupper( $control ) == $letters[ $sumD ] );
	}

	return false;
}

Comprobar si una fecha es válida

Dedicaremos algunos artículos al trabajo con fechas en PHP, pues tiene su recorrido e incluso cierta complejidad. Pero podemos presentar aquí una función sencilla para comprobar si una fecha es válida:

function isDateValid( $date, $format = 'd/m/Y' ) {
	$datetime = \DateTime::createFromFormat( $format, $date );

	return $datetime && $datetime->format( $format ) === $date;
}

Sí, hay varias cosas aquí que no tenemos ni idea de lo que son. Ese \DateTime, esos dos puntos a continuación, esa -> que hay por ahí… Son aspectos que veremos más en el futuro cuando trabajemos con clases pero, por ahora, nos conformamos con saber que la función funciona, valga la redundancia.

Recibido un string en la variable $date, como por ejemplo podría ser 15/01/2020, y un formato en la variable $format que hemos puesto, por defecto, ‘d/m/Y’ tal cual con esas minúsculas y mayúsculas (en su momento veremos por qué), esta función nos devuelve si la fecha es válida o no. Como comentamos, más adelante desarrollaremos el tema de fechas, pero por ahora podéis saber que, si utilizáis la notación anglosajona de poner el mes primero, entonces cambiad el formato predefinido a ‘m/d/Y’ o no os funcionará.

Comprobar si un color hexadecimal es válido

Muchas veces, especialmente en diseño web, nos encontraremos con la necesidad de indicar un color en formato hexadecimal del tipo #FF11AA, por ejemplo. Con la siguiente función comprobaremos si el color que nos pasan es válido:

function isColorValid( $color ) {
	return preg_match( '/^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/', $color );
}

La función es muy sencilla. Simplemente utilizamos de nuevo preg_match para comprobar que existe un patrón que es, en definitiva, que primero encontramos el carácter almohadilla (#) y luego podemos encontrar el color o con un formato de 3 dígitos hexadecimales (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F) o con un formato de 6 dígitos.

Comprobar si un IBAN es válido

Un IBAN, siglas de International Bank Account Number, es el código que tienen las cuentas bancarias en cualquier lugar del mundo o, al menos, en una gran cantidad de países que han optado por esta nomenclatura de cuentas bancarias. Con la función que vamos a presentar podemos comprobar si el IBAN que nos pasan es válido.

Atención porque esta función va a ser un poco alargada debido a cómo hemos definido el array para que no se nos vaya muy horizontalmente.

function isIbanValid( $value ) {
	$iban = strtolower( str_replace( ' ', '', $value ) );

	$ibanLengthByCountry = [
		'ad' => 24,
		'ae' => 23,
		'al' => 28,
		'at' => 20,
		'az' => 28,
		'ba' => 20,
		'be' => 16,
		'bg' => 22,
		'bh' => 22,
		'br' => 29,
		'ch' => 21,
		'cr' => 21,
		'cy' => 28,
		'cz' => 24,
		'de' => 22,
		'dk' => 18,
		'do' => 28,
		'ee' => 20,
		'es' => 24,
		'fi' => 18,
		'fo' => 18,
		'fr' => 27,
		'gb' => 22,
		'ge' => 22,
		'gi' => 23,
		'gl' => 18,
		'gr' => 27,
		'gt' => 28,
		'hr' => 21,
		'hu' => 28,
		'ie' => 22,
		'il' => 23,
		'is' => 26,
		'it' => 27,
		'jo' => 30,
		'kw' => 30,
		'kz' => 20,
		'lb' => 28,
		'li' => 21,
		'lt' => 20,
		'lu' => 20,
		'lv' => 21,
		'mc' => 27,
		'md' => 24,
		'me' => 22,
		'mk' => 19,
		'mr' => 27,
		'mt' => 31,
		'mu' => 30,
		'nl' => 18,
		'no' => 15,
		'pk' => 24,
		'pl' => 28,
		'ps' => 29,
		'pt' => 25,
		'qa' => 29,
		'ro' => 24,
		'rs' => 22,
		'sa' => 24,
		'se' => 24,
		'si' => 19,
		'sk' => 24,
		'sm' => 27,
		'tn' => 24,
		'tr' => 26,
		'vg' => 24,
	];

	$charsNumericValue = [
		'a' => 10,
		'b' => 11,
		'c' => 12,
		'd' => 13,
		'e' => 14,
		'f' => 15,
		'g' => 16,
		'h' => 17,
		'i' => 18,
		'j' => 19,
		'k' => 20,
		'l' => 21,
		'm' => 22,
		'n' => 23,
		'o' => 24,
		'p' => 25,
		'q' => 26,
		'r' => 27,
		's' => 28,
		't' => 29,
		'u' => 30,
		'v' => 31,
		'w' => 32,
		'x' => 33,
		'y' => 34,
		'z' => 35,
	];

	$country = substr( $iban, 0, 2 );
	if ( strlen( $iban ) === ( $ibanLengthByCountry[ $country ] ?? - 1 ) ) {
		$rearranged        = substr( $iban, 4 ) . substr( $iban, 0, 4 );
		$rearrangedAsArray = str_split( $rearranged );
		$numericIban       = '';

		foreach ( $rearrangedAsArray AS $key => $value ) {
			if ( ! is_numeric( $value ) ) {
				if ( ! array_key_exists( $value, $charsNumericValue ) ) {
					return false;
				}

				$value = $charsNumericValue[ $value ];
			}

			$numericIban .= $value;
		}

		if ( bcmod( $numericIban, '97' ) === '1' ) {
			return true;
		}
	}

	return false;
}

No vamos a detenernos a explicar las reglas básicas de formación de las cuentas bancarias. Nos quedamos, ahora mismo, con que la función funciona y podemos comprobar si un IBAN que nos han pasado es válido o no.

Crea y construye tus propias utilidades para validar datos de un formulario

Hemos visto unos cuantos ejemplos de funciones que podemos crear para validar nuestros datos y añadirlas a las utilidades para validar datos de un formulario. Pero las posibilidades son infinitas y cada uno tendrá que buscar, en cada momento, que los datos que se le introducen son correctos. Si tenéis alguna duda, podéis registraros y preguntarla en los comentarios.

Porque una mala validación puede arruinar programas enteros, y porque malos datos proporcionados por el usuario pueden ser fatales o llevarnos a errores difíciles de corregir. Nunca está de más controlar que todos los datos que recibimos son realmente correctos.

No hay que presuponer, jamás, que el usuario introducirá una fecha válida, por ejemplo. Ni siquiera si usamos un <input> de introducción de fechas. Hay formas de modificar, queriendo o sin querer, los datos de entrada y, por ello, cualquier precaución es poca.

Seguimos viendo formularios

Estamos llegando casi al final de la serie de artículos dedicados a los formularios, pero todavía nos faltará por ver alguna cosa más y acabar practicando. En el próximo artículo veremos cómo añadir archivos desde un formulario para, posteriormente, acabar con otro reto de A practicar… donde utilizaremos todo lo visto estas semanas y que podéis revisar visitando el índice de contenidos.

Deja una respuesta