О чём статья
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 для хранения истории выполненных миграций.
Первая настройка
После активации перейдите в Инструменты → Миграции. Здесь можно:
- Просмотреть список всех миграций
- Запустить невыполненные миграции
- Откатить выполненные миграции
- Просмотреть логи выполнения
Основные возможности
- Версионирование изменений БД
Все изменения структуры базы данных хранятся в виде отдельных файлов миграций в папке migrations/. Каждая миграция имеет уникальный номер и описание в имени файла:
migrations/
├── 001\_create\_example\_table.php
├── 002\_add\_status\_column.php
├── 003\_migrate\_old\_data.php
└── ...
- Автоматическое выполнение
Миграции можно запускать:
- Вручную через админ-панель (рекомендуется для продакшена)
- Автоматически при обновлении плагина (настраивается в коде)
- Безопасный перенос сущностей
Плагин предоставляет класс Entity_Migrator для безопасного переноса:
- Постов с проверкой существования по ID, slug, мета-полям
- Пользователей с автоматическим обновлением существующих
- Терминов (категории, теги, таксономии)
- Мета-данных и связей с терминами
- Откат изменений
Каждая миграция должна иметь метод down() для отката изменений. Откат выполняется в обратном порядке выполнения миграций.
- Логирование
Все выполненные миграции записываются в таблицу 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_Table
- 002_add_status_column.php → Migration_002_Add_Status_Column
- 003_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
));
Лучшие практики
- Всегда используйте dbDelta для создания таблиц
require\_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($sql);
- Проверяйте существование перед изменением
if (!Entity\_Migrator::table\_exists($table\_name)) {
// Создаём таблицу
}
if (!Entity\_Migrator::column\_exists($table\_name, 'column\_name')) {
// Добавляем колонку
}
- Используйте транзакции для критических операций
global $wpdb;
$wpdb->query('START TRANSACTION');
try {
// Выполняем операции
$wpdb->query('COMMIT');
} catch (Exception $e) {
$wpdb->query('ROLLBACK');
throw $e;
}
- Всегда реализуйте метод down()
Каждая миграция должна иметь возможность отката:
public function down() {
// Откат изменений
}
- Логируйте ошибки
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 и обеспечивает безопасность всех операций. Подходит для разработки плагинов и тем, где требуется управление структурой базы данных.
Примечание: Перед выполнением миграций на продакшн-сервере всегда делайте резервную копию базы данных!
Top comments (0)