← Назад в блог

Migrations Plugin: управление миграциями базы данных WordPress

Практический обзор плагина для управления миграциями базы данных WordPress: версионирование изменений БД, автоматическое выполнение, безопасный перенос сущностей, откат изменений и логирование.

Migrations Plugin: управление миграциями базы данных WordPress

Требования

  • 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+
  • Права администратора

Установка

  1. Скопируйте папку плагина в wp-content/plugins/
  2. Перейдите в ПлагиныУстановленные плагины
  3. Найдите “Migrations Plugin”
  4. Нажмите Активировать

При активации плагин автоматически создаёт таблицу 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.phpMigration_001_Create_Users_Table
  • 002_add_status_column.phpMigration_002_Add_Status_Column
  • 003_migrate_old_data.phpMigration_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 для хранения истории выполненных миграций:

КолонкаТипОписание
idbigint(20)ID записи
migration_namevarchar(255)Имя миграции
migration_filevarchar(255)Имя файла миграции
executed_atdatetimeДата и время выполнения
execution_timedecimal(10,4)Время выполнения в секундах
statusvarchar(20)Статус (completed/failed)
error_messagetextСообщение об ошибке (если есть)

Итоги

Migrations Plugin — это мощный инструмент для управления изменениями базы данных WordPress. Он решает типичные задачи разработки:

  • ✅ Версионирование изменений БД
  • ✅ Автоматическое применение миграций
  • ✅ Безопасный перенос сущностей между сайтами
  • ✅ Откат изменений
  • ✅ Полное логирование операций
  • ✅ Удобный интерфейс управления

Плагин следует стандартам WordPress и обеспечивает безопасность всех операций. Подходит для разработки плагинов и тем, где требуется управление структурой базы данных.


Примечание: Перед выполнением миграций на продакшн-сервере всегда делайте резервную копию базы данных!

0 просмотров

Комментарии

Загрузка комментариев...
Пока нет комментариев. Будьте первым!