Validación de formularios

Elementos obligatorios

Marcamos los elementos obligatorios con el método setRequired(), cuyo argumento es el texto del mensaje de error que se mostrará si el usuario no rellena el elemento. Si no se proporciona el argumento, se utilizará el mensaje de error predeterminado.

$form->addText('name', 'Nombre:')
	->setRequired('Por favor, introduzca su nombre');

Reglas

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

$form->addPassword('password', 'Contraseña:')
	->addRule($form::MinLength, 'La contraseña debe tener al menos %d caracteres', 8);

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

Nette viene con una serie de reglas predefinidas, cuyos nombres son constantes de la clase Nette\Forms\Form. Podemos usar estas reglas para todos los elementos:

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

Entradas de texto

Para los elementos addText(), addPassword(), addTextArea(), addEmail(), addInteger(), addFloat(), también se pueden usar algunas de las siguientes reglas:

MinLength longitud mínima del texto int
MaxLength longitud máxima del texto int
Length longitud en un rango o longitud exacta par [int, int] o int
Email dirección de correo electrónico válida
URL URL absoluta
Pattern coincide con la expresión regular string
PatternInsensitive como Pattern, pero insensible a mayúsculas/minúsculas string
Integer valor entero
Numeric alias para Integer
Float número
Min valor mínimo del elemento numérico int|float
Max valor máximo del elemento numérico int|float
Range valor en el rango par [int|float, int|float]

Las reglas de validación Integer, Numeric y Float convierten directamente el valor a entero o flotante, respectivamente. Además, la regla URL también acepta una dirección sin esquema (p. ej., nette.org) y añade el esquema (https://nette.org). La expresión en Pattern y PatternIcase debe aplicarse a todo el valor, es decir, como si estuviera envuelta en los caracteres ^ y $.

Número de elementos

Para los elementos addMultiUpload(), addCheckboxList(), addMultiSelect(), también se pueden usar las siguientes reglas para limitar el número de elementos seleccionados o archivos cargados:

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

Carga de archivos

Para los elementos addUpload(), addMultiUpload(), también se pueden usar 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, AVIF
Pattern el nombre del archivo coincide con la expresión regular string
PatternInsensitive como Pattern, pero insensible a mayúsculas/minúsculas string

MimeType e Image requieren la extensión PHP fileinfo. Detectan si el archivo o imagen es del tipo requerido basándose en su firma y no verifican la integridad de todo el archivo. Si una imagen está dañada se puede detectar, por ejemplo, intentando cargarla.

Mensajes de error

Todas las reglas predefinidas, excepto Pattern y PatternInsensitive, tienen un mensaje de error predeterminado, por lo que se puede omitir. Sin embargo, al proporcionar y formular todos los mensajes a medida, hará que el formulario sea más fácil de usar.

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

En el texto de los mensajes de error se pueden usar estas cadenas de marcador de posición:

%d reemplaza secuencialmente con los argumentos de la regla
%n$d reemplaza con el n-ésimo argumento de la regla
%label reemplaza con la etiqueta del elemento (sin dos puntos)
%name reemplaza con el nombre del elemento (p. ej. name)
%value reemplaza con el valor introducido por el usuario
$form->addText('name', 'Nombre:')
	->setRequired('Por favor, rellene %label');

$form->addInteger('id', 'ID:')
	->addRule($form::Range, 'al menos %d y como máximo %d', [5, 10]);

$form->addInteger('id', 'ID:')
	->addRule($form::Range, 'como máximo %2$d y al menos %1$d', [5, 10]);

Condiciones

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

$form->addPassword('password', 'Contraseña:')
	// si la contraseña no tiene más de 8 caracteres
	->addCondition($form::MaxLength, 8)
		// entonces debe contener un dígito
		->addRule($form::Pattern, 'Debe contener un dígito', '.*[0-9].*');

La condición también se puede vincular a un elemento diferente al actual usando addConditionOn(). Como primer parámetro, proporcionamos una referencia al elemento. En este ejemplo, el correo electrónico será obligatorio solo si se marca la casilla de verificación (su valor será true):

$form->addCheckbox('newsletters', 'Enviarme boletines');

$form->addEmail('email', 'E-mail:')
	// si la casilla de verificación está marcada
	->addConditionOn($form['newsletters'], $form::Equal, true)
		// entonces requiere el correo electrónico
		->setRequired('Introduzca una dirección de correo electrónico');

Se pueden crear estructuras complejas a partir de condiciones usando elseCondition() y endCondition():

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

En Nette, es muy fácil reaccionar al cumplimiento o incumplimiento de una condición también en el lado de JavaScript usando el método toggle(), consulte JavaScript dinámico.

Referencia a otro elemento

También se puede pasar otro elemento del formulario como argumento de una regla o condición. La regla entonces usará el valor introducido posteriormente por el usuario en el navegador. De esta manera, se puede validar dinámicamente, por ejemplo, que el elemento password contenga la misma cadena que el elemento password_confirm:

$form->addPassword('password', 'Contraseña');
$form->addPassword('password_confirm', 'Confirmar contraseña')
    ->addRule($form::Equal, 'Las contraseñas introducidas no coinciden', $form['password']);

Reglas y condiciones personalizadas

A veces nos encontramos en una situación en la que las reglas de validación incorporadas en Nette no son suficientes y necesitamos validar los datos del usuario a nuestra manera. ¡En Nette esto es muy simple!

Se puede pasar cualquier callback como primer parámetro a los métodos addRule() o addCondition(). Este recibe el propio elemento como primer parámetro y devuelve un valor booleano que indica si la validación se realizó correctamente. Al añadir una regla con addRule(), también es posible especificar argumentos adicionales, que luego se pasan como segundo parámetro.

Así podemos crear nuestro propio conjunto de validadores como una clase con métodos estáticos:

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

	public static function validateEmailDomain(Nette\Forms\Control $input, $domain)
	{
		// otros validadores
	}
}

El uso es entonces muy simple:

$form->addInteger('num')
	->addRule(
		[MyValidators::class, 'validateDivisibility'],
		'El valor debe ser un múltiplo de %d',
		8,
	);

Las reglas de validación personalizadas 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 JavaScript se crea concatenando el nombre de la clase sin 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

Después de enviar el formulario, se realiza la validación, donde se comprueban las reglas individuales añadidas mediante addRule() y luego se dispara el evento onValidate. Su handler se puede usar para validación adicional, típicamente para verificar la combinación correcta de valores en múltiples elementos del formulario.

Si se detecta un error, lo pasamos al formulario con el método addError(). Se puede llamar en un elemento específico o directamente en el formulario.

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

public function validateSignInForm(Form $form, \stdClass $data): void
{
	if ($data->foo > 1 && $data->bar > 5) {
		$form->addError('Esta combinación no es posible.');
	}
}

Errores durante el procesamiento

En muchos casos, nos enteramos del error solo cuando procesamos un formulario válido, por ejemplo, al escribir un nuevo elemento en la base de datos y encontrar una duplicidad de claves. En tal caso, nuevamente pasamos el error al formulario con el método addError(). Se puede llamar en un elemento específico o directamente en 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('Contraseña inválida.');
	}
}

Si es posible, recomendamos adjuntar el error directamente al elemento del formulario, ya que se mostrará junto a él al usar el renderizador predeterminado.

$form['date']->addError('Lo sentimos, pero esta fecha ya está ocupada.');

Puede llamar a addError() repetidamente para pasar múltiples mensajes de error al formulario o elemento. Los obtiene usando getErrors().

Tenga en cuenta que $form->getErrors() devuelve un resumen de todos los mensajes de error, incluidos los que se pasaron directamente a elementos individuales, no solo directamente al formulario. Los mensajes de error pasados solo al formulario se obtienen a través de $form->getOwnErrors().

Modificación de la entrada

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

$form->addText('zip', 'Código Postal:')
	->addFilter(function ($value) {
		return str_replace(' ', '', $value); // eliminamos los espacios del código postal
	})
	->addRule($form::Pattern, 'El código postal no tiene el formato de cinco dígitos', '\d{5}');

El filtro se integra entre las reglas y condiciones de validación, por lo que el orden de los métodos importa, es decir, el filtro y la regla se llaman en el mismo orden que los métodos addFilter() y addRule().

Validación 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 de JavaScript. Se transmiten en atributos HTML data-nette-rules como JSON. La validación en sí la realiza un script que captura el evento submit del formulario, recorre los elementos individuales y realiza la validación correspondiente.

Ese script es netteForms.js y está disponible desde múltiples fuentes posibles:

Puede insertar el script directamente en la página HTML desde un CDN:

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

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

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

O instalarlo a través de npm:

npm install nette-forms

Y luego cargarlo y ejecutarlo:

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

Alternativamente, puede cargarlo directamente desde la carpeta vendor:

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

JavaScript dinámico

¿Quiere mostrar los campos para introducir la dirección solo si el usuario elige enviar la mercancía por correo? No hay problema. La clave es el par de métodos addCondition() & toggle():

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

Este código dice que cuando se cumple la condición, es decir, cuando la casilla de verificación está marcada, el elemento HTML #address-container será visible. Y viceversa. Así, colocamos los elementos del formulario con la dirección del destinatario en un contenedor con este ID y al hacer clic en la casilla de verificación se ocultarán o mostrarán. Esto lo asegura el script netteForms.js.

Como argumento del método toggle(), se puede pasar cualquier selector. Por razones históricas, una cadena alfanumérica sin otros caracteres especiales se entiende como el ID del elemento, es decir, igual que si le precediera el carácter #. El segundo parámetro opcional permite invertir el comportamiento, es decir, si usáramos toggle('#address-container', false), el elemento se mostraría solo si la casilla de verificación no estuviera marcada.

La implementación predeterminada en JavaScript cambia la propiedad hidden de los elementos. Sin embargo, podemos cambiar fácilmente el comportamiento, por ejemplo, añadir una animación. Simplemente sobrescriba el método Nette.toggle en JavaScript con su propia solución:

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

Desactivación de la validación

A veces puede ser útil desactivar la validación. Si al presionar un botón de envío no se debe realizar la validación (adecuado para botones CancelarVista previa), la desactivamos con el método $submit->setValidationScope([]). Si debe realizar solo una validación parcial, podemos especificar qué campos o contenedores de formulario se deben 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 siempre será llamado. El evento onValidate de un contenedor solo se activará si ese contenedor está marcado para validación parcial.

versión: 4.0