Curso de desarrollo de módulos para Drupal 7(III): hook_schema explicado con el módulo User

Para entender bien esta entrega será necesario tener conocimientos avanzados de uso de arrays en PHP.
Como explicar el hook_schema requiere de un modelo de datos en concreto vamos a apoyarnos sobre el módulo User, que forma parte del nucleo de Drupal.
Abrimos el fichero “modules/user/user.install” desde Aptana para ver su contenido, pulsando doble clic sobre él. Y nos encontramos con el siguiente código en la función user_schema():
/**
 * Implements hook_schema().
 */
function user_schema() {
  $schema[‘authmap’] = array(
    ‘description’ => ‘Stores distributed authentication mapping.’,
    ‘fields’ => array(
      ‘aid’ => array(
        ‘description’ => ‘Primary Key: Unique authmap ID.’,
        ‘type’ => ‘serial’,
        ‘unsigned’ => TRUE,
        ‘not null’ => TRUE,
      ),
      ‘uid’ => array(
        ‘type’ => ‘int’,
        ‘not null’ => TRUE,
        ‘default’ => 0,
        ‘description’ => “User’s {users}.uid.”,
      ),
      ‘authname’ => array(
        ‘type’ => ‘varchar’,
        ‘length’ => 128,
        ‘not null’ => TRUE,
        ‘default’ => ”,
        ‘description’ => ‘Unique authentication name.’,
      ),
      ‘module’ => array(
        ‘type’ => ‘varchar’,
        ‘length’ => 128,
        ‘not null’ => TRUE,
        ‘default’ => ”,
        ‘description’ => ‘Module which is controlling the authentication.’,
      ),
    ),
    ‘unique keys’ => array(
      ‘authname’ => array(‘authname’),
    ),
    ‘primary key’ => array(‘aid’),
    ‘foreign keys’ => array(
      ‘user’ => array(
        ‘table’ => ‘users’,
        ‘columns’ => array(‘uid’ => ‘uid’),
      ),
    ),
  );

  $schema[‘role_permission’] = array(
    ‘description’ => ‘Stores the permissions assigned to user roles.’,
    ‘fields’ => array(
      ‘rid’ => array(
        ‘type’ => ‘int’,
        ‘unsigned’ => TRUE,
        ‘not null’ => TRUE,
        ‘description’ => ‘Foreign Key: {role}.rid.’,
      ),
      ‘permission’ => array(
        ‘type’ => ‘varchar’,
        ‘length’ => 128,
        ‘not null’ => TRUE,
        ‘default’ => ”,
        ‘description’ => ‘A single permission granted to the role identified by rid.’,
      ),
      ‘module’ => array(
        ‘type’ => ‘varchar’,
        ‘length’ => 255,
        ‘not null’ => TRUE,
        ‘default’ => ”,
        ‘description’ => “The module declaring the permission.”,
      ),
    ),
    ‘primary key’ => array(‘rid’, ‘permission’),
    ‘indexes’ => array(
      ‘permission’ => array(‘permission’),
    ),
    ‘foreign keys’ => array(
      ‘role’ => array(
        ‘table’ => ‘roles’,
        ‘columns’ => array(‘rid’ => ‘rid’),
      ),
    ),
  );

  $schema[‘role’] = array(
    ‘description’ => ‘Stores user roles.’,
    ‘fields’ => array(
      ‘rid’ => array(
        ‘type’ => ‘serial’,
        ‘unsigned’ => TRUE,
        ‘not null’ => TRUE,
        ‘description’ => ‘Primary Key: Unique role ID.’,
      ),
      ‘name’ => array(
        ‘type’ => ‘varchar’,
        ‘length’ => 64,
        ‘not null’ => TRUE,
        ‘default’ => ”,
        ‘description’ => ‘Unique role name.’,
        ‘translatable’ => TRUE,
      ),
      ‘weight’ => array(
        ‘type’ => ‘int’,
        ‘not null’ => TRUE,
        ‘default’ => 0,
        ‘description’ => ‘The weight of this role in listings and the user interface.’,
      ),
    ),
    ‘unique keys’ => array(
      ‘name’ => array(‘name’),
    ),
    ‘primary key’ => array(‘rid’),
    ‘indexes’ => array(
      ‘name_weight’ => array(‘name’, ‘weight’),
    ),
  );

  $schema[‘users’] = array(
    ‘description’ => ‘Stores user data.’,
    ‘fields’ => array(
      ‘uid’ => array(
        ‘type’ => ‘int’,
        ‘unsigned’ => TRUE,
        ‘not null’ => TRUE,
        ‘description’ => ‘Primary Key: Unique user ID.’,
        ‘default’ => 0,
      ),
      ‘name’ => array(
        ‘type’ => ‘varchar’,
        ‘length’ => 60,
        ‘not null’ => TRUE,
        ‘default’ => ”,
        ‘description’ => ‘Unique user name.’,
      ),
      ‘pass’ => array(
        ‘type’ => ‘varchar’,
        ‘length’ => 128,
        ‘not null’ => TRUE,
        ‘default’ => ”,
        ‘description’ => “User’s password (hashed).”,
      ),
      ‘mail’ => array(
        ‘type’ => ‘varchar’,
        ‘length’ => 254,
        ‘not null’ => FALSE,
        ‘default’ => ”,
        ‘description’ => “User’s e-mail address.”,
      ),
      ‘theme’ => array(
        ‘type’ => ‘varchar’,
        ‘length’ => 255,
        ‘not null’ => TRUE,
        ‘default’ => ”,
        ‘description’ => “User’s default theme.”,
      ),
      ‘signature’ => array(
        ‘type’ => ‘varchar’,
        ‘length’ => 255,
        ‘not null’ => TRUE,
        ‘default’ => ”,
        ‘description’ => “User’s signature.”,
      ),
      ‘signature_format’ => array(
        ‘type’ => ‘varchar’,
        ‘length’ => 255,
        ‘not null’ => FALSE,
        ‘description’ => ‘The {filter_format}.format of the signature.’,
      ),
      ‘created’ => array(
        ‘type’ => ‘int’,
        ‘not null’ => TRUE,
        ‘default’ => 0,
        ‘description’ => ‘Timestamp for when user was created.’,
      ),
      ‘access’ => array(
        ‘type’ => ‘int’,
        ‘not null’ => TRUE,
        ‘default’ => 0,
        ‘description’ => ‘Timestamp for previous time user accessed the site.’,
      ),
      ‘login’ => array(
        ‘type’ => ‘int’,
        ‘not null’ => TRUE,
        ‘default’ => 0,
        ‘description’ => “Timestamp for user’s last login.”,
      ),
      ‘status’ => array(
        ‘type’ => ‘int’,
        ‘not null’ => TRUE,
        ‘default’ => 0,
        ‘size’ => ‘tiny’,
        ‘description’ => ‘Whether the user is active(1) or blocked(0).’,
      ),
      ‘timezone’ => array(
        ‘type’ => ‘varchar’,
        ‘length’ => 32,
        ‘not null’ => FALSE,
        ‘description’ => “User’s time zone.”,
      ),
      ‘language’ => array(
        ‘type’ => ‘varchar’,
        ‘length’ => 12,
        ‘not null’ => TRUE,
        ‘default’ => ”,
        ‘description’ => “User’s default language.”,
      ),
      ‘picture’ => array(
        ‘type’ => ‘int’,
        ‘not null’ => TRUE,
        ‘default’ => 0,
        ‘description’ => “Foreign key: {file_managed}.fid of user’s picture.”,
      ),
      ‘init’ => array(
        ‘type’ => ‘varchar’,
        ‘length’ => 254,
        ‘not null’ => FALSE,
        ‘default’ => ”,
        ‘description’ => ‘E-mail address used for initial account creation.’,
      ),
      ‘data’ => array(
        ‘type’ => ‘blob’,
        ‘not null’ => FALSE,
        ‘size’ => ‘big’,
        ‘serialize’ => TRUE,
        ‘description’ => ‘A serialized array of name value pairs that are related to the user. Any form values posted during user edit are stored and are loaded into the $user object during user_load(). Use of this field is discouraged and it will likely disappear in a future version of Drupal.’,
      ),
    ),
    ‘indexes’ => array(
      ‘access’ => array(‘access’),
      ‘created’ => array(‘created’),
      ‘mail’ => array(‘mail’),
    ),
    ‘unique keys’ => array(
      ‘name’ => array(‘name’),
    ),
    ‘primary key’ => array(‘uid’),
    ‘foreign keys’ => array(
      ‘signature_format’ => array(
        ‘table’ => ‘filter_format’,
        ‘columns’ => array(‘signature_format’ => ‘format’),
      ),
    ),
  );

  $schema[‘users_roles’] = array(
    ‘description’ => ‘Maps users to roles.’,
    ‘fields’ => array(
      ‘uid’ => array(
        ‘type’ => ‘int’,
        ‘unsigned’ => TRUE,
        ‘not null’ => TRUE,
        ‘default’ => 0,
        ‘description’ => ‘Primary Key: {users}.uid for user.’,
      ),
      ‘rid’ => array(
        ‘type’ => ‘int’,
        ‘unsigned’ => TRUE,
        ‘not null’ => TRUE,
        ‘default’ => 0,
        ‘description’ => ‘Primary Key: {role}.rid for role.’,
      ),
    ),
    ‘primary key’ => array(‘uid’, ‘rid’),
    ‘indexes’ => array(
      ‘rid’ => array(‘rid’),
    ),
    ‘foreign keys’ => array(
      ‘user’ => array(
        ‘table’ => ‘users’,
        ‘columns’ => array(‘uid’ => ‘uid’),
      ),
      ‘role’ => array(
        ‘table’ => ‘roles’,
        ‘columns’ => array(‘rid’ => ‘rid’),
      ),
    ),
  );

  return $schema;
}

Como puede verse en el ejemplo, básicamente está utilizando una variable llamada “$schema” que al final de la función devuelve. Dicha variable es un array que contiene un índice por cada tabla que necesita generar.
Por ejemplo si queremos generar la tabla ‘users’, indicamos que sobre el array $schema metemos un nuevo elemento con el índice ‘users’ y como valor metemos otro array.
El primer elemento del array valor de la tabla ‘users’ será la descripción de la tabla, cuyo índice será ‘description’ y cuyo valor será una cadena de caracteres con la descripción de la tabla.
El segundo elemento tendrá como índice el nombre de ‘fields’ y contiene un array con las defniciones de los campos de la tabla.
Así tendremos un array $schema definido de esta manera:
$schema[‘users’] = array(
    ‘description’ => ‘Stores user data.’,
    ‘fields’ => array()
);
En dicho array de campos (‘fields’), vamos introducciendo cada campo como un elemento cuyo índice es el nombre del campo y su valor es otro array con los metadatos del campo que queremos meter en la tabla, como el caso del campo ‘uid’…
$schema[‘users’] = array(
    ‘description’ => ‘Stores user data.’,
    ‘fields’ => array(
      ‘uid’ => array(
        ‘type’ => ‘int’,
        ‘unsigned’ => TRUE,
        ‘not null’ => TRUE,
        ‘description’ => ‘Primary Key: Unique user ID.’,
        ‘default’ => 0,
      )
     )
);
En este caso, el campo uid, tiene los siguientes elementos definidos:

  • type: tipo de campo definido por drupal
  • unasigned: valor booleano que permite definir si el campo es con o sin signo. Sólo disponible para campos numéricos.
  • not_null: valor booleano que nos indica si es campo puede ser null o no
  • description: descripción del campo
  • default: valor por defecto del campo

Como puede verse dicha definición de campo es una estructuración en forma de array de las típias configuraciones que solemos hacer en la propia base de datos cuando creamos un campo nuevo.
De esta manera ñadiríamos todos los campos que queremos que tenga la tabla, como distintos elementos del valor del array ‘fields’.
A continuación seguramente será necesario indicar de todos los campos cual (o cuales) es la clave primaria, en el caso de la tabla de users el campo ‘uid’ es el campo de clave primaria de la tabla, por lo que tenemos que insertar un nuevo elemento en el array principal $schema[‘users’] llamado ‘primary_key’ cuyo valor es un array con el nombre de los campos que son clave primaria. Por lo que el código quedaría así:
$schema[‘users’] = array(
    ‘description’ => ‘Stores user data.’,
    ‘fields’ => array(
      ‘uid’ => array(
        ‘type’ => ‘int’,
        ‘unsigned’ => TRUE,
        ‘not null’ => TRUE,
        ‘description’ => ‘Primary Key: Unique user ID.’,
        ‘default’ => 0,
      )
     ),
    ‘primary key’ => array(‘uid’)
);
 En el caso de la tabla users, tambiñen necesita hacer uso de índices sobre campos, para ello introduce también dentro del array $schema[‘users’] un elemento cuyo índice es ‘indexes’ cuyo valor debería ser un array con el nombre del índice y su valor un array con los nombres de los campos que queremos introducir en el índice. Así quedaría el array de la siguiente manera…
$schema[‘users’] = array(
    ‘description’ => ‘Stores user data.’,
    ‘fields’ => array(
      ‘uid’ => array(
        ‘type’ => ‘int’,
        ‘unsigned’ => TRUE,
        ‘not null’ => TRUE,
        ‘description’ => ‘Primary Key: Unique user ID.’,
        ‘default’ => 0,
      )//aquí introduciríamos el resto de elementos para definir el resto de campos de la tabla
     ),
    ‘primary key’ => array(‘uid’) ,
    ‘indexes’ => array(
      ‘access’ => array(‘access’),
      ‘created’ => array(‘created’),
      ‘mail’ => array(‘mail’),
    )
);
En este ejemplo, define 3 índices, sobre los campos access, created y mail. Por otra parte podemos definir índices únicos sobre campos, para ello debemos introducir un elemento más con índice ‘unique keys’ cuyo valor es un array con los nombres del índice y el valor de ese elemento es otro array con los campos que pertenecen a dicho array, en el ejemplo…
$schema[‘users’] = array(
    ‘description’ => ‘Stores user data.’,
    ‘fields’ => array(
      ‘uid’ => array(
        ‘type’ => ‘int’,
        ‘unsigned’ => TRUE,
        ‘not null’ => TRUE,
        ‘description’ => ‘Primary Key: Unique user ID.’,
        ‘default’ => 0,
      )//aquí introduciríamos el resto de elementos para definir el resto de campos de la tabla

     ),
    ‘primary key’ => array(‘uid’) ,
    ‘indexes’ => array(
      ‘access’ => array(‘access’),
      ‘created’ => array(‘created’),
      ‘mail’ => array(‘mail’),
    ),
    ‘unique keys’ => array(
      ‘name’ => array(‘name’),
    )
);
En el ejemplo crea un índice único llamado ‘name’  sobre el campo ‘name’. Dejamos para otro momento el índice ‘foreing keys’ por que tendríamos que explicar el modelo entero de users.

Debemos pensar que Drupal utilizará este hook para crear las tablas necesarias en la base de datos para el módulo, pero seguiríamos necesitando modificar el hook_install para introducir los datos iniciales en las tablas definidas en el hook_schema, como en el ejemplo…
// Insert a row for the anonymous user.
  db_insert(‘users’)
    ->fields(array(
      ‘uid’ => 0,
      ‘name’ => ”,
      ‘mail’ => ”,
    ))
    ->execute();
  Debido que al realizar la desinstalación del módulo user no hay que realizar más acciones que el borrado de las tablas generadas con la definición del hook_schema, el módulo user no necesita un hook_uninstall().

Práctica

  1. Genera un nuevo módulo en base a unos requisitos de base de datos dados.
  2. Define el hook_schema definiendo el modelo de base de datos
  3. Define el hook_install para introducir los datos iniciales en las tablas
  4. Prueba a realizar la instalación e activación del módulo, comprueba si las tablas se han generado correctamente.
  5. prueba a desactivar y desinstalar el módulo y mira a ver si ha borrado correctamente las tablas en la BBDD.

Referencias

Licencia Creative Commons

Curso de desarrollo de módulos con Drupal 7 (II): Instalación y Desinstalación

En esta entrega generaremos el fichero module.install en el que colocaremos los hooks de instalación y desinstalación. Para ello crearemos un nuevo fichero en el módulo, en nuestro caso el ejemplo.install y lo colocaremos en la carpeta principal del módulo. Como todo fichero de php del módulo deberemos colocar la cabecera apropiada e incluirlo en el fichero .info. Dentro del fichero ejemplo.info incluiremos una línea con el siguiente código:
files[] = ejemplo.install
De esta manera el módulo configura la inclusión del fichero ejemplo.install para que Drupal sepa que dispone de él.
Por otra parte, dentro del fichero ejemplo.install incluiremos el siguiente código:
<?php
/**
* @file
* Fichero del módulo de ejemplo para drupal
*
* Descripción larga del fichero que netra dentro del módulo de ejemplo de Drupal
*/
/**
 * implements hook_install
 */

function ejemplo_install() {
}

/**
 * implements hook_uninstall
 */
function ejemplo_uninstall() { 

}
Como puede verse incluimos dos funciones nuevas en el fichero: ejemplo_install() y ejemplo_uninstall(). En este caso están vacías, pero dichas funciones va a ejecutarse cuando se realice la instalación y desinstalación del módulo respectivamente.
La instalación del módulo, cuando se está haciendo el desarrollo, se realiza la primera vez que entramos en Drupal  y existe la carpeta del módulo.
Por lo que puede llegar a ser necesario realizar manualmente la desinstalación del módulo para que aplique los cambios de la instalación al volver a entrar. Para realizar la desinstalación deberemos acceder al listado de módulos, y desactivar el módulo.
Después en el listado de módulos deberemos pulsar en la pestaña “Desinstalar”…

Como puede verse esa pestaña aparece en la parte superior derecha de la página. Pulsamos en ella y nos aparecerá un formulario de desinstalación de módulos similar al siguiente…

com puede verse aparecerá todos aquellos módulos desactivados, en los cuales esté permitida su desinstalación. Pulsaremos la checkbox la lado del módulo que queremos desinstalar y pulsaremos en el botón “Desinstalar”. Nos aparecerá una confirmación de la desinstalación del módulo, similar a la siguiente…

pulsaremos en el botón “Desinstalar”. Y nos aparecerá el resultado de la desinstalación, si todo ha ido correctamente, veremos una pantalla indicando que el módulo se ha desinstalado correctamente, similar a la siguiente…

De esta manera se ejecutará el hook_uninstall, en nuestro caso la función ejemplo_uninstall() del ejemplo.module.
Después podemos volver al listado de módulos y volver a activar el módulo, por lo que se ejecutará el hook_install, en nuestro caso la función ejemplo_uninstall().
La idea fundamental de estas dos funciones es que deberían ser totalmente atómicas en su concepto. Es decir, todo aquello que haga el hook_install, debería ser deshecho por el hook_uninstall.

Planificando el módulo

Es hora de decidir qué es lo que queremos que haga el nuevo módulo. En nuesto ejemplo, intentaremos añadir un campo nuevo a Drupal para luego poder usarlo desde los contenidos. Por lo que necesitaremos usar ese campo dentro del módulo.

Desde la instalación deberemos decirle a Drupal que queremos usar un nuevo campo.
Desde la administración del módulo podremos elegir que tipos de contenido tendrán disponible este nuevo campo.
Desde la desinstalación, deberemos mirar todos los tipos de contenido para ver cuales de ellos disponen de ese campo, después borrar el campo de aquellos tipos de contenido que lo tengan y luego borrar el campo para que drupal no lo use.

hook_install()

Desde el hook_install() deberemos decir que debe ejecutar drupal cuando el módulo se instala.
En nuestro caso, debemos usar el propio API de Drupal para retocar los campos disponible para los tipos de datos, por lo que no es necesario cambiar el modelo de la base de datos, y no tenemos que hacer uso del hook_schema(). En el caso de que tuviesemos que generar nuevas tablas y campos de tabla en el modelo de la base de datos, sí tendríamos quehacer uso de hook_schema().
Para incluir el campo deberemos incluir el códufo en el hook_install(), en nuestro caso, en la función ejemplo_install(), con el siguiente código:
function ejemplo_install() {

  // comprueba si existe el código
  $field = field_info_field(‘annotation’);

  // if the annotation field does not exist then create it
  if (empty($field)) {
    $field = array(
      ‘field_name’ => ‘annotation’,
      ‘type’ => ‘text_with_summary’,
      ‘entity_types’ => array(‘node’),
      ‘translatable’ => TRUE,
    );
    $field = field_create_field($field);
  }

}
Com puede verse en este código fundamentalmente estamos usando las funciones del API de Field, cuyos nombres siempre empiezan por “field_”.
Las dos primeras líneas intenta averiguar mediante la función “field_info_field()” si el campo para las anotaciones ‘annotation’ existe ya en el Drupal donde queremos instalar el módulo.
Si es así no hace nada, si no es así, intenta crear el campo mediante la llamada a la función “field_create_field()” donde le pasa como parámetro un array con los metadatos principales del campo que quiere crear, en nuestro caso:

  • field_name: nombre del campo que queremos crear.
  • type: tipo de campo, responde a los distintos tipos redefinidos dentro de drupal
  • entity_types: tipo de entidad sobre el cual queremos hacer uso del campo
  • translatable: boolean que representa si el campo es traducible o no.

hook_uninstall()

Como se ha explicado anteriormente será necesario deshacer las acciones realizadas desde la administración y desde la instalación del módulo, por lo que el código de nuestro ejemplo_uninstall() será el siguiente…
/**
 * implements hook_uninstall
 */
function ejemplo_uninstall() {

   watchdog(“Annotate Module”, “Uninstalling module and deleting fields”);
  
   $types = node_type_get_types();  
  
   foreach($types as $type) {
      delete_annotation($type);  
   }
  
   $field = field_info_field(‘annotation’);

   if ($field) {
      field_delete_field(‘annotation’);
   }
  
}

 Como podemos observar lo primero que hacemos es sacar un mensaje a través de la función watchdog, donde pasamos dos parámetros, el nombre del módulo que genera el mensaje y el mensaje en sí mismo. Para informar de lo que estamos haciendo.
Posteriormente cogemos el listado de tipos de contenidos como un array mediante la función node_type_get_types().
Después recorremos el array de tipos para ir borrando uno a uno el campo ‘annotation’ en todos y cada uno de ellos, a través de una función creada por nosotros que incluiremos más adelante.
Finalmente, miramos a ver si existe el campo ‘annotation’ en el Drupal y si es así lo eliminamos mediante la función field_delete_field(), donde le pasamos como parámetro el nombre del campo ‘annotation’.
De esta manera toda la información que gestiona el módulo será borrada, tanto los campos que hayamos utilizado en los distintos tipos de datos, como el campo en sí mismo.
Por lo que podemos decir que hemos conseguido mantener esa atomicidad en el comportamiento entre install, admin y uninstall, como explicamos anteriormente.

Para que funcione correctamente el hook_uninstall(), agregamos también la función delete_annotation() al fichero ejemplo.install..
/**
 * Function:  delete_annotation($type)
 * Purpose :  deletes instances of the annotation field from content types
 */
function delete_annotation($type) {

  $instance = field_info_instance(‘node’, ‘annotation’, $type->type);

  if ($instance) {
     field_delete_instance($instance);
  }

}
Como puede verse, según el tipo de contenido que pasamos, intentamos saber si dicho tipo tiene asociado el campo ‘annotation’, mediante la llamada a la función field_info_instance. Como primer parámetro pasamos el tipo de contenido que queremos comprobar ‘node’, los contenidos. Como segundo parámetro pasamos el nombre del campo ‘annotation’ y como tercer parámetro pasamos el nombre del tipo de contenido que estamos comprobando.
Si dicho campo está relacionado con el tipo de contenido la variable $instance tendrá un valor distinto a false, por lo que ejecutaremos el método field_delete_instance(), pasándole como parámetro la variable $instance, que eliminará dicha relación con el tipo de contenido.

Referencias: 

Licencia Creative Commons

Suscríbete al Boletín

Si quieres estar al tanto de las novedades del blog, ya sabes :)
* = campo obligatorio

powered by MailChimp!

Uso de cookies

Este sitio web utiliza cookies para que usted tenga la mejor experiencia de usuario. Si continúa navegando está dando su consentimiento para la aceptación de las mencionadas cookies y la aceptación de nuestra política de cookies, pinche el enlace para mayor información. ACEPTAR

Aviso de cookies