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 Cancelar o Vista 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.