Como migrar esquema de base de datos en updates del plugin en PHP en WordPress

Contents

Cómo migrar esquema de base de datos en actualizaciones de un plugin de WordPress (tutorial completo)

Este artículo describe paso a paso cómo diseñar y ejecutar migraciones de esquema de base de datos cuando publicas actualizaciones de un plugin para WordPress. Cubre buenas prácticas, seguridad, ejemplos de código, tratamiento para multisite, manejo de migraciones largas, pruebas, y cómo evitar problemas comunes en producción.

Resumen de la estrategia

  • Versionado: guarda una versión de esquema en la base de datos (opción) y compárala con la versión del plugin para decidir si ejecutar migraciones.
  • Migraciones atómicas y repetibles: cada migración debe poder ejecutarse una sola vez y ser segura si se repite.
  • Separación por funciones: organiza migraciones en funciones pequeñas y numeradas, ejecutándolas en orden.
  • Seguridad y respaldo: crea backups (o tablas temporales) antes de operaciones destructivas y usa transacciones cuando sea posible.
  • dbDelta para CREATE/ALTER: usa dbDelta para crear o añadir columnas a tablas nuevas, con atención a la sintaxis.

Dónde enganchar la comprobación de migración

Las comprobaciones suelen ejecutarse en admin_init o plugins_loaded (preferiblemente admin_init para que se ejecute cuando un administrador carga el área de administración). También puedes ejecutar lógica en register_activation_hook para instalaciones nuevas.

Estructura básica del flujo de actualización

  1. Definir una constante o variable con la versión de esquema actual del plugin.
  2. Al cargar en admin_init, leer la versión guardada en la base de datos (get_option).
  3. Si la versión guardada es inferior, ejecutar un conjunto de migraciones ordenadas hasta alcanzar la versión actual.
  4. Al finalizar cada migración, actualizar la opción de versión para que no se repita.

Ejemplo básico: check de versión y runner de migraciones

lt?php
define( MI_PLUGIN_DB_VERSION, 1.3 )
define( MI_PLUGIN_OPTION_DB_VERSION, mi_plugin_db_version )

add_action( admin_init, mi_plugin_maybe_upgrade )

function mi_plugin_maybe_upgrade() {
    if ( ! current_user_can( manage_options ) ) {
        return
    }

    installed_ver = get_option( MI_PLUGIN_OPTION_DB_VERSION )

    if ( installed_ver === false ) {
        // Nuevo sitio sin instalación previa: ejecutar instalación inicial
        mi_plugin_install()
        update_option( MI_PLUGIN_OPTION_DB_VERSION, MI_PLUGIN_DB_VERSION )
        return
    }

    if ( version_compare( installed_ver, MI_PLUGIN_DB_VERSION, < ) ) {
        mi_plugin_run_migrations( installed_ver )
        update_option( MI_PLUGIN_OPTION_DB_VERSION, MI_PLUGIN_DB_VERSION )
    }
}

function mi_plugin_install() {
    // Crear tablas iniciales
    mi_plugin_create_tables()
}
?gt

Patrón recomendado: migraciones por versión

Define funciones de migración por par de versiones: v1.0 → v1.1, v1.1 → v1.2, etc. Ejecuta sólo las migraciones necesarias en orden.

lt?php
function mi_plugin_run_migrations( installed_ver ) {
    migrations = array(
        1.0 => mi_migrate_1_0_to_1_1,
        1.1 => mi_migrate_1_1_to_1_2,
        1.2 => mi_migrate_1_2_to_1_3,
    )

    foreach ( migrations as ver => callback ) {
        if ( version_compare( installed_ver, ver, <= ) ) {
            if ( is_callable( callback ) ) {
                call_user_func( callback )
            }
        }
    }
}

// Ejemplo de migración
function mi_migrate_1_1_to_1_2() {
    global wpdb
    table = wpdb->prefix . mi_tabla

    // Añadir columna nueva
    wpdb->query(
        wpdb->prepare(
            ALTER TABLE {table} ADD COLUMN nueva_columna varchar(191) NOT NULL DEFAULT 
        )
    )
}
?gt

Uso de dbDelta para crear/alterar tablas

dbDelta es útil para crear tablas y añadir columnas exige que la sentencia CREATE TABLE tenga el formato correcto (espacios, backticks, tipos en mayúsculas o minúsculas consistentes). Siempre incluye wpdb->get_charset_collate() para collation y charset.

lt?php
function mi_plugin_create_tables() {
    global wpdb
    require_once ABSPATH . wp-admin/includes/upgrade.php

    table_name = wpdb->prefix . mi_tabla
    charset_collate = wpdb->get_charset_collate()

    sql = CREATE TABLE table_name (
        id BIGINT(20) NOT NULL AUTO_INCREMENT,
        nombre VARCHAR(191) NOT NULL,
        creado DATETIME DEFAULT CURRENT_TIMESTAMP,
        PRIMARY KEY  (id),
        INDEX (nombre)
    ) charset_collate

    dbDelta( sql )
}
?gt

Comprobaciones útiles (existen tabla/columna/índice)

Antes de ejecutar un ALTER o añadir índices, comprueba existencia para evitar errores.

lt?php
function mi_tabla_existe( table ) {
    global wpdb
    full = wpdb->prefix . table
    return wpdb->get_var( wpdb->prepare( SHOW TABLES LIKE %s, full ) ) === full
}

function mi_columna_existe( table_name, column ) {
    global wpdb
    full = wpdb->prefix . table_name
    row = wpdb->get_results( wpdb->prepare( SHOW COLUMNS FROM {full} LIKE %s, column ) )
    return ! empty( row )
}
?gt

Migraciones seguras para cambios complejos (rename, cambio de tipo)

Para cambios no triviales, usa una estrategia de tabla temporal para evitar interrupciones: crea la nueva tabla con el esquema final, copia los datos transformándolos, renombra tablas en una operación rápida. Si tu motor es InnoDB, usa transacciones para volver atrás si algo falla.

lt?php
function mi_migrate_1_2_to_1_3() {
    global wpdb
    old = wpdb->prefix . mi_tabla
    tmp = wpdb->prefix . mi_tabla_tmp
    backup = wpdb->prefix . mi_tabla_backup_ . time()

    // 1) crear tabla nueva (tmp) con esquema final
    charset_collate = wpdb->get_charset_collate()
    sql = CREATE TABLE tmp (
        id BIGINT(20) NOT NULL AUTO_INCREMENT,
        full_name VARCHAR(255) NOT NULL,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        PRIMARY KEY (id)
    ) charset_collate
    require_once ABSPATH . wp-admin/includes/upgrade.php
    dbDelta( sql )

    // 2) copiar y transformar datos desde la vieja tabla
    // ejemplo: concatenar nombre   apellido a full_name
    wpdb->query(
        INSERT INTO tmp (id, full_name, created_at)
         SELECT id, CONCAT(nombre,  , apellido) as full_name, creado
         FROM old
    )

    // 3) renombrar tablas: backup y swap (rápido)
    wpdb->query( RENAME TABLE old TO backup, tmp TO old )

    // NOTA: después puedes eliminar backup cuando verifiques
}
?gt

Manejo de errores y registro

  • Registra el resultado de cada migración en un log (opciones, archivo, o post_type privado) para diagnosticar fallos.
  • No hagas wp_die en migraciones automáticas mejor reporta errores y detén la operación de manera segura.
  • Valida los resultados de las consultas y, si usas transacciones, aplica rollback en caso de fallo.

Migraciones largas: evitar timeouts

Si la migración implica transformar muchas filas, evita ejecutarla de golpe. Opciones:

  • Dividir la migración en chunks y ejecutar múltiples pasos (cron o llamadas Ajax disparadas por el admin).
  • Usar WP Background Processing (librería de colas) o WP Cron para procesar lotes en segundo plano.
  • Mostrar progreso en el admin y reanudar en la misma versión hasta completar.

Migraciones en sitios multisite

En multisite debes iterar sobre cada sitio y ejecutar la migración en su contexto con switch_to_blog().

lt?php
function mi_plugin_run_migrations_multisite( installed_ver ) {
    if ( ! is_multisite() ) {
        mi_plugin_run_migrations( installed_ver )
        return
    }

    sites = get_sites( array( fields => ids ) )
    foreach ( sites as blog_id ) {
        switch_to_blog( blog_id )
        mi_plugin_run_migrations( get_option( MI_PLUGIN_OPTION_DB_VERSION ) )
        restore_current_blog()
    }
}
?gt

Buenas prácticas adicionales

  • Respaldos: nunca ejecutes migraciones destructivas en producción sin un backup reciente.
  • Pruebas en staging: crea entornos idénticos y prueba la migración con datos reales o representativos.
  • Control de acceso: limita la ejecución de migraciones a usuarios con capacidades administrativas.
  • Compatibilidad de collation: usa get_charset_collate() para evitar problemas con caracteres.
  • Preparación SQL: siempre que pases variables a consultas, usa wpdb->prepare o sanitiza evita concatenaciones inseguras.
  • Evita operaciones bloqueantes largas: reindexados o ALTER TABLE en tablas muy grandes pueden bloquear prefiera crear tabla nueva y swap.

Tabla resumen: cuándo usar qué

Operación Recomendación
Crear tabla nueva dbDelta o CREATE TABLE dbDelta incluir charset_collate
Añadir columna ALTER TABLE si pequeña dbDelta también puede añadir columnas si sintaxis correcta
Cambiar tipo/rename Crear tabla nueva y migrar datos (swap) para evitar locks largos
Migración masiva de datos Procesar en batches con WP Cron o background worker

Ejemplo final completo: patrón robusto

lt?php
define( MI_PLUGIN_DB_VERSION, 1.3 )
define( MI_PLUGIN_OPTION_DB_VERSION, mi_plugin_db_version )

add_action( admin_init, mi_plugin_maybe_upgrade )

function mi_plugin_maybe_upgrade() {
    if ( ! current_user_can( manage_options ) ) {
        return
    }

    installed_ver = get_option( MI_PLUGIN_OPTION_DB_VERSION )

    if ( installed_ver === false ) {
        mi_plugin_install()
        update_option( MI_PLUGIN_OPTION_DB_VERSION, MI_PLUGIN_DB_VERSION )
        return
    }

    if ( version_compare( installed_ver, MI_PLUGIN_DB_VERSION, < ) ) {
        mi_plugin_run_migrations( installed_ver )
        update_option( MI_PLUGIN_OPTION_DB_VERSION, MI_PLUGIN_DB_VERSION )
    }
}

function mi_plugin_install() {
    mi_plugin_create_tables()
    // Inserciones iniciales si son necesarias
}

function mi_plugin_run_migrations( installed_ver ) {
    map = array(
        1.0 => mi_migrate_1_0_to_1_1,
        1.1 => mi_migrate_1_1_to_1_2,
        1.2 => mi_migrate_1_2_to_1_3,
    )

    foreach ( map as ver => fn ) {
        if ( version_compare( installed_ver, ver, <= )  is_callable( fn ) ) {
            ok = call_user_func( fn )
            if ( ok === false ) {
                // Registrar fallo y abortar o tomar acciones
                error_log( mi_plugin: migración {fn} falló )
                return
            }
        }
    }
}
?gt

Pruebas y verificación

  1. Probar migraciones en copia local o staging con dump completo de la base de datos real.
  2. Verificar que las opciones y datos se han trasladado correctamente después del upgrade.
  3. Comprobar rendimiento y locks (durante la migración) en tablas grandes.
  4. Construir pruebas automatizadas que simulen versiones antiguas y ejecuten el proceso de upgrade.

Conclusión

Las migraciones de esquema en plugins de WordPress requieren planificación, versionado y rutinas cuidadosas. Sigue el patrón de versionado, divide migraciones complejas en pasos seguros (tabla temporal swap), maneja migraciones largas en background y prueba siempre en staging con backups. Con estas prácticas minimizarás downtime y errores en actualizaciones del plugin.

Recursos útiles: https://developer.wordpress.org/plugins/ y la referencia de dbDelta().



Acepto donaciones de BAT's mediante el navegador Brave :)



Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *