Validación de formularios

Controles obligatorios

Los controles se marcan como obligatorios con el método setRequired(), cuyo argumento es el texto del mensaje de error que se mostrará si el usuario no lo rellena. Si no se da ningún argumento, se utiliza el mensaje de error por defecto.

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

Reglas

Añadimos reglas de validación a los controles con el método addRule(). El primer parámetro es la regla, el segundo es el 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 sólo se comprueban 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 aplicar estas reglas a todos los elementos:

constante descripción argumentos
Required alias de setRequired()
Filled Alias de setRequired()  
Blank no debe rellenarse
Equal valor es igual al parámetro mixed
NotEqual valor no es ser igual a parámetro mixed
IsIn valor es igual a algún elemento del array array
IsNotIn El valor no es igual a ningún elemento de la matriz. array  
Valid la entrada pasa la validación (para condiciones)

Entradas de texto

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

MinLength longitud mínima de cadena int
MaxLength longitud máxima de cadena int
Length longitud en el rango o longitud exacta par [int, int] o int
Email dirección de correo electrónico válida
URL URL válida
Pattern coincide con patrón regular string
PatternInsensitive como Pattern, pero sin distinguir mayúsculas de minúsculas string
Integer número entero
Numeric alias de Integer
Float número entero o de coma flotante
MIN mínimo del valor entero int|float
MAX máximo del valor entero int|float
Range valor en el rango par [int|float, int|float]

Las reglas Integer, Numeric a Float convierten automáticamente el valor a entero (o flotante respectivamente). Además, la regla URL también acepta una dirección sin esquema (por ejemplo, nette.org) y completa el esquema (https://nette.org). Las expresiones de Pattern y PatternInsensitive deben ser válidas para todo el valor, es decir, como si estuviera envuelto en los caracteres ^ and $.

Número de artículos

Para los elementos addMultiUpload(), addCheckboxList(), addMultiSelect() también puede utilizar 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 rango o número exacto pares [int, int] o int

Carga de archivos

Para los controles addUpload(), addMultiUpload() también se pueden utilizar las siguientes reglas:

MaxFileSize tamaño máximo del archivo en bytes int
MimeType tipo de MIME, acepta comodines ('video/*') string|string[]
Image el archivo cargado es JPEG, PNG, GIF, WebP
Pattern nombre de archivo coincide con expresión regular string
PatternInsensitive como Pattern, pero sin distinguir entre mayúsculas y minúsculas string

MimeType y Image requieren la extensión PHP fileinfo. Si un archivo o imagen es del tipo requerido se detecta por su firma. No se comprueba la integridad de todo el archivo. Puede averiguar si una imagen no está dañada, por ejemplo, intentando cargarla.

Mensajes de error

Todas las reglas predefinidas excepto Pattern y PatternInsensitive tienen un mensaje de error por defecto, por lo que pueden omitirse. Sin embargo, al pasar y formular todos los mensajes personalizados, hará que el formulario sea más fácil de usar.

Puede cambiar los mensajes por defecto en configuration, modificando los textos de la matriz Nette\Forms\Validator::$messages o utilizando el traductor.

Se pueden utilizar los siguientes comodines en el texto de los mensajes de error:

%d reemplaza gradualmente las reglas después de los argumentos
%n$d sustituye por el enésimo argumento de regla
%label sustituye por la etiqueta del campo (sin dos puntos)
%name sustituye por el nombre del campo (por ejemplo, name)
%value sustituye por 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 no más de %d', [5, 10]);

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

Condiciones

Además de las reglas de validación, se pueden establecer condiciones. Se establecen de forma muy parecida a las reglas, aunque utilizamos addRule() en lugar de addCondition() y, por supuesto, lo dejamos sin mensaje de error (la condición sólo 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 número
		->addRule($form::Pattern, 'Debe contener número', '.*[0-9].*');

La condición puede vincularse a un elemento distinto del actual utilizando addConditionOn(). El primer parámetro es una referencia al campo. En el siguiente caso, el correo electrónico sólo será necesario si la casilla de verificación está marcada (es decir, su valor es true):

$form->addCheckbox('boletines', 'enviarme boletines');

$form->addEmail('email', 'Email:')
	// si la casilla está marcada ...
	->addConditionOn($form['newsletters'], $form::Equal, true)
		// ... requiere email
		->setRequired('Introduzca su dirección de correo electrónico');

Las condiciones pueden agruparse en estructuras complejas con los métodos elseCondition() y endCondition().

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

En Nette, es muy fácil reaccionar al cumplimiento o no de una condición en la parte JavaScript utilizando el método toggle(), véase JavaScript dinámico.

Referencias entre Controles

El argumento de la regla o condición puede ser una referencia a otro elemento. Por ejemplo, puede validar dinámicamente que text tenga tantos caracteres como el valor del campo length:

$form->addInteger('length');
$form->addText('text')
	->addRule($form::Length, null, $form['length']);

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 fácil.

Puede pasar cualquier callback como primer parámetro a los métodos addRule() o addCondition(). El callback acepta el propio elemento como primer parámetro y devuelve un valor booleano que indica si la validación se ha realizado correctamente. Cuando se añade una regla utilizando addRule(), se pueden pasar argumentos adicionales, que se pasan como segundo parámetro.

De este modo, el conjunto personalizado de validadores puede crearse 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)
	{
		// validadores adicionales
	}
}

El uso es muy sencillo:

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

Las reglas de validación personalizadas también se pueden añadir a JavaScript. El único requisito 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 \, the underscore _, y el nombre del método. Por ejemplo, escriba App\MyValidators::validateDivisibility como AppMyValidators_validateDivisibility y añádalo al objeto Nette.validators:

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

Evento onValidate

Una vez enviado el formulario, la validación se realiza comprobando las reglas individuales añadidas por addRule() y llamando al evento onValidate. Su manejador puede ser usado para validación adicional, típicamente para verificar la correcta combinación de valores en múltiples elementos del formulario.

Si se detecta un error, se pasa al formulario utilizando el método addError(). Este puede ser llamado 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('This combination is not possible.');
	}
}

Tratamiento de errores

En muchos casos, descubrimos un error cuando estamos procesando un formulario válido, por ejemplo, cuando escribimos una nueva entrada en la base de datos y nos encontramos con una clave duplicada. En este caso, devolvemos el error al formulario utilizando el método addError(). Este método puede ejecutarse 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('Invalid password.');
	}
}

Si es posible, recomendamos añadir el error directamente al elemento del formulario, ya que aparecerá junto a él cuando se utilice el renderizador por defecto.

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

Puedes llamar repetidamente a addError() para pasar múltiples mensajes de error a un formulario o elemento. Se obtienen con getErrors().

Tenga en cuenta que $form->getErrors() devuelve un resumen de todos los mensajes de error, incluso los pasados directamente a elementos individuales, no sólo directamente al formulario. Los mensajes de error pasados sólo al formulario se recuperan mediante $form->getOwnErrors().

Modificación de los valores de entrada

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

$form->addText('zip', 'Postcode:')
	->addFilter(function ($value) {
		return str_replace(' ', '', $value); // remove spaces from the postcode
	})
	->addRule($form::Pattern, 'The postal code is not five digits', '\d{5}');

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

Validación JavaScript

El lenguaje de reglas y condiciones de validación es poderoso. Aunque todas las construcciones funcionan tanto del lado del servidor como del lado del cliente, en JavaScript. Las reglas se transfieren en atributos HTML data-nette-rules como JSON. La validación en sí es manejada por otro script, que engancha todos los eventos del formulario submit, itera sobre todas las entradas y ejecuta las validaciones respectivas.

Este script es netteForms.js, que está disponible en varias fuentes posibles:

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

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

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

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

O instalar a través de npm:

npm install nette-forms

Y luego cargar y ejecutar:

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

También 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 de dirección sólo si el usuario elige enviar la mercancía por correo? No hay problema. La clave es un 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 ese ID, y cuando se pulsa la casilla de verificación, se ocultan o se muestran. De esto se encarga el script netteForms.js.

Se puede pasar cualquier selector como argumento al método toggle(). Por razones históricas, una cadena alfanumérica sin otros caracteres especiales se trata como un ID de elemento, igual que si fuera precedida por el # character. The second optional parameter allows us to reverse the behavior, i.e. if we used toggle('#address-container', false), el elemento se mostraría sólo si la casilla de verificación estuviera desmarcada.

La implementación por defecto de JavaScript cambia la propiedad hidden para los elementos. Sin embargo, podemos cambiar fácilmente el comportamiento, por ejemplo añadiendo una animación. Basta con anular el método Nette.toggle en JavaScript con una solución personalizada:

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

Desactivación de la validación

En algunos casos, es necesario desactivar la validación. Si un botón de envío no se supone que ejecute la validación después del envío (por ejemplo CancelarPreview), puedes desactivar la validación llamando a $submit->setValidationScope([]). También puede validar el formulario parcialmente especificando los elementos a 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['nombre']]); // Valida sólo el campo 'nombre
$form->addSubmit('send4')
	->setValidationScope([$form['details']['age']]); // Valida sólo el campo 'edad
$form->addSubmit('send5')
	->setValidationScope([$form['detalles']]); // Valida el contenedor 'detalles

El evento onValidate en el formulario se invoca siempre y no se ve afectado por setValidationScope. El evento onValidate en el contenedor se invoca sólo cuando este contenedor está especificado para validación parcial.

versión: 4.0