Validación de formularios

Elementos obligatorios

Los elementos se marcan como obligatorios con el método setRequired(). Su argumento es el texto del mensaje de error que se mostrará si el usuario no rellena el elemento. Si no se indica ningún argumento, se usa el mensaje de error predeterminado.

$form->addText('name', 'Name:')
	->setRequired('Please fill in your name.');

Reglas

Las reglas de validación se añaden a los elementos con el método addRule(). El primer parámetro es la regla, el segundo el mensaje de error y el tercero el argumento de la regla de validación.

$form->addPassword('password', 'Password:')
	->addRule($form::MinLength, 'Password must be at least %d characters long', 8);

Las reglas de validación solo se comprueban si el usuario ha rellenado el elemento.

Nette trae varias reglas predefinidas cuyos nombres son constantes de la clase Nette\Forms\Form. Estas reglas se pueden aplicar a todos los elementos:

constante descripción tipo del argumento
Required elemento obligatorio, alias de setRequired()
Filled elemento obligatorio, alias de setRequired()
Blank el elemento no debe estar relleno
Equal el valor debe ser igual al parámetro mixed
NotEqual el valor no debe ser igual al parámetro mixed
IsIn el valor debe ser uno de los elementos del array array
IsNotIn el valor no debe ser ninguno de los elementos del array array
Valid ¿está el elemento relleno correctamente? (solo en addConditionOn())

Campos de texto

En los elementos addText(), addPassword(), addTextArea(), addEmail(), addInteger() y addFloat() se pueden aplicar además algunas de las siguientes reglas:

MinLength longitud mínima del texto int
MaxLength longitud máxima del texto int
Length longitud dentro de un rango o longitud exacta par [int, int] o int
Email dirección de correo válida
URL URL absoluta
Pattern encaja con la expresión regular string
PatternInsensitive como Pattern, pero sin distinguir mayúsculas string
Integer valor entero
Numeric entero no negativo (solo dígitos)
Float número
Min valor mínimo de un elemento numérico int|float
Max valor máximo de un elemento numérico int|float
Range valor dentro de un rango par [int|float, int|float]

Las reglas de validación Integer y Float convierten automáticamente el valor a entero o a float, respectivamente. Además, la regla URL acepta también una dirección sin esquema (p. ej. nette.org) y completa el esquema (https://nette.org). La expresión de Pattern y PatternInsensitive debe ser válida para el valor entero, es decir, como si estuviera envuelta entre los caracteres ^ y $.

Número de elementos

En los elementos addMultiUpload(), addCheckboxList() y addMultiSelect() puede usar además las siguientes reglas para limitar el número de elementos seleccionados o de archivos subidos:

MinLength número mínimo int
MaxLength número máximo int
Length número dentro de un rango o número exacto par [int, int] o int

Subida de archivos

En los elementos addUpload() y addMultiUpload() se pueden usar además las siguientes reglas:

MaxFileSize tamaño máximo del archivo en bytes int
MimeType tipo MIME, se permiten comodines ('video/*') string|string[]
Image imagen JPEG, PNG, GIF, WebP o AVIF
Pattern el nombre del archivo encaja con la expresión regular string
PatternInsensitive como Pattern, pero sin distinguir mayúsculas string

MimeType e Image requieren la extensión de PHP fileinfo. Que un archivo o una imagen sea del tipo requerido se detecta a partir de su firma, y no se comprueba la integridad del archivo entero. Si una imagen está dañada se puede averiguar, por ejemplo, intentando cargarla.

Mensajes de error

Todas las reglas predefinidas salvo Pattern y PatternInsensitive tienen un mensaje de error predeterminado, así que se pueden omitir. Pero, si indica y formula todos los mensajes propios a la medida de sus necesidades, hará el formulario más cómodo para el usuario.

Puede cambiar los mensajes predeterminados en la configuración, modificando los textos del array Nette\Forms\Validator::$messages, o usando un traductor.

En el texto de los mensajes de error se pueden usar los siguientes marcadores:

%d se sustituye sucesivamente por los argumentos de la regla
%n$d se sustituye por el n-ésimo argumento de la regla
%label se sustituye por la etiqueta del elemento (sin los dos puntos)
%name se sustituye por el nombre del elemento (p. ej. name)
%value se sustituye por el valor introducido por el usuario
$form->addText('name', 'Name:')
	->setRequired('Please fill in %label');

$form->addInteger('id', 'ID:')
	->addRule($form::Range, 'at least %d and at most %d', [5, 10]);

$form->addInteger('id', 'ID:')
	->addRule($form::Range, 'at most %2$d and at least %1$d', [5, 10]);

Condiciones

Además de reglas se pueden añadir condiciones. Se escriben de forma parecida a las reglas, pero en lugar de addRule() usamos el método addCondition() y, naturalmente, no indicamos ningún mensaje de error (la condición solo pregunta):

$form->addPassword('password', 'Password:')
	// si la longitud de la contraseña no es mayor que 8
	->addCondition($form::MaxLength, 8)
		// entonces debe contener un dígito
		->addRule($form::Pattern, 'Must contain a digit', '.*[0-9].*');

La condición se puede enlazar con un elemento distinto del actual mediante addConditionOn(). El primer parámetro es una referencia al elemento. En este ejemplo, el correo será obligatorio solo si el checkbox está marcado (es decir, su valor es true):

$form->addCheckbox('newsletters', 'Send me newsletters');

$form->addEmail('email', 'Email:')
	// si el checkbox está marcado
	->addConditionOn($form['newsletters'], $form::Equal, true)
		// entonces exige el correo
		->setRequired('Enter your email address');

Las condiciones se pueden componer en estructuras complejas con elseCondition() y endCondition():

$form->addText(/* ... */)
	->addCondition(/* ... */) // si se cumple la primera condición
		->addConditionOn(/* ... */) // y se cumple también la segunda condición sobre otro elemento
			->addRule(/* ... */) // exige esta regla
		->elseCondition() // si no se cumple la segunda condición
			->addRule(/* ... */) // exige estas reglas
			->addRule(/* ... */)
		->endCondition() // volvemos a la primera condición
		->addRule(/* ... */);

El primer argumento de addCondition() también puede ser un valor booleano. Es útil cuando la decisión ya se conoce mientras se construye el formulario, por ejemplo para aplicar una regla solo en determinadas circunstancias:

$form->addText('nickname')
	->addCondition($isRequired) // un valor conocido al construir el formulario
		->setRequired();

En Nette es muy fácil reaccionar en el lado de JavaScript a que se cumpla o no una condición, con el método toggle(), véase JavaScript dinámico.

Referencia a otro elemento

Como argumento de una regla o una condición también puede pasar otro elemento del formulario. La regla usará entonces el valor que el usuario introduzca más tarde en el navegador. Eso se puede usar, por ejemplo, para validar dinámicamente que el elemento password contiene la misma cadena que el elemento password_confirm:

$form->addPassword('password', 'Password');
$form->addPassword('password_confirm', 'Confirm Password')
    ->addRule($form::Equal, 'The passwords do not match', $form['password']);

Reglas y condiciones propias

A veces nos encontramos con situaciones en las que las reglas de validación integradas de Nette no bastan y necesitamos validar los datos del usuario a nuestra manera. ¡En Nette eso es muy sencillo!

Como primer parámetro de los métodos addRule() o addCondition() puede pasar cualquier callback. El callback acepta el propio elemento como primer parámetro y devuelve un valor booleano que indica si la validación tuvo éxito. Al añadir una regla con addRule() se pueden indicar argumentos adicionales, que se pasan después como segundo parámetro.

Un conjunto propio de validadores se puede crear, por tanto, como una clase con métodos estáticos:

class MyValidators
{
	// comprueba si el valor es divisible por el argumento
	public static function validateDivisibility(BaseControl $input, $arg): bool
	{
		return $input->getValue() % $arg === 0;
	}

	public static function validateEmailDomain(BaseControl $input, $domain)
	{
		// otros validadores
	}
}

El uso es después muy directo:

$form->addInteger('num')
	->addRule(
		[MyValidators::class, 'validateDivisibility'],
		'The value must be a multiple of %d',
		8,
	);

Las reglas de validación propias también se pueden añadir a JavaScript. La condición es que la regla sea un método estático. Su nombre para el validador de JavaScript se forma concatenando el nombre de la clase sin las barras invertidas \, un guion bajo _ y el nombre del método. Por ejemplo, App\MyValidators::validateDivisibility se escribe como AppMyValidators_validateDivisibility y se añade al objeto Nette.validators:

Nette.validators['AppMyValidators_validateDivisibility'] = (elem, args, val) => {
	return val % args === 0;
};

Evento onValidate

Tras enviar el formulario se realiza la validación, que comprueba las distintas reglas añadidas con addRule(), y a continuación se dispara el evento onValidate. Su manejador se puede usar para validaciones adicionales, normalmente para verificar la combinación correcta de valores en varios elementos del formulario.

Si se detecta un error, se le pasa al formulario con el método addError(). Se puede llamar sobre un elemento concreto o directamente sobre el formulario.

protected function createComponentSignInForm(): Form
{
	$form = new Form;
	// ...
	$form->onValidate[] = $this->validateSignInForm(...);
	return $form;
}

private function validateSignInForm(Form $form, \stdClass $data): void
{
	if ($data->foo > 1 && $data->bar > 5) {
		$form->addError('This combination is not possible.');
	}
}

Errores de procesamiento

En muchos casos descubrimos un error solo al procesar un formulario válido, por ejemplo al escribir una entrada nueva en la base de datos y toparnos con una clave duplicada. En ese caso devolvemos de nuevo el error al formulario con el método addError(). Se puede llamar sobre un elemento concreto o directamente sobre el formulario:

try {
	$data = $form->getValues();
	$this->user->login($data->username, $data->password);
	$this->redirect('Home:');

} catch (Nette\Security\AuthenticationException $e) {
	if ($e->getCode() === Nette\Security\Authenticator::InvalidCredential) {
		$form->addError('Invalid password.');
	}
}

Si es posible, recomendamos añadir el error directamente al elemento del formulario, porque con el renderizador predeterminado se mostrará junto a él.

$form['date']->addError('Sorry, this date is already taken.');

Puede llamar a addError() repetidamente para pasar varios mensajes de error a un formulario o a un elemento. Puede obtenerlos con getErrors().

Tenga en cuenta que $form->getErrors() devuelve un resumen de todos los mensajes de error, incluidos los que se pasaron directamente a los distintos elementos, no solo los pasados directamente al formulario. Los mensajes de error pasados solo al formulario se pueden obtener con $form->getOwnErrors().

Modificar los valores de entrada

Con el método addFilter() podemos modificar el valor introducido por el usuario. En este ejemplo toleraremos y eliminaremos los espacios del código postal:

$form->addText('zip', 'Postal Code:')
	->addFilter(function ($value) {
		return str_replace(' ', '', $value); // elimina los espacios del código postal
	})
	->addRule($form::Pattern, 'Postal code is not five digits', '\d{5}');

El filtro se integra entre las reglas de validación y las condiciones, así que el orden de los métodos importa: el filtro y la regla se llaman en el mismo orden en el que están escritos los métodos addFilter() y addRule().

Validación en JavaScript

El lenguaje para formular condiciones y reglas es muy potente. Todas las construcciones funcionan tanto en el lado del servidor como en el lado del cliente en JavaScript. Se transfieren en los atributos HTML data-nette-rules como JSON. De la validación en sí se ocupa un script que intercepta el evento submit del formulario, recorre los distintos elementos y realiza la validación correspondiente.

Ese script es netteForms.js y está disponible desde varias fuentes posibles:

Puede incrustar el script directamente en la página HTML desde una CDN:

<script src="https://unpkg.com/nette-forms@3"></script>

O copiarlo localmente a la carpeta pública de su proyecto (p. ej. desde vendor/nette/forms/src/assets/netteForms.min.js):

<script src="/path/to/netteForms.min.js"></script>

O instalarlo con npm:

npm install nette-forms

Y después cargarlo y ejecutarlo:

import netteForms from 'nette-forms';
netteForms.initOnLoad();

Alternativamente puede cargarlo directamente de la carpeta vendor:

import netteForms from '../path/to/vendor/nette/forms/src/assets/netteForms.js';
netteForms.initOnLoad();

Puede desactivar por completo la validación en el cliente añadiendo al formulario el atributo novalidate. El script netteForms.js se salta entonces su validación al enviarlo, así que la validación ocurre solo en el servidor:

$form->setHtmlAttribute('novalidate');

JavaScript dinámico

¿Quiere mostrar los campos de la dirección solo si el usuario elige que le envíen la mercancía por correo? Ningún problema. La clave está en la pareja de métodos addCondition() y toggle():

$form->addCheckbox('send_it')
	->addCondition($form::Equal, true)
		->toggle('#address-container');

Este código dice que, cuando se cumpla la condición (es decir, cuando el checkbox esté marcado), el elemento HTML #address-container será visible, y al revés. Así que colocamos los elementos del formulario con la dirección del destinatario en un contenedor con ese ID y se ocultarán o mostrarán al pulsar el checkbox. De eso se ocupa el script netteForms.js.

Como argumento del método toggle() se puede pasar cualquier selector. Por motivos históricos, una cadena que empiece por una letra, un dígito o un guion bajo y contenga solo letras, dígitos, guiones bajos, guiones, puntos y dos puntos se trata como el ID de un elemento, igual que si fuera precedida del carácter #. El segundo parámetro, opcional, permite invertir el comportamiento; por ejemplo, si usáramos toggle('#address-container', false), el elemento se mostraría solo si el checkbox no estuviera marcado.

La implementación predeterminada de JavaScript cambia la propiedad hidden de los elementos. Pero podemos cambiar el comportamiento con facilidad, por ejemplo añadiendo una animación. Basta con sobrescribir en JavaScript el método Nette.toggle con una solución propia:

Nette.toggle = (selector, visible, srcElement, event) => {
	document.querySelectorAll(selector).forEach((el) => {
		// oculta o muestra 'el' según el valor de 'visible'
	});
};

Desactivar la validación

A veces puede resultar útil desactivar la validación. Si pulsar un botón de envío no debe realizar la validación (adecuado para los botones CancelarVista previa), la desactivamos con el método $submit->setValidationScope([]). Si debe realizar solo una validación parcial, podemos indicar qué campos o contenedores del formulario hay que validar.

$form->addText('name')
	->setRequired();

$details = $form->addContainer('details');
$details->addInteger('age')
	->setRequired('age');
$details->addInteger('age2')
	->setRequired('age2');

$form->addSubmit('send1'); // Valida todo el formulario
$form->addSubmit('send2')
	->setValidationScope([]); // No valida nada
$form->addSubmit('send3')
	->setValidationScope([$form['name']]); // Valida solo el elemento 'name'
$form->addSubmit('send4')
	->setValidationScope([$form['details']['age']]); // Valida solo el elemento 'age'
$form->addSubmit('send5')
	->setValidationScope([$form['details']]); // Valida el contenedor 'details'

setValidationScope no afecta al Evento onValidate del formulario, que se llamará siempre. El evento onValidate de un contenedor solo se disparará si ese contenedor está marcado para la validación parcial.

La validación parcial afecta además a los valores que devuelve getValues(): el resultado contiene solo los valores de los elementos que caen dentro del alcance de la validación. Los valores de los elementos que quedan fuera de ese alcance se omiten.

versión: 4.x