Formularios utilizados de forma autónoma
Los Formularios Nette facilitan enormemente la creación y el procesamiento de formularios web. Puedes utilizarlos en tus aplicaciones completamente solos sin el resto del framework, lo que demostraremos en este capítulo.
Sin embargo, si usas Nette Application y presentadores, hay una guía para ti: formularios en presentadores.
Primer formulario
Intentaremos escribir un sencillo formulario de registro. Su código tendrá este aspecto (código completo):
use Nette\Forms\Form;
$form = new Form;
$form->addText('name', 'Nombre:');
$form->addPassword('password', 'Contraseña:');
$form->addSubmit('send', 'Regístrate');
Y vamos a renderizarlo:
$form->render();
y el resultado debería verse así:
El formulario es un objeto de la clase Nette\Forms\Form
(la clase Nette\Application\UI\Form
se
utiliza en los presentadores). Le añadimos los controles nombre, contraseña y botón de envío.
Ahora reactivaremos el formulario. Preguntando a $form->isSuccess()
, averiguaremos si el formulario fue enviado
y si fue rellenado válidamente. En caso afirmativo, volcaremos los datos. Tras la definición del formulario añadiremos:
if ($form->isSuccess()) {
echo 'El formulario se ha rellenado y enviado correctamente';
$data = $form->getValues();
// $data->name contains name
// $data->password contains password
var_dump($data);
}
El método getValues()
devuelve los datos enviados en forma de objeto ArrayHash. Más adelante
mostraremos cómo modificar esto. La variable $data
contiene las claves name
y password
con
los datos introducidos por el usuario.
Normalmente enviamos los datos directamente para su posterior procesamiento, que puede ser, por ejemplo, su inserción en la
base de datos. Sin embargo, puede producirse un error durante el procesamiento, por ejemplo, que el nombre de usuario ya esté
ocupado. En este caso, pasamos el error de vuelta al formulario usando addError()
y dejamos que se redibuje, con un
mensaje de error:
$form->addError('Lo sentimos, el nombre de usuario ya está en uso.');
Después de procesar el formulario, redirigiremos a la página siguiente. Esto evita que el formulario sea reenviado involuntariamente haciendo clic en el botón refresh, back, o moviendo el historial del navegador.
Por defecto, el formulario se envía usando el método POST a la misma página. Ambos pueden cambiarse:
$form->setAction('/submit.php');
$form->setMethod('GET');
Y eso es todo :-) Tenemos un formulario funcional y perfectamente asegurado.
Prueba a añadir más controles de formulario.
Acceso a los controles
El formulario y sus controles individuales se denominan componentes. Crean un árbol de componentes, cuya raíz es el formulario. Puede acceder a los controles individuales de la siguiente manera:
$input = $form->getComponent('name');
// sintaxis alternativa: $input = $form['nombre'];
$button = $form->getComponent('send');
// sintaxis alternativa: $button = $form['enviar'];
Los controles se eliminan utilizando unset:
unset($form['name']);
Reglas de validación
Aquí se usó la palabra valid, pero el formulario aún no tiene reglas de validación. Arreglémoslo.
El nombre será obligatorio, así que lo marcaremos 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 utilizará el mensaje de error
por defecto.
$form->addText('name', 'Nombre:')
->setRequired('Por favor, introduzca su nombre.');
Intente enviar el formulario sin el nombre rellenado y verá que se muestra un mensaje de error y el navegador o servidor lo rechazará hasta que lo rellene.
Al mismo tiempo, no podrá engañar al sistema escribiendo sólo espacios en la entrada, por ejemplo. De ninguna manera. Nette recorta automáticamente los espacios en blanco a izquierda y derecha. Pruébalo. Es algo que debería hacer siempre con cada entrada de una sola línea, pero a menudo se olvida. Nette lo hace automáticamente. (Puede intentar engañar a los formularios y enviar una cadena multilínea como nombre. Incluso en este caso, Nette no se dejará engañar y los saltos de línea cambiarán a espacios).
El formulario siempre se valida en el lado del servidor, pero también se genera validación JavaScript, que es rápida y el
usuario conoce el error inmediatamente, sin tener que enviar el formulario al servidor. De esto se encarga el script
netteForms.js
. Añádelo a la página:
<script src="https://unpkg.com/nette-forms@3"></script>
Si miras en el código fuente de la página con formulario, puedes notar que Nette inserta los campos requeridos en elementos
con una clase CSS required
. Pruebe a añadir el siguiente estilo a la plantilla, y la etiqueta “Nombre” será de
color rojo. Elegantemente, marcamos los campos obligatorios para los usuarios:
<style>
.required label { color: maroon }
</style>
Se añadirán reglas de validación adicionales mediante el método addRule()
. El primer parámetro es la regla,
el segundo es de nuevo el texto del mensaje de error, y el argumento opcional regla de validación puede seguir. ¿Qué
significa esto?
El formulario recibirá otra entrada opcional edad con la condición, que tiene que ser un número
(addInteger()
) y en ciertos límites ($form::Range
). Y aquí usaremos el tercer argumento de
addRule()
, el propio rango:
$form->addInteger('age', 'Edad:')
->addRule($form::Range, 'Debes ser mayor de 18 años y tener menos de 120.', [18, 120]);
Si el usuario no rellena el campo, las reglas de validación no se verificarán, porque el campo es opcional.
Obviamente hay espacio para una pequeña refactorización. En el mensaje de error y en el tercer parámetro, los números
aparecen por duplicado, lo que no es lo ideal. Si estuviéramos creando un formulario multilingüe y el mensaje que contiene los números
tuviera que traducirse a varios idiomas, dificultaría el cambio de valores. Por esta razón, se pueden utilizar los caracteres
sustitutos %d
:
->addRule($form::Range, 'You must be older %d years and be under %d.', [18, 120]);
Volvamos al campo contraseña, hagámoslo requerido, y verifiquemos la longitud mínima de la contraseña
($form::MinLength
), de nuevo usando los caracteres sustitutos en el mensaje:
$form->addPassword('password', 'Contraseña:')
->setRequired('Elige una contraseña')
->addRule($form::MinLength, 'Su contraseña debe tener una longitud mínima de %d', 8);
Añadiremos un campo passwordVerify
al formulario, donde el usuario introduzca de nuevo la contraseña, para su
comprobación. Usando reglas de validación, comprobamos si ambas contraseñas son iguales ($form::Equal
). Y como
argumento damos una referencia a la primera contraseña usando corchetes:
$form->addPassword('passwordVerify', 'Contraseña de nuevo:')
->setRequired('Rellene de nuevo su contraseña para comprobar si hay algún error tipográfico')
->addRule($form::Equal, 'Contraseña incorrecta', $form['password'])
->setOmitted();
Usando setOmitted()
, marcamos un elemento cuyo valor realmente no nos importa y que existe sólo para validación.
Su valor no se pasa a $data
.
Tenemos un formulario completamente funcional con validación en PHP y JavaScript. Las capacidades de validación de Nette son mucho más amplias, puedes crear condiciones, mostrar y ocultar partes de una página de acuerdo a ellas, etc. Puedes encontrarlo todo en el capítulo sobre validación de formularios.
Valores por defecto
A menudo establecemos valores por defecto para los controles de formulario:
$form->addEmail('email', 'Email')
->setDefaultValue($lastUsedEmail);
A menudo es útil establecer valores por defecto para todos los controles a la vez. Por ejemplo, cuando el formulario se utiliza para editar registros. Leemos el registro de la base de datos y lo establecemos como valores por defecto:
//$row = ['name' => 'John', 'age' => '33', /* ... */];
$form->setDefaults($row);
Llama a setDefaults()
después de definir los controles.
Representación del formulario
Por defecto, el formulario se muestra como una tabla. Los controles individuales siguen las pautas básicas de accesibilidad
web. Todas las etiquetas se generan como elementos <label>
y están asociadas a sus entradas; al hacer clic en
la etiqueta, el cursor se desplaza a la entrada.
Podemos establecer cualquier atributo HTML para cada elemento. Por ejemplo, añadir un marcador de posición:
$form->addInteger('age', 'Edad:')
->setHtmlAttribute('placeholder', 'Por favor, introduzca la edad');
Realmente hay muchas maneras de renderizar un formulario, así que es un capítulo dedicado al renderizado.
Mapeo a Clases
Volvamos al procesamiento de los datos del formulario. El método getValues()
devuelve los datos enviados como un
objeto ArrayHash
. Al tratarse de una clase genérica, algo así como stdClass
, careceremos de algunas
comodidades a la hora de trabajar con ella, como la compleción de código para propiedades en editores o el análisis estático
de código. Esto podría solucionarse teniendo una clase específica para cada formulario, cuyas propiedades representen los
controles individuales. Ej:
class RegistrationFormData
{
public string $name;
public int $age;
public string $password;
}
Alternativamente, puede utilizar el constructor:
class RegistrationFormData
{
public function __construct(
public string $name,
public int $age,
public string $password,
) {
}
}
Las propiedades de la clase de datos también pueden ser enums y se asignarán automáticamente.
¿Cómo decirle a Nette que nos devuelva datos como objetos de esta clase? Más fácil de lo que piensas. Todo lo que tienes que hacer es especificar el nombre de la clase u objeto a hidratar como parámetro:
$data = $form->getValues(RegistrationFormData::class);
$name = $data->name;
También se puede especificar un 'array'
como parámetro, y entonces los datos vuelven como un array.
Si los formularios consisten en una estructura de varios niveles compuesta por contenedores, cree una clase distinta para cada uno de ellos:
$form = new Form;
$person = $form->addContainer('person');
$person->addText('firstName');
/* ... */
class PersonFormData
{
public string $firstName;
public string $lastName;
}
class RegistrationFormData
{
public PersonFormData $person;
public int $age;
public string $password;
}
El mapeo entonces sabe por el tipo de propiedad $person
que debe mapear el contenedor a la clase
PersonFormData
. Si la propiedad contiene una matriz de contenedores, proporcione el tipo array
y pase la
clase que debe asignarse directamente al contenedor:
$person->setMappedType(PersonFormData::class);
Puede generar una propuesta para la clase de datos de un formulario utilizando el método
Nette\Forms\Blueprint::dataClass($form)
, que la imprimirá en la página del navegador. A continuación, puede
simplemente hacer clic para seleccionar y copiar el código en su proyecto.
Botones de envío múltiples
Si el formulario tiene más de un botón, normalmente necesitamos distinguir cuál ha sido pulsado. El método
isSubmittedBy()
del botón nos devuelve esta información:
$form->addSubmit('save', 'Save');
$form->addSubmit('delete', 'Delete');
if ($form->isSuccess()) {
if ($form['save']->isSubmittedBy()) {
// ...
}
if ($form['delete']->isSubmittedBy()) {
// ...
}
}
No omitas el $form->isSuccess()
para verificar la validez de los datos.
Cuando se envía un formulario con la tecla Intro, se trata como si se hubiera enviado con el primer botón.
Protección contra vulnerabilidades
Nette Framework pone un gran esfuerzo en ser seguro y dado que los formularios son la entrada más común del usuario, los formularios de Nette son tan buenos como impenetrables.
Además de proteger los formularios contra ataques de vulnerabilidades bien conocidas como Cross-Site Scripting (XSS) y Cross-Site Request Forgery (CSRF) hace un montón de pequeñas tareas de seguridad en las que ya no tienes que pensar.
Por ejemplo, filtra todos los caracteres de control de las entradas y comprueba la validez de la codificación UTF-8, para que los datos del formulario estén siempre limpios. En el caso de las casillas de selección y las listas de radio, verifica que los elementos seleccionados sean realmente de los ofrecidos y que no haya habido ninguna falsificación. Ya hemos mencionado que para la entrada de texto de una sola línea, elimina los caracteres de final de línea que un atacante podría enviar allí. Para entradas multilínea, normaliza los caracteres de final de línea. Y así sucesivamente.
Nette soluciona para usted vulnerabilidades de seguridad que la mayoría de los programadores no tienen ni idea de que existen.
El mencionado ataque CSRF consiste en que un atacante atrae a la víctima para que visite una página que ejecuta silenciosamente una petición en el navegador de la víctima al servidor donde la víctima está conectada en ese momento, y el servidor cree que la petición ha sido realizada por la víctima a voluntad. Por lo tanto, Nette impide que el formulario sea enviado vía POST desde otro dominio. Si por alguna razón desea desactivar la protección y permitir que el formulario sea enviado desde otro dominio, utilice:
$form->allowCrossOrigin(); // ¡ATENCIÓN! ¡Desactiva la protección!
Esta protección utiliza una cookie SameSite llamada _nss
. Por lo tanto, cree un formulario antes de enviar la
primera salida para que la cookie pueda ser enviada.
La protección de cookies SameSite puede no ser 100% fiable, por lo que es una buena idea activar la protección de token:
$form->addProtection();
Es muy recomendable aplicar esta protección a los formularios en una parte administrativa de su aplicación que cambie datos
sensibles. El framework protege contra un ataque CSRF generando y validando el token de autenticación que se almacena en una
sesión (el argumento es el mensaje de error que se muestra si el token ha caducado). Por eso es necesario tener una sesión
iniciada antes de mostrar el formulario. En la parte de administración del sitio web, la sesión suele estar ya iniciada, debido
al login del usuario. En caso contrario, inicie la sesión con el método Nette\Http\Session::start()
.
Así, tenemos una rápida introducción a los formularios en Nette. Intente buscar en el directorio de ejemplos en la distribución para más inspiración.