Acceso a base datos en Drupal 7

Estamos haciendo unas sesiones de formación en el trabajo con Drupal 7, y nos ha quedado un ejercicio bastante completo que creo que merece la pena publicar por si puede ayudar a alguien mas.

La idea es mostrar cómo podemos crear un módulo en Drupal 7 que cree su propia tabla en la base de datos, y realizar operaciones sobre ella usando la API de acceso a datos de Drupal.

Para este ejemplo vamos a crear un módulo que nos permita gestionar una tabla sencilla de incidencias (Issues). Los objetivos va a ser:

  • Crear una nueva tabla en la base de datos de Drupal.
  • Listar todos los registros de la tabla
  • Crear nuevos registros mediante un formulario
  • Modificar los datos de un registro
  • Eliminar registros de la tabla

Voy a partir el ejercicio en dos artículos para que no me quede un ladrillo infumable, a ver si lo consigo.

Antes de empezar un par de cosas:

  • Estoy usando un Drupal 7 corriente y moliente. Para este ejemplo no necesitamos instalar ninguna dependencia (de módulos).
  • El tema de presentación que uso es Bootstrap, así que el HTML que vamos a ver usará algunas clases de Bootstrap que nos ayudará un poco con la presentación. No es realmente necesario y el HTML tendréis que adaptarlo según vuestras necesidades.
  • Todos los nombres de campos, tablas, etc. van a ir en inglés. Los textos de la interfaz (botones, etiquetas, etc.) también, ya que vamos a usar siempre la función t() de Drupal para traducir.

Comencemos.

 

Creando nuestro módulo

Para el ejemplo vamos a crear un nuevo módulo para el control de nuestra tabla de incidencias, que llamaremos issue_control. Lo creamos en el directorio estándar:

sites/all/modules/custom/issue_control

Tan sólo vamos a necesitar tres archivos:

  • issue_control.info: Archivo de control del módulo.
  • issue_control.module: Aquí tendremos nuestro código PHP.
  • issue_control.install: Lo usaremos para definir la tabla en la base de datos.

Para empezar, será suficiente con completar el archivo info, con algo parecido a esto:

name = Issues control
project = "issue_control"
description = "Store information into a database table"
package = Custom
core = 7.x
version = "7.x-0.0-rc1"

De momento, con esto nos vale para comenzar. Ojo, es mejor no instalar o activar el módulo todavía, hasta que tengamos la definición del esquema de base de datos. Vamos a ello, a ver como creamos nuestra tabla.
 

Hook Schema: Creando nuestra tabla

Perfecto, ya tenemos nuestro módulo. Ahora la idea es que al instalar el módulo, este nos cree automáticamente la tabla que necesitamos en la base de datos. Para ello vamos a usar el hook_schema(), que vamos a implementar en el archivo issue_control.install que acabamos de crear.

Pero primero vamos a ver que campos necesitamos en nuestra tabla. Hagamos algo sencillo:

Campo Tipo Nulo Descripción
issue_id Int NO El identificador de la incidencia. Debería ser una secuencia o un autoincrement. No puede ser nulo, ya que lo vamos a usar como identificador y primary key. En Drupal usaremos el tipo serial, que nos permite hacer justamente eso y no tener que preocuparnos de rellenarlo nosotros.
title String (255) NO Título de la incidencia. Una cadena de caracteres no muy larga. No queremos que se quede vacío. En Drupal usaremos el tipo varchar.
description String (Largo)   Este será el campo para guardar el contenido de la incidencia. Queremos que sea una text area donde el usuario pueda meter mucho texto. Para esto en Drupal tenemos el tipo text.
uid Int NO Vamos a incluir el id del usuario (uid) que crea o modifica la incidencia, para que nos dé un poco de juego el ejercicio. Este sera un campo numérico sencillo de tipo int en nuestro Drupal.

Bien, ahora que tenemos mas o menos claro los campos que queremos, vamos a crear nuestro hook_schema. Este hook debe ser implementado en el archivo .install del módulo, y nos va a permitir declarar una o varias tablas que se crearán cuando se instale el módulo (ojo: instalación, no activación) y se eliminarán automáticamente cuando se desinstale (ojo otra vez: desinstalar, no deshabilitar).

Vamos a ver el código:

<?php

/**
 * Implements hook_schema().
 */
function issue_control_schema() {
  $schema['issues'] = array(
    'description' => 'Table for storing issues',
    'fields' => array(
      'issue_id' => array(
        'type' => 'serial',
        'unsigned' => TRUE,
        'not null' => TRUE,
        'description' => 'Issue ID',
      ),
      'title' => array(
        'type' => 'varchar',
        'length' => 255,
        'not null' => TRUE,
        'default' => '',
        'description' => 'Issue title',
      ),
      'description' => array(
        'type' => 'text',
        'size' => 'normal',
        'description' => 'Issue description',
      ),
      'uid' => array(
        'type' => 'int',
        'unsigned' => TRUE,
        'not null' => TRUE,
        'default' => 0,
        'description' => 'User uid for the issue',
      ),

    ),
    'primary key' => array('issue_id'),
    'indexes' => array(
      'issueid' => array('issue_id'),
    ),
  );

  return $schema;
}

La referencia del Schema API de Drupal tiene bastante miga, y daría para varios artículos, pero no es el objetivo de este post. Recomiendo darle un buen repaso a la documentación, aunque no he podido encontrarla en castellano.

Es importante tener en cuenta que si tenéis errores en este fichero o en este hook, lo más probable es que pete al instalar el módulo y no cree nada. Para que Drupal vuelva a ejecutar el hook_schema después de una prueba fallida, teneis que desinstalar el módulo por completo y volver a instalar. Podeis usar drush pmu issue_control, o bien utilizar el módulo Devel, que tiene una opción para reinstalar módulos muy útil para esto, hasta que consigáis que funcione correctamente la creación de la tabla.

 

Listado de registros de la tabla

Lo primero que vamos a hacer es preparar una página para mostrar el listado de las incidencias guardadas en la tabla. Logicamente, al principio estará vacía si acabamos de instalar el módulo. Podemos crear algunos registros a mano, o bien utilizando una herramienta como phpMyAdmin. Una ejemplo sencillo para tener algunos datos de prueba podría ser usando Drush:

drush sql-cli

insert into issues (title, description, uid) values ('Un titulo', 'Description de pruebas', 1);

Para hacer nuestro listado vamos a utilizar dos funciones en nuestro archivo .module:

  • Un hook_menu para definir la URL que usaremos para la lista, y la función a llamar para pintarla.
  • Una función nuestra que devolverá el listado de incidencias en formato HTML.

Primero, vamos a crear un hook_menu, y definiremos la URL /issues/list para nuestro listado de incidencias. De esta forma, cuando carguemos la URL http://misitio.drupal/issues/list, obtendremos la lista de incidencias.

En nuestro archivo .module, declaramos el hook_menu:

/**
 * Implements hook_menu().
 *
 */
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,
  );

   return $items;
}

Con este hook definimos la URL y especificamos que al cargar dicha URL, se ejecutará la función issue_control_list() para pintar los resultados.

Después implementamos esa función para listar:

/**
 * Shows a list with all issues in the database table.
 */
function issue_control_list() {

  $content = '<table class="table table-hover table-striped">';
  $content .= '<tr>';
  $content .= '<th>' . t('Issue Id') . '</th>';
  $content .= '<th>' . t('Title') . '</th>';
  $content .= '<th>' . t('Description') . '</th>';
  $content .= '<th>' . t('Username') . '</th>';
  $content .= '<th>' . t('Actions') . '</th>';
  $content .= '</tr>';

  $results = db_query('SELECT * FROM {issues}');
  foreach ($results as $row) {
    $user = user_load($row->uid);

    $content .= '<tr>';
    $content .= '<td>' . $row->issue_id . '</td>';
    $content .= '<td>' . $row->title . '</td>';
    $content .= '<td>' . $row->description . '</td>';
    $content .= '<td>' . (!empty($user->name) ? $user->name : t('Anonymous')) . '</td>';
    $content .= '<td class="btn-toolbar">';
    $content .= '<a class="btn btn-xs btn-primary" href="' . url('/issues/edit/' . $row->issue_id) . '">' . t('Edit') . '</a>';
    $content .= '<a class="btn btn-xs btn-danger" href="' . url('/issues/delete/' . $row->issue_id) . '">' . t('Delete') . '</a>';
    $content .= '</td>';
    $content .= '</tr>';
  }

  $content .= '</table>';
  $content .= '<a class="btn btn-success" href="' . url('/issues/create') . '">' . t('Add an issue') . '</a>';

  return $content;
}

Con este código estamos obteniendo todos los registros de la tabla issues y pintando una tabla HTML para mostrar los resultados. Esta función tiene varias partes interesantes.

  1. Se utiliza una variable $content para ir guardando el HTML que vamos construyendo.
  2. Primero creamos las cabeceras de la tabla con los nombres de los campos.
  3. Después, se lanza la consulta a la base de datos para pedir todos los registros de la tabla issues.
  4. A continuación se utiliza un bucle para pintar cada uno de los registros. Para cada registro cargaremos el usuario de Drupal correspondiente al uid que venga en la issue, y pintamos su nombre.
  5. Las clases CSS que estoy utilizando son para Bootstrap, por ejemplo, los tabletable-striped nos proporcionarán una tabla más pintona, o los btn y btn-primary para los botones de acción.

 

Obteniendo los datos

Vamos a ver un poco mas en detalle cómo estamos obteniendo los registros de la base de datos. En este caso como es una query muy sencilla, usaremos la función db_query de Drupal:

$results = db_query('SELECT * FROM {issues}');

Esta llamada nos devuelve el resultado de la query como un array en la variable $results. Un detalle importante es utilizar el nombre de la tabla entre llaves, ya que Drupal lo reemplazará por la tabla correcta si nuestro sitio usa un prefijo (prefix) para la base de datos.

Este array tendrá un objeto de tipo stdClass por cada registro de la base de datos que devuelva la consulta, y en cada uno de estos objetos podremos acceder a los campos de la tabla como si fuesen propiedades, por ejemplo $row->title, nos devolverá el título. 

De esta forma, para leer y pintar los registros usaremos un bucle, e iremos recogiendo los valores de cada campo que queremos mostrar:

foreach ($results as $row) {
  $user = user_load($row->uid); // Aquí cargamos el usuario correspondiente por su uid.
  
  $content .= '<tr>';
  $content .= '<td>' . $row->issue_id . '</td>'; // Pintamos el campo issue_id.
  ... 
}

No debemos usar db_query para realizar operaciones de insert, update o delete, y si necesitamos realizar operaciones con la query antes de ejecutarla, es mejor usar la función db_select que veremos más adelante.

 

Resultados

Si todo ha ido bien tendremos algo parecido a este ejemplo que se ve bastante resultón gracias a Bootstrap:

Listado de incidencias

Como podéis ver, estamos ya pintando los botones de acción para cada registro: Edit y Delete, que son enlaces a otras páginas que aún no existen, y después de la tabla tenemos otro botón para crear una incidencia nueva. Estos enlaces serán:

  • Un enlace /issues/edit/[issue_id] para modificar los datos de una incidencia.
  • Otro a /issues/delete/[issue_id] para eliminar incidencias de la tabla.
  • Por último, /issues/create para dar de alta incidencias desde un formulario de Drupal.

Estas tres acciones las veremos en el próximo artículo, en el que os dejaré un enlace para descargar todo el código del ejemplo completo.

Como pequeña puntualización, esta función para pintar la lista tiene demasiado código HTML. Lo correcto sería utilizar una plantilla o template para pintar la lista, de forma que todo el HTML estaría dentro del template. Hay mil ejemplos en internet para utilizar templates en nuestros módulos con Drupal 7, pero aún así pienso escribir otro artículo después de estos para mostrar cómo hacerlo en condiciones con este ejemplo.

Muchas gracias y hasta pronto.

 

La segunda parte de este artículo esta aquí.