Acceder al contenido principal

Actualizando un tipo de campo en Drupal

Volver a Blog

Actualizando un tipo de campo en Drupal

19 Nov 020
Drupal logo
Luis Juncal

Drupal

Seguro que os ha pasado alguna vez el tener que cambiar el tipo de campo de un tipo de contenido (o alguna otra entidad) al desarrollar en Drupal; creamos un campo y posteriormente nos damos cuenta de que no debería ser de ese tipo.

Un ejemplo muy simple: hemos creado un campo 'Código Postal', pero le hemos dicho que sea de tipo numérico, cuando debería ser de tipo texto, para que no existan problemas con códigos postales que empiecen por '0'.

Realizar este tipo de cambios no suele ser un problema, siempre y cuando estemos en la fase de desarrollo y no tengamos aún contenido de base de datos que queramos mantener. Tan fácil como:

  1. Eliminar el campo existente.
  2. Crear el campo nuevo, esta vez utilizando el tipo correcto.
  3. Ajustar código custom y configuración existente, si aplica.

Pero, ¿qué pasa si el problema lo descubrimos cuando ya existe contenido? Por ejemplo, estamos con un mantenimiento y necesitamos realizar este cambio, manteniendo los datos existentes. Drupal, por defecto, no nos permite cambiar el tipo de un campo una vez creado, y mucho menos si ya tiene contenido en base de datos.

La solución que podríamos haber utilizado en el desarrollo ya no nos vale; si eliminamos el campo existente para crear el nuevo, con él se nos va todo el contenido que tenía almacenado. Por lo que... ¿qué solución utilizamos?

 

¿Mantenemos dos campos a la vez?

Una posible solución es la siguiente: mantener el campo actual, con el tipo de campo incorrecto, y crear un campo nuevo con el tipo de campo correcto. Los datos que necesitamos mantener seguirán guardados en el campo viejo, y los nuevos los almacenaremos en el nuevo. Esta solución es simple, pero trae consigo una larga serie de problemas que debemos controlar. Algunos de ellos son:

  • Qué hacemos con el campo incorrecto? Es necesario controlar su acceso, edición, etc.
     
  • Qué pasa con la visualización del contenido? Tenemos dos campos que intentan representar lo mismo, de tipos distintos, que debemos controlar manualmente (vistas, plantilas...).
     
  • Si tenemos código custom que interactúa con estos campos, será necesario añadir lógica para controlar ambos.
     
  • Y muchas otras casuhísticas que requerirán un control, dependiendo claro de cada proyecto.

No parece una solución ideal para la mayoría de casos.

 

¿Migramos el campo antiguo al nuevo?

Otra solución sería la siguiente: igual que en la anterior, vamos a crear un campo nuevo con el tipo correcto, pero en vez de mantener ambos, vamos a migrar los datos del anterior al nuevo, y luego eliminarlo.

Ventajas comparadas con el primer método:

  • No necesitamos controlar el acceso campo antiguo, ya que no existe.
     
  • Sólo es necesario configurar la visualización del campo nuevo, en vez de ambos.
     
  • Si tenemos código custom, tendremos que actualizar la lógica, pero sólo para un campo, mucha menos complejidad.

Sigue sin ser una solución libre de problemas: no es viable mantener el mismo nombre máquina para el campo nuevo, ya que no podemos eliminar el viejo antes de crear el nuevo, lo que nos ahorraría bastante trabajo. Además, el despliegue ya no es tan sencillo como antes, ya que necesitaríamos:

  1. Desplegar configuración para el nuevo campo sin eliminar el anterior, y un mecanismo para migrar los datos.
     
  2. Realizar la migración de los datos (se podría hacer automáticamente con una actualización de base de datos).
     
  3. Desplegar configuración para eliminar el campo viejo.

Como mínimo, requerirá dos despliegues distintos, o modificar el funcionamiento típico de los despliegues.

 

¿Alguna opción mejor?

Existe otra opción, similar a la anterior, pero que nos permitiría mantener el nombre máquina de forma directa y realizar un despliegue mucho más simple:

 

Primero, necesitamos generar la configuración para el nuevo tipo de campo:

  1. Generamos un backup de la Base de datos y de la Configuración.
     
  2. Eliminamos el campo antiguo y creamos el campo nuevo.
     
  3. Ajustamos la configuración necesaria (form display, display, algunas vistas, etc...)
     - Al mentener el mismo nombre, muchas configuraciones funcionarán automáticamente.
     
  4. Exportamos y guardamos esta nueva configuración, y recuperamos el backup.

 

Una vez tengamos la configuración para el nuevo tipo de campo, creamos una actualización automática de base de datos.

El código que utilizaríamos sería el siguiente:

/**
 * Change node__field_postcode field type.
 */
function my_module_update_8001() {
  $database = \Drupal::database();

  // Specify the field we want to update
  $table = 'node__field_postcode';
  $entity_type = 'node';
  $field_name = 'field_postcode';

  // Load the storage for the existing field
  $field_storage = FieldStorageConfig::loadByName($entity_type, $field_name);
  if (is_null($field_storage)) {
    return;
  }

  // Load the data from the existing field
  $rows = NULL;
  if ($database->schema()->tableExists($table)) {
    // The table data to restore after the update is completed.
    $rows = $database->select($table, 'n')
      ->fields('n')
      ->execute()
      ->fetchAll();
  }

  $new_fields = array();

  // Use existing field config for the new field.
  foreach ($field_storage->getBundles() as $bundle => $label) {
    $field = FieldConfig::loadByName($entity_type, $bundle, $field_name);
    $new_field = $field->toArray();
    $new_field['field_type'] = 'string';
    $new_field['settings'] = array();

    $new_fields[] = $new_field;
  }

  // Use existing field storage config for the new field storage
  $new_field_storage = $field_storage->toArray();
  $new_field_storage['type'] = 'string';
  $new_field_storage['settings'] = array(
    'max_length' => 255,
    'is_ascii' => FALSE,
    'case_sensitive' => FALSE,
  );

  // Delete the old field storage
  $field_storage->delete();

  // Purge field data now to allow new field and field_storage with same name
  // to be created. You may need to increase batch size.
  field_purge_batch(10);

  // Create new field storage with the previously obtained configuration.
  $new_field_storage = FieldStorageConfig::create($new_field_storage);
  $new_field_storage->save();

  // Create the new fields.
  foreach ($new_fields as $new_field) {
    $new_field = FieldConfig::create($new_field);
    $new_field->save();
  }

  // Restore existing data in the same table.
  if (!is_null($rows)) {
    foreach ($rows as $row) {

      // Add here any logic you need to update the old value to a new value
      
      $database->insert($table)
        ->fields((array) $row)
        ->execute();
    }
  }

}

El código se encargará de almacenar la información actual, eliminar el campo viejo y crear el campo nuevo, cambiándole el tipo, y recuperar los datos al nuevo campo.

Tendremos, por supuesto, que modificar el código para nuestro caso particular, editando el nombre del campo, el tipo de campo nuevo que queremos generar (y su configuración), y añadiendo la lógica necesaria para ajustar la información existente al nuevo tipo de campo. Cabe destacar que la lógica que podemos aplicar aquí puede ser tan compleja como queramos: algo simple como cambiar de número a texto, o algo más complejo como mapear un texto a términos de taxonomía, etc.

 

Una vez tengamos la configuración y la actualización de base de datos preparada, podemos realizar el despliegue de forma habitual:

  1. Lanzamos la actualización de base de datos, que moverá los valores a su nuevo campo y eliminará el antiguo.
     
  2. Importamos configuración, dejando estable la configuración del portal.
     
  3. Limpiamos caché.

 

Utilizando esta última opción, mantenemos el mismo nombre máquina y realizamos un despliegue habitual.

 

En conclusión,

idealmente, nunca necesitaremos tener que cambiar el tipo de un campo en Drupal, pero si surge el problema, existe al menos una solución rápida que nos permite reducir el impacto que puede suponer encontrarnos con este problema.

Comentarios

Añadir nuevo comentario