本文目录导读:

在 PHP 中实现数据库迁移回滚,主要取决于你使用的迁移工具,以下是几种常见方式的详细说明:
使用 Laravel(最常用)
Laravel 内置了强大的迁移系统,支持回滚操作。
基础命令
# 回滚最后一次迁移 php artisan migrate:rollback # 回滚指定步数(回滚最后2次) php artisan migrate:rollback --step=2 # 回滚所有迁移 php artisan migrate:rollback --step=0 # 回滚并重新迁移(常用于开发环境) php artisan migrate:refresh # 回滚所有并重新迁移 php artisan migrate:fresh
定义可回滚的迁移
<?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->timestamps();
});
}
/**
* 回滚迁移(必须实现)
*/
public function down()
{
Schema::dropIfExists('users');
}
}
部分回滚
// 仅在特定条件满足时执行回滚
public function down()
{
if (Schema::hasTable('users')) {
Schema::table('users', function (Blueprint $table) {
// 只删除特定列
$table->dropColumn(['name']);
});
}
}
使用纯 PHP(手动实现)
如果不使用框架,可以自己实现简单的迁移回滚机制:
基础迁移类
<?php
interface MigrationInterface
{
public function up(PDO $pdo);
public function down(PDO $pdo);
}
abstract class BaseMigration implements MigrationInterface
{
protected $connection;
public function __construct(PDO $pdo)
{
$this->connection = $pdo;
}
protected function execute($sql)
{
try {
$this->connection->exec($sql);
echo "执行成功: " . date('Y-m-d H:i:s') . "\n";
} catch (PDOException $e) {
echo "执行失败: " . $e->getMessage() . "\n";
throw $e;
}
}
protected function log($message)
{
echo "[INFO] " . date('Y-m-d H:i:s') . " - " . $message . "\n";
}
}
具体迁移实现
<?php
class CreateUsersTableMigration extends BaseMigration
{
public function up(PDO $pdo)
{
$sql = "CREATE TABLE IF NOT EXISTS users (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4";
$this->execute($sql);
$this->log("用户表创建完成");
}
public function down(PDO $pdo)
{
$sql = "DROP TABLE IF EXISTS users";
$this->execute($sql);
$this->log("用户表已删除");
}
}
迁移管理器
<?php
class MigrationManager
{
private $pdo;
private $migrationsDir;
private $migrations = [];
public function __construct(PDO $pdo, $migrationsDir)
{
$this->pdo = $pdo;
$this->migrationsDir = $migrationsDir;
$this->ensureMigrationsTable();
$this->loadMigrations();
}
private function ensureMigrationsTable()
{
$sql = "CREATE TABLE IF NOT EXISTS migration_logs (
id INT PRIMARY KEY AUTO_INCREMENT,
migration_name VARCHAR(255) UNIQUE NOT NULL,
executed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)";
$this->pdo->exec($sql);
}
private function loadMigrations()
{
$files = glob($this->migrationsDir . '/*.php');
sort($files);
foreach ($files as $file) {
$className = basename($file, '.php');
require_once $file;
$this->migrations[$className] = new $className($this->pdo);
}
}
// 执行迁移
public function migrate()
{
foreach ($this->migrations as $name => $migration) {
// 检查是否已执行
$stmt = $this->pdo->prepare("SELECT id FROM migration_logs WHERE migration_name = ?");
$stmt->execute([$name]);
if (!$stmt->fetch()) {
echo "正在执行迁移: $name\n";
$migration->up($this->pdo);
// 记录执行日志
$stmt = $this->pdo->prepare("INSERT INTO migration_logs (migration_name) VALUES (?)");
$stmt->execute([$name]);
echo "迁移完成: $name\n";
}
}
}
// 回滚迁移
public function rollback($steps = 1)
{
// 获取最近执行的迁移
$stmt = $this->pdo->prepare(
"SELECT migration_name FROM migration_logs
ORDER BY id DESC LIMIT ?"
);
$stmt->execute([$steps]);
$executedMigrations = $stmt->fetchAll(PDO::FETCH_COLUMN);
foreach ($executedMigrations as $name) {
if (isset($this->migrations[$name])) {
echo "回滚迁移: $name\n";
$this->migrations[$name]->down($this->pdo);
// 删除迁移记录
$stmt = $this->pdo->prepare("DELETE FROM migration_logs WHERE migration_name = ?");
$stmt->execute([$name]);
echo "回滚完成: $name\n";
}
}
}
// 回滚所有
public function rollbackAll()
{
$stmt = $this->pdo->query("SELECT migration_name FROM migration_logs ORDER BY id DESC");
$executedMigrations = $stmt->fetchAll(PDO::FETCH_COLUMN);
foreach ($executedMigrations as $name) {
if (isset($this->migrations[$name])) {
echo "回滚迁移: $name\n";
$this->migrations[$name]->down($this->pdo);
$stmt = $this->pdo->prepare("DELETE FROM migration_logs WHERE migration_name = ?");
$stmt->execute([$name]);
echo "回滚完成: $name\n";
}
}
}
}
使用示例
<?php
// 连接数据库
$pdo = new PDO('mysql:host=localhost;dbname=my_database', 'username', 'password');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// 创建迁移管理器
$manager = new MigrationManager($pdo, __DIR__ . '/migrations');
// 执行迁移
$manager->migrate();
// 回滚最后一次迁移
$manager->rollback();
// 回滚最后3次迁移
$manager->rollback(3);
// 回滚所有迁移
$manager->rollbackAll();
使用第三方库
Doctrine Migrations
composer require doctrine/migrations
<?php
use Doctrine\Migrations\Configuration\Configuration;
use Doctrine\Migrations\DependencyFactory;
use Doctrine\Migrations\Configuration\Connection\ExistingConnection;
use Doctrine\Migrations\Configuration\Migration\PhpFile;
// 配置迁移
$config = new PhpFile('migrations.php');
$connection = new ExistingConnection($pdo);
$dependencyFactory = DependencyFactory::fromConnection($config, $connection);
// 回滚到指定的迁移版本
$dependencyFactory->getMigrator()->rollback('MigrationName');
// 回滚到上一个版本
$dependencyFactory->getMigrator()->rollback(['step' => 1]);
Phinx
composer require robmorgan/phinx
// 回滚命令 vendor/bin/phinx rollback vendor/bin/phinx rollback -t 20170310101000 // 回滚到指定版本
重要注意事项
- 总是实现
down()方法:每个迁移必须有对应的回滚逻辑 - 测试回滚:在生产环境执行回滚前,先在开发环境测试
- 备份数据:重要数据回滚前先备份
- 确保幂等性:回滚操作应该可以重复执行
- 处理复杂关系:考虑外键约束、索引等复杂关系
最佳实践
- 使用事务:在迁移和回滚中使用数据库事务
- 记录日志:记录所有迁移和回滚操作
- 版本控制:迁移文件提交到代码仓库
- 持续集成:在 CI/CD 流程中集成迁移测试
选择哪种方式取决于项目规模和需求,对于小型项目,手动实现足够;对于大型项目,建议使用 Laravel 或 Doctrine 等成熟方案。