Migrations Plugin: управление миграциями базы данных WordPress
Практический обзор плагина для управления миграциями базы данных WordPress: версионирование изменений БД, автоматическое выполнение, безопасный перенос сущностей, откат изменений и логирование.
Requirements
- WordPress 5.0+
- PHP 8.1+
- MySQL 5.6+
- Базовое понимание SQL и структуры базы данных WordPress
О чём статья
Migrations Plugin — это плагин для WordPress, который позволяет версионировать изменения базы данных и управлять ими через удобный интерфейс. Вместо того чтобы вручную править структуру БД или запускать SQL-скрипты, можно создавать миграции, которые автоматически применяются при обновлении плагина или вручную через админ-панель.
Цель статьи: разобрать функциональность плагина, показать примеры создания миграций и объяснить, как безопасно переносить данные между сайтами.
Репозиторий проекта: GitHub - va-proger/wordpress-migrations-plugin
Проблема, которую решает плагин
При разработке WordPress-плагинов и тем часто возникает необходимость изменять структуру базы данных:
- ❌ Нет версионирования изменений БД
- ❌ Нет автоматического применения изменений при обновлении
- ❌ Нет возможности откатить изменения
- ❌ Сложно переносить данные между сайтами
- ❌ Нет истории выполненных изменений
- ❌ Риск потерять данные при неправильном выполнении SQL
Плагин решает все эти задачи, предоставляя систему миграций с логированием и возможностью отката.
Установка и активация
Требования
- WordPress 5.0+
- PHP 8.1+
- MySQL 5.6+
- Права администратора
Установка
- Скопируйте папку плагина в
wp-content/plugins/ - Перейдите в Плагины → Установленные плагины
- Найдите “Migrations Plugin”
- Нажмите Активировать
При активации плагин автоматически создаёт таблицу wp_migrations_log для хранения истории выполненных миграций.
Первая настройка
После активации перейдите в Инструменты → Миграции. Здесь можно:
- Просмотреть список всех миграций
- Запустить невыполненные миграции
- Откатить выполненные миграции
- Просмотреть логи выполнения
Основные возможности
1. Версионирование изменений БД
Все изменения структуры базы данных хранятся в виде отдельных файлов миграций в папке migrations/. Каждая миграция имеет уникальный номер и описание в имени файла:
migrations/
├── 001_create_example_table.php
├── 002_add_status_column.php
├── 003_migrate_old_data.php
└── ...
2. Автоматическое выполнение
Миграции можно запускать:
- Вручную через админ-панель (рекомендуется для продакшена)
- Автоматически при обновлении плагина (настраивается в коде)
3. Безопасный перенос сущностей
Плагин предоставляет класс Entity_Migrator для безопасного переноса:
- Постов с проверкой существования по ID, slug, мета-полям
- Пользователей с автоматическим обновлением существующих
- Терминов (категории, теги, таксономии)
- Мета-данных и связей с терминами
4. Откат изменений
Каждая миграция должна иметь метод down() для отката изменений. Откат выполняется в обратном порядке выполнения миграций.
5. Логирование
Все выполненные миграции записываются в таблицу wp_migrations_log с информацией о:
- Времени выполнения
- Длительности выполнения
- Статусе (completed/failed)
- Сообщениях об ошибках
Создание миграций
Базовая структура миграции
Каждая миграция — это PHP-класс с методами up() и down():
<?php
class Migration_001_Create_Example_Table {
public function up() {
global $wpdb;
$table_name = $wpdb->prefix . 'example_table';
$charset_collate = $wpdb->get_charset_collate();
$sql = "CREATE TABLE IF NOT EXISTS $table_name (
id bigint(20) NOT NULL AUTO_INCREMENT,
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);
}
public function down() {
global $wpdb;
$table_name = $wpdb->prefix . 'example_table';
$wpdb->query("DROP TABLE IF EXISTS $table_name");
}
}
Именование миграций
Правила:
- Файл:
001_descriptive_name.php - Класс:
Migration_001_Descriptive_Name
Имя класса формируется автоматически:
- Префикс
Migration_ - Номер из имени файла
- Каждое слово с заглавной буквы, разделено подчеркиванием
Примеры:
001_create_users_table.php→Migration_001_Create_Users_Table002_add_status_column.php→Migration_002_Add_Status_Column003_migrate_old_data.php→Migration_003_Migrate_Old_Data
Примеры использования
Пример 1: Создание таблицы с проверкой
<?php
class Migration_002_Create_Products_Table {
public function up() {
global $wpdb;
$table_name = $wpdb->prefix . 'products';
$charset_collate = $wpdb->get_charset_collate();
$sql = "CREATE TABLE IF NOT EXISTS $table_name (
id bigint(20) NOT NULL AUTO_INCREMENT,
name varchar(255) NOT NULL,
price decimal(10,2) NOT NULL,
description text,
created_at datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY idx_name (name)
) $charset_collate;";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($sql);
}
public function down() {
global $wpdb;
$table_name = $wpdb->prefix . 'products';
$wpdb->query("DROP TABLE IF EXISTS $table_name");
}
}
Пример 2: Добавление колонки с проверкой существования
<?php
class Migration_003_Add_Status_Column {
public function up() {
global $wpdb;
$table_name = $wpdb->prefix . 'products';
// Проверяем существование колонки через Entity_Migrator
if (!Entity_Migrator::column_exists($table_name, 'status')) {
$table_name_escaped = esc_sql($table_name);
$wpdb->query("ALTER TABLE {$table_name_escaped} ADD COLUMN status varchar(50) DEFAULT 'active'");
}
}
public function down() {
global $wpdb;
$table_name = $wpdb->prefix . 'products';
if (Entity_Migrator::column_exists($table_name, 'status')) {
$table_name_escaped = esc_sql($table_name);
$wpdb->query("ALTER TABLE {$table_name_escaped} DROP COLUMN status");
}
}
}
Пример 3: Безопасный перенос поста
<?php
class Migration_004_Import_Post {
public function up() {
$post_data = array(
'post_title' => 'Название поста',
'post_name' => 'post-slug',
'post_content' => 'Содержимое поста',
'post_status' => 'publish',
'post_type' => 'post',
'meta' => array(
'custom_field' => 'значение',
),
);
$result = Entity_Migrator::migrate_post($post_data, array(
'update_if_exists' => true, // Обновлять если существует
'update_meta' => true, // Обновлять мета-данные
'update_terms' => true, // Обновлять термины
));
if (!$result['success']) {
throw new Exception('Ошибка импорта поста: ' . $result['error']);
}
}
public function down() {
// Откат: удаление поста
global $wpdb;
$post_id = $wpdb->get_var($wpdb->prepare(
"SELECT ID FROM {$wpdb->posts} WHERE post_name = %s LIMIT 1",
'post-slug'
));
if ($post_id) {
wp_delete_post($post_id, true);
}
}
}
Пример 4: Массовый импорт постов
<?php
class Migration_005_Import_Multiple_Posts {
public function up() {
// Получаем посты для импорта через Entity_Selector
$posts = Entity_Selector::get_posts_for_migration(array(
'post_type' => 'product',
'post_status' => 'publish',
'numberposts' => 10,
));
foreach ($posts as $post_data) {
// Убираем ID для безопасного импорта
unset($post_data['ID']);
$result = Entity_Migrator::migrate_post($post_data, array(
'update_if_exists' => true,
'update_meta' => true,
'update_terms' => true,
));
if (!$result['success']) {
error_log('Ошибка импорта поста: ' . $result['error']);
}
}
}
public function down() {
// Откат: удаление импортированных постов
// Реализация зависит от логики
}
}
Безопасный перенос сущностей
Entity_Migrator
Класс Entity_Migrator предоставляет методы для безопасного переноса сущностей WordPress:
migrate_post($post_data, $options)
Переносит или обновляет пост с проверкой существования:
$result = Entity_Migrator::migrate_post($post_data, array(
'update_if_exists' => true, // Обновлять если существует
'skip_if_exists' => false, // Пропускать если существует
'update_meta' => true, // Обновлять мета-данные
'update_terms' => true, // Обновлять термины
));
// Результат:
// $result['success'] - успешность операции
// $result['action'] - 'created', 'updated' или 'skipped'
// $result['post_id'] - ID поста
migrate_user($user_data, $options)
Переносит или обновляет пользователя:
$result = Entity_Migrator::migrate_user($user_data, array(
'update_if_exists' => true,
'update_meta' => true,
));
migrate_term($term_data, $options)
Переносит или обновляет термин:
$result = Entity_Migrator::migrate_term($term_data, array(
'taxonomy' => 'category',
'update_if_exists' => true,
));
Проверка существования
Плагин проверяет существование элементов по:
- Для постов: ID, slug (post_name), мета-поля, заголовок + тип
- Для пользователей: ID, логин, email
- Для терминов: ID, slug, название + таксономия
Программный API
Запуск миграций
$runner = new Migration_Runner();
// Запустить все невыполненные миграции
$results = $runner->run();
// Запустить конкретную миграцию
$results = $runner->run('001_example_migration');
Откат миграций
// Откатить все миграции в обратном порядке
$results = $runner->rollback();
// Откатить конкретную миграцию
$results = $runner->rollback('001_example_migration');
Проверка статуса
// Проверить, выполнена ли миграция
$is_executed = Migrations_Plugin::is_migration_executed('001_example_migration');
// Получить список выполненных миграций
$executed = Migrations_Plugin::get_executed_migrations();
Безопасность
Плагин следует стандартам безопасности WordPress:
- ✅ Защита от SQL Injection — все запросы с переменными используют
$wpdb->prepare()илиesc_sql() - ✅ Защита от XSS — все выходные данные экранируются через
esc_html(),esc_attr(),esc_url() - ✅ Защита от CSRF — проверка nonce для всех действий в админ-панели
- ✅ Защита от Local File Inclusion — валидация путей к файлам миграций
- ✅ Проверка прав доступа —
current_user_can('manage_options')для всех административных функций - ✅ Валидация входных данных — санитизация всех пользовательских данных
Пример безопасного запроса
// ❌ Неправильно
$wpdb->query("SELECT * FROM {$wpdb->posts} WHERE post_title = '{$title}'");
// ✅ Правильно
$wpdb->get_results($wpdb->prepare(
"SELECT * FROM {$wpdb->posts} WHERE post_title = %s",
$title
));
Лучшие практики
1. Всегда используйте dbDelta для создания таблиц
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($sql);
2. Проверяйте существование перед изменением
if (!Entity_Migrator::table_exists($table_name)) {
// Создаём таблицу
}
if (!Entity_Migrator::column_exists($table_name, 'column_name')) {
// Добавляем колонку
}
3. Используйте транзакции для критических операций
global $wpdb;
$wpdb->query('START TRANSACTION');
try {
// Выполняем операции
$wpdb->query('COMMIT');
} catch (Exception $e) {
$wpdb->query('ROLLBACK');
throw $e;
}
4. Всегда реализуйте метод down()
Каждая миграция должна иметь возможность отката:
public function down() {
// Откат изменений
}
5. Логируйте ошибки
if (!$result['success']) {
error_log('Ошибка миграции: ' . $result['error']);
throw new Exception($result['error']);
}
Структура плагина
migrations-plugin/
├── migrations/ # Папка с миграциями
│ ├── 001_example_migration.php
│ └── ...
├── includes/ # Основные классы
│ ├── class-migrations.php # Главный класс плагина
│ ├── class-migration-runner.php # Выполнение миграций
│ ├── class-entity-migrator.php # Перенос сущностей
│ ├── class-entity-selector.php # Выбор элементов
│ ├── class-export-loader.php # Загрузка экспортов
│ └── class-backup.php # Резервное копирование
├── admin/ # Админ-панель
│ └── class-migrations-admin.php # Интерфейс управления
├── migrations-plugin.php # Главный файл плагина
└── README.md # Документация
Структура базы данных
Плагин создаёт таблицу wp_migrations_log для хранения истории выполненных миграций:
| Колонка | Тип | Описание |
|---|---|---|
id | bigint(20) | ID записи |
migration_name | varchar(255) | Имя миграции |
migration_file | varchar(255) | Имя файла миграции |
executed_at | datetime | Дата и время выполнения |
execution_time | decimal(10,4) | Время выполнения в секундах |
status | varchar(20) | Статус (completed/failed) |
error_message | text | Сообщение об ошибке (если есть) |
Итоги
Migrations Plugin — это мощный инструмент для управления изменениями базы данных WordPress. Он решает типичные задачи разработки:
- ✅ Версионирование изменений БД
- ✅ Автоматическое применение миграций
- ✅ Безопасный перенос сущностей между сайтами
- ✅ Откат изменений
- ✅ Полное логирование операций
- ✅ Удобный интерфейс управления
Плагин следует стандартам WordPress и обеспечивает безопасность всех операций. Подходит для разработки плагинов и тем, где требуется управление структурой базы данных.
Примечание: Перед выполнением миграций на продакшн-сервере всегда делайте резервную копию базы данных!



Комментарии