Acceso a base datos en Drupal 7. Parte 2

Continuamos con el ejemplo de acceso a base de datos en Drupal 7 que comenzamos hace unos días. Puedes consultar la primera parte del artículo para refrescar o si simplemente te la perdiste.

En esta segunda parte vamos a ver cómo:

  • Crear un formulario para que el usuario pueda crear un nuevo registro en nuestra tabla de issues.
  • Usar el mismo formulario para modificar los datos de una incidencia.
  • Eliminar el registro de una incidencia de la tabla.

 

Definir Hook menus para las acciones

Lo primero que vamos a hacer es ampliar nuestra implementación de hook_menu() para declarar las tres URLs nuevas que necesitamos:

  • Una URL para modificar los datos de una incidencia: /issues/edit/[issue_id]
  • Otra para eliminar incidencias de la tabla: /issues/delete/[issue_id] 
  • Por último, para dar de alta incidencias desde un formulario de Drupal:  /issues/create

Atención, porque las dos primeras van a necesitar un parámetro que será el identificador (issue_id) de la incidencia que queremos modificar o eliminar, y que debemos especificar en el hook_menu(), que quedaría así (ojo a las partes en negrita):

/**
 * Implements hook_menu().
 *
 * Defines URLS for the list, create, edit and delete actions.
 */
function issue_control_menu() {
  $items = array();

  $items['issues/list'] = array(
    'title' => 'Issues List',
    'description' => 'Issues List from the database.',
    'page callback' => 'issue_control_list',
    'page arguments' => array(),
    'access callback' => TRUE,
  );

  $items['issues/create'] = array(
    'title' => 'Issues Form',
    'description' => 'A form to write rows into issues table.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('issue_control_form'),
    'access callback' => TRUE,
  );

  $items['issues/edit/%'] = array(
    'title' => 'Issues Form',
    'description' => 'A form to write rows into issues table.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('issue_control_form', 2),
    'access callback' => TRUE,
  );

  $items['issues/delete/%'] = array(
    'title' => 'Delete issue',
    'description' => 'Deletes an issue.',
    'page callback' => 'issue_control_perform_delete',
    'page arguments' => array(2),
    'access callback' => TRUE,
  );

  return $items;
}

Las definiciones de las URLs de modificar y eliminar llevan un símbolo % que actua de comodín, y se pasa después como parámetro a la función que definimos en 'page callback', mediante la siguiente línea de 'page arguments'. En nuestro caso estamos pasando el argumento 2, ya que el 0 sería 'issues', el 1 sería 'delete' o 'edit', y el 2 será el identificador de la incidencia que ya estamos incluyendo en la URL cuando creamos nuestra lista.

De esta forma, cuando llamamos a la url http://nuestro.drupal/issue/edit/1, el último 1 (que es el issue_id) se recibirá en nuestra función como un parámetro.

Además, en el caso de las llamadas de modificar y crear, la función que ponemos en el 'page callback' no es directamente una función declarada por nosotros, sino una llamada a drupal_get_form(), a la que pasamos el nombre de nuestra función que pintará realmente el formulario. 

 

Acceso a datos: Las funciones básicas

Antes de empezar a desarrollar el formulario para editar y crear incidencias, vamos a empezar por la parte básica primero: las funciones de acceso a datos. Es lógico que implementemos estas funciones esenciales, ya que lo normal en aplicaciones grandes es que tengamos varias llamadas, por ejemplo, para cargar una incidencia en varias partes del código, por lo podremos reutilizalas, y favorecemos la encapsulación, el principio de responsabilidad simple, etc. 

Estas son las funciones que vamos a necesitar:

Acción Función Descripción
Cargar issue_control_load_issue() Devuelve una incidencia.
Nueva issue_control_add() Crea una indicencia en la tabla.
Modificar issue_control_edit() Modifica los datos de una incidencia.
Eliminar issue_control_delete() Eliminar una indicencia.

Es buena práctica en Drupal que todas los nombres de funciones que declaremos se precedan con el nombre de nuestro módulo. Asi nos evitaremos sorpresas desagradables, ya que si dos funciones en distintos módulos se llaman igual, tendremos un error de PHP.

 

Cargar incidencia

La primera función de acceso a datos que necesitamos va a ser para obtener los datos de una incidencia de la tabla a partir de su identificador (issue_id).

function issue_control_load_issue($issue_id) {
  $issue = NULL;

  if (isset($issue_id)) {
    $issue = db_select('{issues}', 'i')
      ->fields('i')
      ->condition('issue_id', $issue_id, '=')
      ->execute()
      ->fetchAssoc();
  }

  return $issue;
}

Estamos utilzando en este caso la función db_select() de la API de Drupal para recuperar un único registro mediante la condición de que coincida el issue_id con el que recibimos por parámetro. Esta función nos devolverá un objeto de tipo SelectQuery que este caso estamos ejecutando directamente en la misma línea con execute() y recuperando el registro con fetchAssoc().

La función nos devolverá un array con los valores de los campos hemos especifiquedo en fields, en este caso todos. Para acceder mas tarde a los datos de los campos lo haremos así:

$issue = issue_control_load_issue($issue_id);
print ($issue['title']);

 

Crear incidencia

La siguiente función que vamos a necesitar será para dar de alta una nueva incidencia en la tabla. El código es el siguiente:

/**
 * Creates an issue.
 */
function issue_control_add($form_state) {
  global $user;

  $issue_id = db_insert('{issues}')
    ->fields(array(
      'title' => $form_state['values']['title'],
      'description' => $form_state['values']['description'],
      'uid' => $user->uid,
    ))
    ->execute();

  $message = t('New issue [@issue_id] has been created by uid [@uid].',
    array('@issue_id' => $issue_id, '@uid' => $user->uid)
  );
  watchdog('watchdog_form', $message);
  drupal_set_message($message);
}

En esta función estamos recibiendo como parámetro un array $form_state, que es que nos llega cuando se envía el formulario que construiremos mas tarde. De momento podeis ver que este $form_state obtenemos los valores de los campos para guardar el registro.

El otro dato que necesitamos a la hora de crear la incidencia es el identificador del usuario que la esta creando. Para ello usamos la variable global $user de Drupal, de la que podemos obtenemos el uid.

Aquí la parte importante es la función db_insert() de Drupal que es la encargada de guardar dicho registro. La sintaxis es muy sencilla, recibe por parámetro el nombre de la tabla y después usamos el método fields para añadir los campos y sus valores, y llamamos al execute() para lanzar la consulta.

A continuación estamos creando un mensaje con información del registro que acabamos de crear con el que vamos a hacer dos cosas:

  • Lo guardamos al watchdog de Drupal, para dejar un registro de cuando se creó.
  • Lo pasamos a drupal_set_message(), y Drupal lo mostrará en la siguiente página, para informar al usuario.

Esta función no devuelve ningún valor, aunque deberíamos añadir al menos un control de errores.

 

Modificar incidencia

Vamo ahora con la modificación de datos de una incidencia. En este caso también vamos a recibir por parámetro un array $form_state que vendrá del envío del formulario de edición.

function issue_control_edit($form_state) {
  global $user;

  db_update('{issues}')
    ->fields(array(
      'issue_id' => $form_state['values']['issue_id'],
      'title' => $form_state['values']['title'],
      'description' => $form_state['values']['description'],
      'uid' => $user->uid,
    ))
    ->condition('issue_id', $form_state['values']['issue_id'], '=')
    ->execute();

  $message = t('Issue [@issue_id] has been edited by uid [@uid].',
    array('@issue_id' => $form_state['values']['issue_id'], '@uid' => $user->uid)
  );
  watchdog('watchdog_form', $message);
  drupal_set_message($message);
}

Igual que en el caso de la creación del registro, usamos la variable global $user para obtener el uid del usuario que esta modificando la incidencia.

Para este caso, utlizamos la función db_update() de Drupal, y de forma similar a la creación del registro, obtenemos los valores de los campos del array $form_state, y establecemos que la condición para modificar los registros es la coincidencia por el issue_id con el que hemos recibido.

De la misma forma que en el caso anterior, componemos un mensaje que se manda al watchdog y se muestra al usuario.

 

Eliminar incidencia

La última función de acceso a datos es para borrar incidencias de la tabla.

function issue_control_delete($issue_id) {
  global $user;

  db_delete('{issues}')
    ->condition('issue_id', $issue_id)
    ->execute();

  $message = t('Issue [@issue_id] has been deleted by uid [@uid].',
    array('@issue_id' => $issue_id, '@uid' => $user->uid)
  );
  watchdog('watchdog_form', $message);
  drupal_set_message($message);
}

En este caso también es necesario recibir como parámetro el identificador de la incidencia para poder eliminarla utilizando db_delete() de Drupal.

Como en los casos anteriores, componemos un mensaje informativo y lo enviamos al watchdog y al usuario.

 

El formulario de creación y modificación

Una vez tenemos nuestra API de acceso a base de datos, solo nos queda contruir el formulario de creación y modificación de incidencias, y la llamada para eliminarlas.

Para construir el formulario vamos a utilizar la Form API de Drupal, que nos permite definir un formulario completo utilizando un array asociativo para definir los controles.

La función se llamara issue_control_form() y quedará así, atención a las partes en negrita:

function issue_control_form($form, &$form_status, $issue_id = NULL) {

  $issue = issue_control_load_issue($issue_id);

  $form['issue_id'] = array(
    '#type' => 'textfield',
    '#title' => 'Id',
    '#required' => TRUE,
    '#disabled' => TRUE,
    '#default_value' => isset($issue) ? $issue['issue_id'] : '',
  );

  $form['title'] = array(
    '#type' => 'textfield',
    '#title' => 'Title',
    '#required' => TRUE,
    '#default_value' => isset($issue) ? $issue['title'] : '',
  );

  $form['description'] = array(
    '#type' => 'textarea',
    '#title' => 'Description',
    '#default_value' => isset($issue) ? $issue['description'] : '',
  );

  if (isset($issue)) {
    $submit_message = t('Save issue');
  }
  else {
    $submit_message = t('Create issue');
  }

  $form['submit_button'] = array(
    '#type' => 'submit',
    '#value' => $submit_message,
  );

  return $form;
}

El formulario en este caso es muy sencillo, con tres campos y botón para envíar el formulario:

  • Identificador issue_id: Será un campo de texto sencillo (textfield), pero lo vamos a desactivar para que el usuario no pueda escribir en el.
  • Campo title: el título de la incidencia. Otro campo tipo textfield.
  • Campo description: Descripción de la incidencia. Este campo es mas largo, asi que usaremos un tipo textarea para mostrar una caja de texto grande con multiples líneas. Este tipo de campo puede configurarse también para usar un editor de texto enriquecido.

Usando Bootstrap se vería algo parecido a esto:

Formulario de incidencias

Lo imporante en este caso es que esta función sera utilizada por drupal_get_form(), y puede recibir un tercer parámetro que será el issue_id, en el caso de que sea llamada desde el hook_menu() para modificar. 

En el caso de recibir ese parámetro, cargamos los datos de esa incidencia mediante issue_control_load_issue(), y ponemos los valores por defecto de los campos a los valores correspondientes, para que el usuario ya vea el formulario relleno con los datos y pueda modificarlos.

Dependiendo del caso, ajustaremos tambien el texto del botón de enviar el formulario, mostrando el mensaje correspondiente a cada caso: Guardar o modificar.

Ahora necesitamos la función que va a controlar que hacer cuando el usuario envía el formulario:

function issue_control_form_submit($form, &$form_state) {

  if (empty($form_state['values']['issue_id'])) {
    issue_control_add($form_state);
  }
  else {
    issue_control_edit($form_state);
  }

  drupal_goto('/issues/list');
}

Esta función simplemente comprueba el issue_id que viene al envíar el formulario (en el $form_state) está vacío, en cuyo caso se tratará de una nueva incidencia, y llamamos a nuestra función issue_control_add para crear el nuevo registro.

Si por el contrario tenemos un valor en issue_id, se tratará de una moficiación, por lo que se llama a issue_control_edit().

En ambos casos, el usuario será redirigido a la página del listado de incidencias.

Eliminando incidencias

Para este ejemplo lo haremos muy sencillo, implementando la función que se llamará desde el hook_menu:

function issue_control_perform_delete($issue_id) {
  issue_control_delete($issue_id);
  drupal_goto('/issues/list');
}

Esta función simplemente llama a nuestra función de acceso a datos issue_control_delete() y le pasa el identificador de la incidencia. Una vez eliminada, devolvemos al usuario a la lista de incidencias.

Lo suyo sería implementar algún mecanismo para obtener una confirmación del usuario antes de eliminar, para evitar errores, pero no me quiero extender mas con este ejercicio, mejor lo dejamos para otro rato. Si alguien tiene interés que me deje un comentario.

 

Espero que el ejemplo no haya quedado demasiado espeso y que os ayude. A modo de cierre un par de cosas que quedan en el aire:

  • Habria que sacar el HTML de la lista de incidencias que vimos en la primera parte a un template.
  • Ojo porque no hemos hablado nada de seguridad. Todas las funciones de base de datos de Drupal que estamos usando estan protegidas contra inyección de SQL, pero es una buena práctica utilizar la funcion check_plain() cada vez que vayamos a pintar información que viene de la base de datos.

Muchas gracias y saludos