Form API (I): Comprender, crear y modificar formularios en Drupal 8

@davidjguru
Drupal & /me
Published in
15 min readApr 9, 2018

Introducción a formularios en proyectos basados en Drupal 8.

Photo by Alex Read on Unsplash

El otro día mantenía una conversación con un colega en la que nos planteábamos (previamente habíamos despellejado a un tercero no-presente por su escasa capacidad didáctica) como lo haríamos para enseñar Drupal, o mejor dicho, que vector de entrada usaríamos para llegar a cosas chulas y tal vez más avanzadas dentro de Drupal. Entre churrete y churrete tampoco es que saliese algo que fuese demasiado útil, pero ya en casa pensando en algunas modificaciones que he tenido que hacer recientemente y repasando las notas majaras que suelo ir dejando por ahí (siempre con el modo verbose activado) se me ocurrió que podría casarlo todo en una propuesta didáctica y progresiva. Como normalmente escribo una novela en el corpus de cada ticket, lo tengo fácil para seguir mis propios pasos y recomponer experiencias. Así nace este artículo algo desastrao sobre usar la Form API de Drupal para conocer mejor su funcionamiento interno.

Que ustedes lo disfruten.

Índice: en este artículo
1- Introducción: ¿Esto cómo va?
1.1- La premisa: Form API como vector de entrada
1.2- Métodos elementales para operar con formularios
2- Rebobinamos: Repaso (breve, muy breve) de formularios en Drupal
3- Ahora sí, vamos al lío: operando con formularios en Drupal
3.1- Ejemplo existente de formulario en Drupal
3.2- Creando tu propio formulario en Drupal 8
3.3- Alterando formularios ya existentes
4- Recomendaciones y lecturas aconsejadas
4.1- Módulos Drupal para formularios
4.2- Drupal Day Workshop 2017 de Álvaro J Hurtado
4.3- Módulo Examples
Este artículo continúa en:
Form API(II): Modificando formularios en Drupal8 mediante form_alter
Otros artículos de la serie:
Form API(II): Modificando formularios en Drupal8 mediante form_alter
Form API (III): Ejemplo de modificación de formulario en Drupal 8
Otros artículos que podrían interesarte:
Drupal Fast Tips (I) - Using links in Drupal 8
Docker, Docker-Compose and DDEV - Cheatsheet
OpenEuropa - The European Commission in Drupal 8
Development environments for Drupal with DDEV

1- Introducción: ¿Esto cómo va?

La Form API de Drupal, es decir, el conjunto de clases, interfaces, métodos y recursos disponibles para operar sobre formularios en el contexto de Drupal, puede ser uno de los puntos de entrada más interesantes en los entresijos de la plataforma.

1.1- La premisa: Form API como vector de entrada

¿En que me baso para establecer esta premisa?

Bueno, podemos establecer el siguiente argumentario:

  • Es una de las APIS más “simples” y manejables dentro de Drupal.
  • En si mismo, el workflow de un formulario en Drupal es uno de los procesos más sencillos, intuitivos y manejables que existen: un formulario se construye (build), se revisan sus valores (validate) y por último se envía (submit). Sobre estos tres pilares se articulan casi todas las opciones disponibles.
  • Es un buen vector de entrada para practicar de manera complementaria con la creación de módulos custom en Drupal 8. Muy asimilable y progresivo.
  • De alguna manera, son un puente más que interesante entre “lo viejo” y “lo nuevo”: nos permite seguir prácticando con viejos hooks que aún sobreviven en un contexto actualizado de rutas, componentes y MVC heredado de Symfony.

1.2- Métodos elementales para operar con formularios

Como decíamos en uno de los puntos anteriores, las operaciones elementales de un formulario son pocas, como podemos ver en la lista de métodos disponibles en la FormInterface de Drupal+:

Métodos acordados en la FormInterface de Drupal

Supongo que por tener un contrato pequeñín con un número tan reducido de métodos, esta Interface es implementada por siete millones de clases o así+

Y como podemos ver en el listado a través del operador de ámbito usado (el “::”, llamado Paamayim Nekudotayim), son métodos estáticos de la interfaz (no requieren de la instanciación de una clase a través de un objeto para ser llamados). Ok. Descendamos un nivel más para observar más detenidamente estos métodos proporcionados por Drupal:

public function buildForm($form, $form_state);
public function getFormId();
public function validateForm(&$form, $form_state);
public function submitForm(&$form, $form_state);

Aquí los tenemos: cuatro métodos (tres para funcionar y otro extra para obtener el nombre del formulario), con pocos parámetros y nombres muy intuitivos. Ideales para sumergirse en el funcionamiento de Drupal. Pero antes de avanzar, retrocedamos un poco. Apenas un pasito.

2- Rebobinamos: Repaso (breve, muy breve) de los formularios en Drupal

Antes de empezar a manipular formularios, conviene conocer algunos conceptos esenciales sobre como operan los formularios en el contexto de Drupal.

  1. En primer lugar, podemos asumir que un formulario en Drupal es una especie de array multidimensional (un array que puede contener otros arrays y estos otros más), normalmente nombrado como $form -ahí está la mandanga-. Se supone que para facilitar el manejo y la reutilización y esto y lo otro, pero en definitiva al tratar con formularios estamos tratando con arrays que luego se “pintarán” en pantalla para el usuario final a través de procesos de renderizado.
  2. En segundo lugar, es importante conocer que lo relacionado con el renderizado de formularios es una herencia del concepto de “Render Elements” que son tratados en “Render Arrays”. Estos arrays son contenedores de info que será pintada en pantalla y tienen una serie de normas, jerarquías y propiedades para ser manejados. Los arrays de formularios son un sub-tipo de esta categoría de recursos “pintables” en pantalla. Aquí puedes consultar+ el listado de propiedades internas que puedes registrar en los elementos de formulario de un array de este tipo.
  3. En tercer lugar, existen varios tipos “fundamentales” de formularios en Drupal y cada cual responde en cierta manera a una clase específica:
  • Formularios genéricos, que normalmente extienden la clase abstracta FormBase+.
  • Formularios de configuración para configurar un módulo, que extienden la clase abstracta ConfigFormBase+.
  • Formularios con pasos intermedios de confirmación -¿Seguro que quiere realizar la siguiente acción?-, que extienden la clase abstracta ConfirmFormBase+.

3- Ahora sí, vamos al lío: operando con formularios en Drupal

Para entrar en harina, lo mejor que se me ha ocurrido es la de tomar un poco de CRUD pero a mi manera, preguntándome ¿Qué buscaría alguien que empieza con esto? bueno, tal vez para empezar un ejemplo para ver como va. Luego algo de información para crear sus propios formularios y por último cuando ya es un Maikel Nai creando sus movidas, pues info sobre como modificar los formularios de otra persona. WHAT ELSE? Venga, vamos, arremangarze.

3. 1- Ejemplo existente de formulario en Drupal

Muy bien, como la interface (siempre me ha rayado tratar esta palabra en castellano desde los tiempos de la facultad). En fin, eso: como la FormInterface está implementada por muchas clases, elegir una para extraer un ejemplo manejable es relativamente sencillo (revisar hasta encontrar una que mole y tal) pero yo ya tengo mi preferida, una clase que siempre me ha parecido muy bonica y muy sencilla e intuitiva a la vez: la clase BanAdmin del módulo Ban+.

Plano de situación: la clase BanAdmin es el eje funcional del módulo Ban disponible en el core de Drupal 8 (pero desactivado por defecto en el perfil estándar) y cuyo funcionamiento esencial es el de bloquear el acceso a la plataforma de direcciones IP determinadas. Una funcionalidad simple en un módulo muy intuitivo manejado a través de dos formularios…¿Recordáis un párrafo anterior donde anotaba que normalmente los formularios tienen tres modelos básicos? bien este módulo tiene dos de ellos, localizables en la ruta:

/tu_drupal/core/modules/ban/

BanAdmin que como vemos extiende FormBase (para construir formularios genéricos).

BanDelete que extiende a ConfirmFormBase, por lo que podemos entender de manera intuitiva que se trata de un formulario para confirmar una actividad (en este caso unban).

Como decía, es un módulo para registrar direcciones IP a las que bloquear el acceso y que al abrirlo con nuestro editor de texto phavorito veremos su chicha, donde más allá del constructor de la clase y el método central de construcción del formulario (buildForm) con sus particularidades, podemos ver el método getFormId() que devuelve la cadena de texto del ID:

public function getFormId() {
return 'ban_ip_form';
}

O como realiza la validación del formulario, repasando el valor introducido y asegurándose de cargar un error (setErrorByName) en $form_state en los casos:

  • Que la dirección IP ya esté baneada
  • Que la IP sea tu propia IP y no puedes banearte a ti mismo
  • Que el formato no sea el de una dirección IP
public function validateForm(array &$form, FormStateInterface $form_state) {    $ip = trim($form_state->getValue('ip'));    if ($this->ipManager->isBanned($ip)) {
$form_state->setErrorByName('ip', $this->t('This IP address is already banned.'));
}
elseif ($ip == $this->getRequest()->getClientIP()) {
$form_state->setErrorByName('ip', $this->t('You may not ban your own IP address.'));
}
elseif (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_RES_RANGE) == FALSE) {
$form_state->setErrorByName('ip', $this->t('Enter a valid IP address.'));
}

}

Y la gestión que realiza del envío del formulario, cargando la IP recibida en el ipManager tras sacarlo del $form_state, chutando luego un drupal message a pantalla y cargando una redirección del formulario en $form_state tras el envío:

public function submitForm(array &$form, FormStateInterface $form_state) {    $ip = trim($form_state->getValue('ip'));    $this->ipManager->banIp($ip);    drupal_set_message($this->t('The IP address %ip has been banned.', ['%ip' => $ip]));    $form_state->setRedirect('ban.admin_page');  }

Así que como decíamos al principio, aquí tenemos en acción a los cuatro métodos fundamentales de cualquier formulario:

public function buildForm($form, $form_state) // Construye el form
public function getFormId() // Devuelve el identificador del form
public function validateForm(&$form, $form_state) // Valida el form
public function submitForm(&$form, $form_state) // Envía el form

Ok, ¿Y eso del $form_state que está tan presente? bien estamos ANTE LA PIEDRA ROSETTA DE LOS FORMULARIOS EL SANCTASANCTÓRUM DE LA FORMAPI (al tío se le van las metáforas de las manos). Tranqui, veamos:

FormStateInterface $form_state

Conceptualmente hablando, $form_state es un objeto que se encarga de registrar los distintos valores asociados a un formulario en un momento determinado (normalmente el momento en el que se consulta su contenido) y corresponde al tipo FormStateInterface o de alguna de las clases que implementan la Interface. En versiones anteriores era un array multidimensional al que se le pedían y se le cargaban cosas, como a un array manejado por claves (keys) y ahora mediante llamadas a métodos específicos:

// Forma anterior (útil por si nos movemos a un Drupal 7)$getresponse = $form_state['response'];
$form_state['response'] = $setresponse;
// Forma actual en Drupal 8 mediante llamadas a métodos$response = $form_state->getResponse();
$form_state->setResponse($response);

El objeto form_state se encarga de proporcionarnos métodos para la extracción, inserción y diversas comprobaciones de los valores de los campos de los formularios para realizarles consultas en el momento en el que lo necesitemos. Mejor conocer sus métodos disponibles aquí+ pero si me preguntas, yo nombraría algunas funciones elementales para el tratamiento de datos ya sea en fase de validación en fase de envío (submit):

$form_state->getValue($key) 
// Extrae el valor (o array) de un campo específico del form.
$form_state->getValues()
// Devuelve todos los valores asociados al form.
$form_state->setValue($key, $value)
// Carga un valor específico en un campo del form.
$form_state->isValueEmpty($key)
// Útil para saber si un campo del form está vacío en validación.

$form_state->setError($element, $message)
// Carga mensaje de error en relación a un elemento del form.

3. 2- Creando tu propio formulario en Drupal 8

Llevamos un buen rato comentando amistosamente diferentes aspectos y si has llegado hasta aquí (además de un premio a la resiliencia) nos merecemos un respiro para jugar a construir cosas. Vamos a intentar remover todo lo que hemos ido anotando previamente y a licuarlo en una construcción para darle a este infumable artículo un enfoque teorico-práctico. Avanti con la guaracha.

¿Qué decisiones debemos tomar?

  • Un tipo de formulario: ¿Qué modelo de formulario de los que conocemos usaremos como base de partida? vamos a crear un formulario básico para recoger algunos datos, para lo cual extenderemos la clase FormBase.
  • Unos campos para registrar datos: ¿Qué formatos requieren los datos que queremos registrar? Crearemos un formulario estándar con tres campos de referencia.
  • Una validación de los datos: ¿Cómo declararemos válidos y listos para envío los datos recopilados desde el formulario? Repasaremos longitud y valores de los campos, en concreto que uno de los campos con valor numérico esté por encima de un valor determinado.
  • Un almacenamiento de los datos: ¿Qué modo de almacenamiento queremos para esos datos recogidos desde el formulario? Usaremos la opción clásica de guardarlos en una tabla creada ad-hoc en la base de datos.
  • Un acceso al formulario: ¿Cómo se llegará hasta el formulario? Crearemos una ruta de acceso al formulario.

Para articularlo todo, vamos a crear un módulo para Drupal que contenga las acciones, rutas y definiciones necesarias para nuestro formulario. Elegiremos un formulario sencillo de apenas tres campos: Nombre (campo “name” de tipo texto), Apellido (campo “surname”, tipo texto) y Edad (campo “age” de tipo numérico). Añadiremos una regla de validación (comprobaremos que la edad introducida sea mayor que 18 años) y nos aseguraremos que almacenaremos los datos, creando una nueva tabla en nuestra base de datos asociada al formulario, crearemos el primer registro de datos a modo de prueba durante la instalación del módulo desde el fichero .install y en cada envío del formulario (submit) ejecutaremos una inserción en la base de datos.

Nombre del módulo: formexample

Ruta: tudrupal/modules/custom/formexample/

Fichero formexample.info.yml del módulo Drupal:

Fichero formexample.install para crear una tabla asociada al formulario en la base de datos de Drupal:

Fichero formexample.routing.yml para la definición de rutas de acceso al formulario:

Fichero FormExample.php que contiene la definición del formulario con validate, submit, build y getId:

Estructura de directorios y ficheros resultante en /modules/custom/formexample:

Y si todo ha ido bien, al instalar el nuevo módulo en nuestro Drupal, se habrá creado la nueva tabla en base de datos y un nuevo registro inicial en dicha tabla (en mi caso para este site de pruebas uso un fichero SQLite como base de datos):

Y accediendo a la dirección URL que hemos configurado en el fichero de routing(formexample/myform), tendremos disponible el formulario de ejemplo que hemos creado, donde nos dará un error si intentamos introducir una edad < 18:

Repasando & Resumiendo, en realidad solo hemos vuelto a trabajar con la misma mecánica desde otro punto de vista: build, validate, submit, construir un módulo Drupal 8 en torno al nuevo formulario y asegurarnos que los valores que registramos quedan almacenados.

Obviamente las posibilidades son muchas debido a la multitud de elementos que pueden usarse, los vínculos que pueden declararse y las propiedades que podemos asignarles a través de la estructura de arrays de Drupal. Pero aunque pueda parecer liviano, en cierta manera ya hemos practicado con los elementos fundamentales de la FormAPI en Drupal, a saber:

  1. Conocemos los tipos de formularios que podemos usar.
  2. Sabemos cuales son las clases PHP básicas para crear formularios.
  3. Comprendemos la estructura de arrays multidimensionales para renderizar en Drupal.
  4. Experimentamos la construcción de un módulo custom en Drupal 8.
  5. Practicamos con la Database API de Drupal 8 para realizar conexiones e inserciones.
  6. Probamos la gestión de un formulario mediante $form_state a través de métodos getters y setters.

3. 3- Alterando formularios ya existentes

Nos queda una tercera parte por cubrir en este artículo (palabra que será casi la última), en realidad una igual de importante que la anterior, ya que suele ser muy habitual en el día a día que tengamos que realizar modificaciones sobre formularios ya existentes en en un proyecto.

De cara a la segunda orientación de la FormAPI (la de realizar modificaciones sobre formularios ya existentes), podríamos subrayar varios aspectos interesantes. Pero para para empezar, vamos a centrarnos en el uso del hook_form_alter(), una función de tipo “Hook” es un tipo de método muy usado para relacionar el core de Drupal con nuevas funcionalidades y nuevos módulos, y que aunque en franca retirada, todavía quedan algunos que no se han replegado…clásicos como el hook_help, para mostrar información de ayuda sobre un módulo o el propio hook_form_alter, para realizar modificaciones sobre formularios. Bien ¿Cómo funciona? aunque no entra en el alcance de este artículo, consensuemos algo así como que el core de Drupal se da un largo paseo por todos los módulos y va pillando y sumando cualquier función que tenga formato de hook en su nombre y se la queda y sabe que debe ejecutarla cuando toque. De momento nos conformamos con eso.

¿Qué supone esto? pues que podemos agrupar acciones a acometer dentro del corpus de un mismo hook de manera centralizada. Imaginemos que queremos realizar modificaciones en varios formularios: vamos a unificar las acciones en un único proceso de alteraciones al que daremos (one more time) la cobertura estructural de módulo de Drupal. Crearemos nuestro nuevo módulo…emmm… “Alterforms”, sí eso (locos por el branding).

¿Qué necesitamos?

  • Identificar el formulario: Uno de los primeros pasos para operar sobre un formulario en Drupal suele ser encontrar su identificador interno para saber como se llama y poder operar sobre él.

Usaremos la función Hook principal form_alter que en su forma original ya opera con el core de Drupal manejando el id de cada formulario:

function nombredetumodulo_form_alter(&$form, $form_state, $form_id)

Por lo que solo tendremos que sacar esa variable $form_id en pantalla a través de una función muy socorrida y ya usada en el módulo anterior:

drupal_set_message("Form ID is : " . $form_id);

Con estas líneas ya tendremos el identificador de cada formulario en pantalla:

  • Depurar el formulario: Una vez localizado, desde el hook_form_alter necesitamos localizar visualmente como se organiza a nivel interno y conocer sus valores y meta-información, así que teniendo en cuenta que en Drupal un formulario es algo así como un array renderizado…necesitamos debuguear ese array.

Seleccionamos el formulario a modificar mediante una estructura switch que irá segmentando cada caso de $form_id hasta intervenir en el que necesitamos:

switch ($form_id){     
case "your_string_form_id":
// Acciones a acometer en el formulariobreak;
}

Depurando formularios: Existen varios métodos que podemos usar siempre que a la hora de definir nuestro módulo para alterar formularios incluyamos como dependencias los recursos necesarios. Es el caso de kint() una función para depurar que muestra en pantalla _toda_ la información y que requiere declarar como dependencia la suite de módulos Devel+ de Drupal.

Podemos llamar a la función directamente dentro del case de nuestro form en el switch anterior de la siguiente manera:

kint($form);

Pero dado que es enorme la cantidad de información que kint() vuelca en pantalla, recomiendo añadir como dependencia el módulo Search Kint+ para habilitar una caja de búsqueda sobre toda la información y poder localizar elementos de manera más ágil:

Vista de la información mostrada por kint( ) con Search Kint integrada

Otra opción podría ser realizar un volcado directo de la variable-array $form desde var_dump y volcarla en un mensaje de sistema Drupal:

drupal_set_message(var_dump($form));

Pero lo cierto que suele ser la opción con formato de salida más majareta y caótica como para localizar información. Veamos una última opción.

La opción más manejable:

dpm($form, 'Formato íntegro del formulario: ');

La función dpm() procesa valores y los incrusta dentro del sistema de mensajes de Drupal a partir de su estructura interna:

function dpm($input, $name = NULL, $type = 'status') {
\Drupal::service('devel.dumper')
->message($input, $name, $type);
return $input;
}
Volcado de la variable $form a partir de la función dpm( )
  • Realizar sustituciones/cambios: Un grupo de tareas muy habitual es realizar cambios de tipos de campo en formularios sencillo, reestructurar secciones y bloques de campos, alterar la organización visual…tenemos que hacer cambios con la información recabada en los dos puntos anteriores.

Pero se nos hace un poco largo ya el artículo (creo que van unas tres mil quinientas palabras), así que os emplazo para el siguiente post de la serie, FormAPI (II): Modificando formularios mediante form_alter, donde construiremos un módulo central de manera completa para realizar cambios, ejecutaremos modificaciones y analizaremos algunas cuestiones útiles a tener en cuenta.

Si queréis ampliar un poco más la información, referencias o practicar un poco con estas cuestiones iniciales de la FormAPI de Drupal, a continuación os dejo el último apartado de este artículo con algunas recomendaciones y lecturas que pueden ser de vuestro interés.

¡Salud!

4- Recomendaciones y lecturas aconsejadas

Algunas referencias que pueden ser útiles:

4. 1- Módulos Drupal para formularios

A lo largo y ancho de Internet hay algunos estudios comparativos sobre módulos Drupal orientados a la construcción y modificación de formularios.

4. 2- Drupal Day Workshop 2017 de Alvaro J Hurtado

Para el Drupal Day Spain de 2017 (noviembre’17 en Cáceres), Álvaro Hurtado+ impartió un taller muy interesante de creación de módulos en Drupal. Los ejercicios eran progresivos e incluían todos los aspectos básicos del día a día con Drupal: routing, fields, widgets, formatters y por supuesto, formularios. En el ejercicio 4 ya hace aparición un pequeño formulario de configuración.

El código de la sesión está disponible en el github de Álvaro+ y solo es necesario descargarlo/clonarlo/instalarlo en un Drupal local para testearlo y jugar con él. Muy recomendable.

4. 3- Módulo Examples

El set de módulos de ejemplos de Drupal es uno de los más interesantes para aprender casi cualquier cosa que ocurra dentro de Drupal. Con unos treinta módulos incluidos con ejemplos de muchos tipos, incorpora también un módulo específico para ejemplos de uso de FormAPI.

En concreto, el módulo Form API Example contiene un set muy completo de casos de uso, elementos y posibilidades para construir formularios.

--

--

@davidjguru
Drupal & /me

I’m @davidjguru this is my off-broadway channel, speaking about Drupal like in https://www.therussianlullaby.com or https://davidjguru.github.io but in spanish.