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
- Definir una constante o variable con la versión de esquema actual del plugin.
- Al cargar en admin_init, leer la versión guardada en la base de datos (get_option).
- Si la versión guardada es inferior, ejecutar un conjunto de migraciones ordenadas hasta alcanzar la versión actual.
- 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
- Probar migraciones en copia local o staging con dump completo de la base de datos real.
- Verificar que las opciones y datos se han trasladado correctamente después del upgrade.
- Comprobar rendimiento y locks (durante la migración) en tablas grandes.
- 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 :) |