Formularios utilizados de forma independiente
Nette Forms facilita enormemente la creación y el procesamiento de formularios web. Puede usarlos en sus aplicaciones de forma completamente independiente del resto del framework, como mostraremos en este capítulo.
Pero si utiliza Nette Application y presenters, la guía para uso en presenters es para usted.
Primer formulario
Intentemos escribir un formulario de registro simple. Su código será el siguiente (código completo):
use Nette\Forms\Form;
$form = new Form;
$form->addText('name', 'Nombre:');
$form->addPassword('password', 'Contraseña:');
$form->addSubmit('send', 'Registrarse');
Lo renderizamos muy fácilmente:
$form->render();
y en el navegador se mostrará así:

El formulario es un objeto de la clase Nette\Forms\Form
(la clase Nette\Application\UI\Form
se usa en
presenters). Le hemos añadido los llamados elementos nombre, contraseña y un botón de envío.
Y ahora vamos a darle vida al formulario. Preguntando a $form->isSuccess()
averiguamos si el formulario fue
enviado y si se rellenó válidamente. Si es así, mostramos los datos. Detrás de la definición del formulario, añadimos:
if ($form->isSuccess()) {
echo 'El formulario se rellenó correctamente y se envió';
$data = $form->getValues();
// $data->name contiene el nombre
// $data->password contiene la contraseña
var_dump($data);
}
El método getValues()
devuelve los datos enviados en forma de objeto ArrayHash. Mostraremos cómo cambiar esto más adelante. El objeto $data
contiene las claves name
y
password
con los datos que el usuario rellenó.
Normalmente, enviamos los datos directamente para su posterior procesamiento, que puede ser, por ejemplo, insertarlos en la
base de datos. Sin embargo, durante el procesamiento puede ocurrir un error, por ejemplo, que el nombre de usuario ya esté
ocupado. En tal caso, devolvemos el error al formulario usando addError()
y dejamos que se renderice de nuevo, junto
con el mensaje de error.
$form->addError('Lo sentimos, este nombre de usuario ya está en uso.');
Después de procesar el formulario, redirigimos a la página siguiente. Esto evita el reenvío no deseado del formulario con el botón actualizar, atrás o moviéndose en el historial del navegador.
El formulario se envía por defecto mediante el método POST y a la misma página. Ambos se pueden cambiar:
$form->setAction('/submit.php');
$form->setMethod('GET');
Y eso es todo :-) Tenemos un formulario funcional y perfectamente seguro.
Intente añadir también otros elementos de formulario.
Acceso a los elementos
Llamamos componentes tanto al formulario como a sus elementos individuales. Forman un árbol de componentes, donde la raíz es precisamente el formulario. Podemos acceder a los elementos individuales del formulario de esta manera:
$input = $form->getComponent('name');
// sintaxis alternativa: $input = $form['name'];
$button = $form->getComponent('send');
// sintaxis alternativa: $button = $form['send'];
Los elementos se eliminan usando unset
:
unset($form['name']);
Reglas de validación
Se mencionó la palabra válido, pero el formulario aún no tiene reglas de validación. Vamos a corregirlo.
El nombre será obligatorio, por lo que lo marcamos con el método setRequired()
, cuyo argumento es el texto del
mensaje de error que se mostrará si el usuario no rellena el nombre. Si no se proporciona el argumento, se utilizará el mensaje
de error predeterminado.
$form->addText('name', 'Nombre:')
->setRequired('Por favor, introduzca su nombre');
Intente enviar el formulario sin rellenar el nombre y verá que se muestra un mensaje de error y el navegador o el servidor lo rechazarán hasta que rellene el campo.
Al mismo tiempo, no puede engañar al sistema escribiendo, por ejemplo, solo espacios en el campo. De ninguna manera. Nette elimina automáticamente los espacios iniciales y finales. Pruébelo usted mismo. Es algo que siempre debería hacer con cada input de una sola línea, pero a menudo se olvida. Nette lo hace automáticamente. (Puede intentar engañar al formulario y enviar una cadena de varias líneas como nombre. Incluso aquí, Nette no se deja engañar y convierte los saltos de línea en espacios).
El formulario siempre se valida en el lado del servidor, pero también se genera una validación JavaScript, que se ejecuta
instantáneamente y el usuario se entera del error de inmediato, sin necesidad de enviar el formulario al servidor. Esto lo
gestiona el script netteForms.js
. Insértelo en la página:
<script src="https://unpkg.com/nette-forms@3"></script>
Si mira el código fuente de la página con el formulario, puede notar que Nette inserta los elementos obligatorios en
elementos con la clase CSS required
. Intente añadir la siguiente hoja de estilos a la plantilla y la etiqueta
“Nombre” será roja. De esta manera, marcamos elegantemente los elementos obligatorios para los usuarios:
<style>
.required label { color: maroon }
</style>
Añadimos otras reglas de validación con el método addRule()
. El primer parámetro es la regla, el segundo es
nuevamente el texto del mensaje de error y puede seguir un argumento de la regla de validación. ¿Qué significa esto?
Ampliaremos el formulario con un nuevo campo opcional “edad”, que debe ser un número entero (addInteger()
) y
además estar en un rango permitido ($form::Range
). Y aquí es donde usaremos el tercer parámetro del método
addRule()
, con el que pasamos al validador el rango requerido como un par [desde, hasta]
:
$form->addInteger('age', 'Edad:')
->addRule($form::Range, 'La edad debe estar entre 18 y 120', [18, 120]);
Si el usuario no rellena el campo, las reglas de validación no se verificarán, ya que el elemento es opcional.
Aquí surge espacio para una pequeña refactorización. En el mensaje de error y en el tercer parámetro, los números se
indican de forma duplicada, lo cual no es ideal. Si estuviéramos creando formularios multilingües y el mensaje que contiene números se
tradujera a varios idiomas, dificultaría un posible cambio de valores. Por esta razón, es posible usar los marcadores de
posición %d
y Nette completará los valores:
->addRule($form::Range, 'La edad debe estar entre %d y %d años', [18, 120]);
Volvamos al elemento password
, que también haremos obligatorio y además verificaremos la longitud mínima de la
contraseña ($form::MinLength
), nuevamente usando un marcador de posición:
$form->addPassword('password', 'Contraseña:')
->setRequired('Elija una contraseña')
->addRule($form::MinLength, 'La contraseña debe tener al menos %d caracteres', 8);
Añadimos al formulario otro campo passwordVerify
, donde el usuario introduce la contraseña de nuevo, para
verificar. Usando reglas de validación, comprobamos si ambas contraseñas son iguales ($form::Equal
). Y como
parámetro, damos una referencia a la primera contraseña usando corchetes:
$form->addPassword('passwordVerify', 'Contraseña para verificar:')
->setRequired('Por favor, introduzca la contraseña de nuevo para verificarla')
->addRule($form::Equal, 'Las contraseñas no coinciden', $form['password'])
->setOmitted();
Con setOmitted()
hemos marcado un elemento cuyo valor en realidad no nos importa y que existe solo por motivos de
validación. El valor no se pasará a $data
.
Con esto, tenemos un formulario completamente funcional con validación en PHP y JavaScript. Las capacidades de validación de Nette son mucho más amplias, se pueden crear condiciones, mostrar y ocultar partes de la página según ellas, etc. Todo lo aprenderá en el capítulo sobre validación de formularios.
Valores por defecto
Normalmente establecemos valores por defecto para los elementos del formulario:
$form->addEmail('email', 'E-mail')
->setDefaultValue($lastUsedEmail);
A menudo es útil establecer valores por defecto para todos los elementos a la vez. Por ejemplo, cuando el formulario se utiliza para editar registros. Leemos el registro de la base de datos y establecemos los valores por defecto:
//$row = ['name' => 'John', 'age' => '33', /* ... */];
$form->setDefaults($row);
Llame a setDefaults()
después de definir los elementos.
Renderizado del formulario
Por defecto, el formulario se renderiza como una tabla. Los elementos individuales cumplen la regla básica de accesibilidad:
todas las etiquetas se escriben como <label>
y están vinculadas al elemento de formulario correspondiente. Al
hacer clic en la etiqueta, el cursor aparece automáticamente en el campo del formulario.
Podemos establecer atributos HTML arbitrarios para cada elemento. Por ejemplo, añadir un placeholder:
$form->addInteger('age', 'Edad:')
->setHtmlAttribute('placeholder', 'Por favor, introduzca la edad');
Hay realmente muchas formas de renderizar un formulario, por lo que se dedica a ello un capítulo separado sobre renderizado.
Mapeo a clases
Volvamos al procesamiento de los datos del formulario. El método getValues()
nos devolvía los datos enviados
como un objeto ArrayHash
. Dado que es una clase genérica, algo así como stdClass
, nos faltará cierta
comodidad al trabajar con ella, como el autocompletado de propiedades en los editores o el análisis estático de código. Esto
podría resolverse teniendo una clase específica para cada formulario, cuyas propiedades representen los elementos individuales.
Por ejemplo:
class RegistrationFormData
{
public string $name;
public ?int $age;
public string $password;
}
Alternativamente, puede utilizar un 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 mapearán automáticamente.
¿Cómo decirle a Nette que nos devuelva los datos como objetos de esta clase? Más fácil de lo que piensa. Simplemente indique el nombre de la clase o el objeto a hidratar como parámetro:
$data = $form->getValues(RegistrationFormData::class);
$name = $data->name;
También se puede indicar 'array'
como parámetro y entonces los datos se devolverán como un array.
Si los formularios forman una estructura multinivel compuesta por contenedores, cree una clase separada para cada uno:
$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, a partir del tipo de la propiedad $person
, sabe que debe mapear el contenedor a la clase
PersonFormData
. Si la propiedad contuviera un array de contenedores, indique el tipo array
y pase la
clase para el mapeo directamente al contenedor:
$person->setMappedType(PersonFormData::class);
Puede generar el diseño de la clase de datos del formulario usando el método
Nette\Forms\Blueprint::dataClass($form)
, que lo imprimirá en la página del navegador. Luego, simplemente seleccione
el código haciendo clic y cópielo en su proyecto.
Múltiples botones
Si el formulario tiene más de un botón, generalmente necesitamos distinguir cuál de ellos fue presionado. Esta información
nos la devuelve el método isSubmittedBy()
del botón:
$form->addSubmit('save', 'Guardar');
$form->addSubmit('delete', 'Eliminar');
if ($form->isSuccess()) {
if ($form['save']->isSubmittedBy()) {
// procesar guardar
}
if ($form['delete']->isSubmittedBy()) {
// procesar eliminar
}
}
No omita la consulta $form->isSuccess()
, verifica la validez de los datos.
Cuando el formulario se envía con la tecla Enter, se considera como si se hubiera enviado con el primer botón.
Protección contra vulnerabilidades
Nette Framework pone gran énfasis en la seguridad y, por lo tanto, se preocupa escrupulosamente por la buena seguridad de los formularios.
Además de proteger los formularios contra ataques Cross Site Scripting (XSS) y Cross-Site Request Forgery (CSRF), realiza muchas pequeñas medidas de seguridad en las que ya no tiene que pensar.
Por ejemplo, filtra todos los caracteres de control de las entradas y verifica la validez de la codificación UTF-8, por lo que los datos del formulario siempre estarán limpios. En los select boxes y radio lists, verifica que los elementos seleccionados fueran realmente de los ofrecidos y que no hubo suplantación. Ya mencionamos que en las entradas de texto de una sola línea elimina los caracteres de fin de línea que un atacante podría haber enviado. En las entradas de varias líneas, normaliza los caracteres de fin de línea. Y así sucesivamente.
Nette resuelve por usted los riesgos de seguridad que muchos programadores ni siquiera saben que existen.
El ataque CSRF mencionado consiste en que un atacante atrae a la víctima a una página que ejecuta discretamente una solicitud en el navegador de la víctima al servidor en el que la víctima está conectada, y el servidor cree que la solicitud fue realizada por la víctima por su propia voluntad. Por lo tanto, Nette evita el envío de formularios POST desde otro dominio. Si por alguna razón desea desactivar la protección y permitir el envío de formularios desde otro dominio, use:
$form->allowCrossOrigin(); // ¡CUIDADO! Desactiva la protección!
Esta protección utiliza una cookie SameSite llamada _nss
. Por lo tanto, cree el objeto de formulario antes de
enviar la primera salida, para que la cookie pueda ser enviada.
La protección mediante la cookie SameSite puede no ser 100% fiable, por lo que es recomendable activar también la protección mediante token:
$form->addProtection();
Recomendamos proteger de esta manera los formularios en la parte de administración del sitio web que modifican datos sensibles
en la aplicación. El framework se defiende contra el ataque CSRF generando y verificando un token de autorización que se
almacena en la sesión. Por lo tanto, es necesario tener la sesión abierta antes de mostrar el formulario. En la parte de
administración del sitio web, la sesión generalmente ya está iniciada debido al inicio de sesión del usuario. De lo contrario,
inicie la sesión con el método Nette\Http\Session::start()
.
Bien, hemos cubierto una introducción rápida a los formularios en Nette. Intente también mirar el directorio examples en la distribución, donde encontrará más inspiración.