PHP项目如何实现数据迁移工具?

wen PHP项目 1

本文目录导读:

PHP项目如何实现数据迁移工具?

  1. 使用成熟的数据迁移库
  2. 手动实现轻量级迁移工具
  3. 迁移文件示例
  4. 数据库种子数据功能
  5. 命令行工具封装
  6. 最佳实践建议

我来详细介绍PHP项目实现数据迁移工具的几种主流方案和实践方法。

使用成熟的数据迁移库

Phinx (推荐)

最流行的PHP数据迁移库,支持多种数据库。

// 安装
composer require robmorgan/phinx
// 创建迁移文件
vendor/bin/phinx create MyNewMigration
// 示例迁移文件
<?php
use Phinx\Migration\AbstractMigration;
class CreateUsersTable extends AbstractMigration
{
    public function up()
    {
        $table = $this->table('users', ['id' => false, 'primary_key' => ['id']]);
        $table->addColumn('id', 'integer', ['signed' => false, 'identity' => true])
              ->addColumn('username', 'string', ['limit' => 100])
              ->addColumn('email', 'string')
              ->addColumn('password', 'string')
              ->addColumn('created_at', 'datetime')
              ->addColumn('updated_at', 'datetime')
              ->create();
    }
    public function down()
    {
        $this->dropTable('users');
    }
}

Laravel Migration (Laravel框架)

// Laravel框架内置
php artisan make:migration create_users_table
// 示例
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateUsersTable extends Migration
{
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('email')->unique();
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password');
            $table->rememberToken();
            $table->timestamps();
        });
    }
    public function down()
    {
        Schema::dropIfExists('users');
    }
}

手动实现轻量级迁移工具

<?php
class SimpleMigration
{
    private $pdo;
    private $migrationsPath;
    private $migrationsTable = 'migrations';
    public function __construct(PDO $pdo, string $migrationsPath)
    {
        $this->pdo = $pdo;
        $this->migrationsPath = $migrationsPath;
    }
    public function initialize()
    {
        // 创建迁移记录表
        $this->pdo->exec("
            CREATE TABLE IF NOT EXISTS {$this->migrationsTable} (
                id INT AUTO_INCREMENT PRIMARY KEY,
                migration VARCHAR(255) NOT NULL,
                batch INT NOT NULL,
                executed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
            )
        ");
    }
    public function migrate()
    {
        $this->initialize();
        // 获取已执行的迁移
        $executed = $this->getExecutedMigrations();
        $batch = $this->getNextBatchNumber();
        // 获取所有迁移文件
        $files = glob($this->migrationsPath . '/*.php');
        sort($files);
        foreach ($files as $file) {
            $migrationName = basename($file, '.php');
            if (!in_array($migrationName, $executed)) {
                // 执行迁移
                require_once $file;
                $className = $this->getClassName($migrationName);
                $migration = new $className($this->pdo);
                echo "Running migration: {$migrationName}\n";
                $migration->up();
                // 记录执行状态
                $stmt = $this->pdo->prepare(
                    "INSERT INTO {$this->migrationsTable} (migration, batch) VALUES (?, ?)"
                );
                $stmt->execute([$migrationName, $batch]);
            }
        }
    }
    public function rollback()
    {
        // 获取最后一批迁移
        $stmt = $this->pdo->prepare(
            "SELECT migration FROM {$this->migrationsTable} WHERE batch = (
                SELECT MAX(batch) FROM {$this->migrationsTable}
            ) ORDER BY id DESC"
        );
        $stmt->execute();
        $lastBatch = $stmt->fetchAll(PDO::FETCH_COLUMN);
        foreach ($lastBatch as $migrationName) {
            require_once $this->migrationsPath . '/' . $migrationName . '.php';
            $className = $this->getClassName($migrationName);
            $migration = new $className($this->pdo);
            echo "Rolling back: {$migrationName}\n";
            $migration->down();
            // 删除记录
            $this->pdo->prepare(
                "DELETE FROM {$this->migrationsTable} WHERE migration = ?"
            )->execute([$migrationName]);
        }
    }
    private function getExecutedMigrations(): array
    {
        $stmt = $this->pdo->query(
            "SELECT migration FROM {$this->migrationsTable} ORDER BY id ASC"
        );
        return $stmt->fetchAll(PDO::FETCH_COLUMN);
    }
    private function getNextBatchNumber(): int
    {
        $stmt = $this->pdo->query(
            "SELECT COALESCE(MAX(batch), 0) + 1 FROM {$this->migrationsTable}"
        );
        return (int) $stmt->fetchColumn();
    }
    private function getClassName(string $migrationName): string
    {
        // 移除时间戳,转换为类名
        $parts = preg_split('/_/', $migrationName, 2);
        return 'Migration_' . str_replace(' ', '', ucwords(str_replace('_', ' ', $parts[1] ?? $parts[0])));
    }
}
// 使用示例
try {
    $pdo = new PDO('mysql:host=localhost;dbname=test', 'root', 'password');
    $migration = new SimpleMigration($pdo, __DIR__ . '/migrations');
    // 执行迁移
    $migration->migrate();
    // 回滚
    // $migration->rollback();
} catch (PDOException $e) {
    echo "Database error: " . $e->getMessage() . "\n";
}

迁移文件示例

<?php
// migrations/20210101000001_create_users_table.php
class Migration_CreateUsersTable
{
    private $pdo;
    public function __construct(PDO $pdo)
    {
        $this->pdo = $pdo;
    }
    public function up()
    {
        $this->pdo->exec("
            CREATE TABLE users (
                id INT AUTO_INCREMENT PRIMARY KEY,
                username VARCHAR(100) NOT NULL,
                email VARCHAR(255) NOT NULL UNIQUE,
                password VARCHAR(255) NOT NULL,
                is_active TINYINT(1) DEFAULT 1,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
                INDEX idx_username (username),
                INDEX idx_email (email)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
        ");
    }
    public function down()
    {
        $this->pdo->exec("DROP TABLE IF EXISTS users");
    }
}

数据库种子数据功能

<?php
// seeds/AdminUserSeeder.php
class AdminUserSeeder
{
    private $pdo;
    public function __construct(PDO $pdo)
    {
        $this->pdo = $pdo;
    }
    public function run()
    {
        $stmt = $this->pdo->prepare("
            INSERT INTO users (username, email, password, role) 
            VALUES (?, ?, ?, ?)
        ");
        $stmt->execute([
            'admin',
            'admin@example.com',
            password_hash('admin123', PASSWORD_BCRYPT),
            'admin'
        ]);
        echo "Admin user created successfully\n";
    }
}

命令行工具封装

<?php
// migrate.php - 命令行入口
require_once 'vendor/autoload.php';
// 配置
$config = [
    'driver' => 'mysql',
    'host' => 'localhost',
    'database' => 'test',
    'username' => 'root',
    'password' => 'password',
    'charset' => 'utf8mb4',
    'migrations_path' => __DIR__ . '/migrations',
    'seeds_path' => __DIR__ . '/seeds'
];
// 参数处理
$command = $argv[1] ?? 'help';
$pdo = new PDO(
    "{$config['driver']}:host={$config['host']};dbname={$config['database']};charset={$config['charset']}",
    $config['username'],
    $config['password']
);
$migration = new SimpleMigration($pdo, $config['migrations_path']);
switch ($command) {
    case 'migrate':
        $migration->migrate();
        break;
    case 'rollback':
        $migration->rollback();
        break;
    case 'seed':
        // 运行种子数据
        $seederFile = $config['seeds_path'] . '/' . ($argv[2] ?? '') . '.php';
        if (file_exists($seederFile)) {
            require_once $seederFile;
            $seeder = new AdminUserSeeder($pdo);
            $seeder->run();
        }
        break;
    default:
        echo "Usage: php migrate.php [migrate|rollback|seed]\n";
}

最佳实践建议

版本控制

// 迁移文件命名规范
// YYYYMMDDHHMMSS_description.php
//  20231201120000_create_users_table.php
// 确保按时间顺序执行
function sortMigrations($files) {
    sort($files);
    return $files;
}

事务处理

// 使用事务确保数据一致性
public function migrate()
{
    try {
        $this->pdo->beginTransaction();
        // 执行迁移操作
        $this->pdo->exec("ALTER TABLE users ADD COLUMN phone VARCHAR(20)");
        $this->pdo->commit();
    } catch (Exception $e) {
        $this->pdo->rollBack();
        throw $e;
    }
}

安全考虑

// 验证迁移文件来源
function validateMigration($file) {
    // 检查文件格式
    if (!preg_match('/^\d{14}_.+\.php$/', basename($file))) {
        throw new Exception("Invalid migration filename: " . $file);
    }
    // 检查文件是否可读
    if (!is_readable($file)) {
        throw new Exception("Migration file not readable: " . $file);
    }
}

推荐使用 PhinxLaravel Migration 等成熟方案,它们提供了更完善的功能和更好的安全性,如果项目较小或需要自定义迁移逻辑,可以参考上述轻量级实现方案,记住始终在开发环境中测试迁移,并在生产环境中做好备份。

抱歉,评论功能暂时关闭!