news 2026/6/6 23:32:40

PHP数据库迁移与版本管理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
PHP数据库迁移与版本管理

PHP数据库迁移与版本管理

数据库迁移是管理数据库结构变更的标准化方式。每次修改数据库结构都记录在迁移文件中,团队成员可以按顺序执行迁移,保持数据库结构一致。

先实现一个简单的迁移系统,理解迁移的工作原理。

```php
class Migration
{
protected PDO $pdo;

public function __construct(PDO $pdo)
{
$this->pdo = $pdo;
}

public function up(): void {}
public function down(): void {}
}

class MigrationRunner
{
private PDO $pdo;
private string $migrationDir;
private string $table = 'migrations';

public function __construct(PDO $pdo, string $migrationDir)
{
$this->pdo = $pdo;
$this->migrationDir = rtrim($migrationDir, '/');
$this->initTable();
}

private function initTable(): void
{
$this->pdo->exec("
CREATE TABLE IF NOT EXISTS {$this->table} (
id INT AUTO_INCREMENT PRIMARY KEY,
migration VARCHAR(255) NOT NULL,
batch INT NOT NULL,
executed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
");
}

public function migrate(): void
{
$executed = $this->getExecutedMigrations();
$files = $this->getMigrationFiles();
$batch = $this->getNextBatch();

$pending = array_diff($files, $executed);
if (empty($pending)) {
echo "所有迁移已完成\n";
return;
}

foreach ($pending as $file) {
$migrationClass = $this->loadMigration($file);
echo "执行迁移: $file\n";

try {
$this->pdo->beginTransaction();
$migrationClass->up();
$this->recordMigration($file, $batch);
$this->pdo->commit();
echo " 完成\n";
} catch (Exception $e) {
$this->pdo->rollBack();
echo " 失败: {$e->getMessage()}\n";
throw $e;
}
}
}

public function rollback(int $steps = 1): void
{
$lastBatch = $this->getLastBatch();
if ($lastBatch === 0) {
echo "没有可回滚的迁移\n";
return;
}

$migrations = $this->getBatchMigrations($lastBatch);

foreach (array_reverse($migrations) as $file) {
$migrationClass = $this->loadMigration($file);
echo "回滚迁移: $file\n";

try {
$this->pdo->beginTransaction();
$migrationClass->down();
$this->removeMigration($file);
$this->pdo->commit();
echo " 完成\n";
} catch (Exception $e) {
$this->pdo->rollBack();
echo " 失败: {$e->getMessage()}\n";
throw $e;
}
}
}

private function getExecutedMigrations(): array
{
$stmt = $this->pdo->query("SELECT migration FROM {$this->table}");
return $stmt->fetchAll(PDO::FETCH_COLUMN);
}

private function getMigrationFiles(): array
{
$files = glob($this->migrationDir . '/*.php');
sort($files);
return array_map('basename', $files);
}

private function getNextBatch(): int
{
$stmt = $this->pdo->query("SELECT COALESCE(MAX(batch), 0) FROM {$this->table}");
return (int)$stmt->fetchColumn() + 1;
}

private function getLastBatch(): int
{
$stmt = $this->pdo->query("SELECT COALESCE(MAX(batch), 0) FROM {$this->table}");
return (int)$stmt->fetchColumn();
}

private function getBatchMigrations(int $batch): array
{
$stmt = $this->pdo->prepare("SELECT migration FROM {$this->table} WHERE batch = ? ORDER BY id ASC");
$stmt->execute([$batch]);
return $stmt->fetchAll(PDO::FETCH_COLUMN);
}

private function loadMigration(string $file): Migration
{
require_once $this->migrationDir . '/' . $file;
$className = pathinfo($file, PATHINFO_FILENAME);
$className = $this->formatClassName($className);
return new $className($this->pdo);
}

private function formatClassName(string $filename): string
{
preg_match('/^\d+_\d+_\d+_\d+_\d+_(.+)$/', $filename, $matches);
if (isset($matches[1])) {
return implode('', array_map('ucfirst', explode('_', $matches[1])));
}
return $filename;
}

private function recordMigration(string $file, int $batch): void
{
$stmt = $this->pdo->prepare("INSERT INTO {$this->table} (migration, batch) VALUES (?, ?)");
$stmt->execute([$file, $batch]);
}

private function removeMigration(string $file): void
{
$stmt = $this->pdo->prepare("DELETE FROM {$this->table} WHERE migration = ?");
$stmt->execute([$file]);
}
}
?>

// 使用示例
$pdo = new PDO('mysql:host=localhost;dbname=test', 'root', '');
$runner = new MigrationRunner($pdo, __DIR__ . '/migrations');
$runner->migrate();
?>

迁移文件示例:

// migrations/2024_01_01_000001_create_users_table.php
class CreateUsersTable extends Migration
{
public function up(): void
{
$this->pdo->exec("
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(255) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
status VARCHAR(20) DEFAULT 'active',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
)
");
}

public function down(): void
{
$this->pdo->exec("DROP TABLE IF EXISTS users");
}
}
?>

简化版的迁移文件:

// 2024_01_02_000002_add_phone_to_users.php
class AddPhoneToUsers extends Migration
{
public function up(): void
{
$this->pdo->exec("ALTER TABLE users ADD COLUMN phone VARCHAR(20) AFTER email");
}

public function down(): void
{
$this->pdo->exec("ALTER TABLE users DROP COLUMN phone");
}
}
?>

好的迁移系统让数据库版本管理变得简单。每个迁移文件都是小型的、可测试的、可回滚的。团队协作时不会出现数据库结构冲突,部署时也能自动更新数据库结构。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/6 23:32:12

告别繁琐安装:新手利用快马平台零配置开启python编程第一课

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容: 请生成一个面向绝对新手的python入门学习项目。项目需包含:1、一个打印‘hello world’并接受用户输入名字的欢迎程序。2、一个简单的计算器,能执行加、减、…

作者头像 李华
网站建设 2026/6/6 23:18:11

5G NR PDSCH TBSize计算保姆级教程:从N_info量化到查表避坑

5G NR PDSCH TBSize计算实战指南:从协议公式到工程实现在5G NR物理层开发中,PDSCH(物理下行共享信道)的传输块大小(TBSize)计算是每个工程师必须掌握的硬核技能。不同于简单的查表操作,真实的TB…

作者头像 李华
网站建设 2026/6/6 23:17:01

用K210+STM32做个智能门禁:从硬件选型到代码调试的完整避坑指南

K210STM32智能门禁实战:从硬件选型到模型部署的避坑全记录去年帮朋友改造工作室门禁时,我原以为用现成的开发板搭建人脸识别系统会很简单,结果在电源干扰问题上栽了跟头——舵机动作时整个系统重启了三次。这个经历让我意识到,嵌入…

作者头像 李华
网站建设 2026/6/6 23:09:07

5分钟快速上手:免费开源的Marzipano全景图工具完整指南

5分钟快速上手:免费开源的Marzipano全景图工具完整指南 【免费下载链接】marzipano A 360 media viewer for the modern web. 项目地址: https://gitcode.com/gh_mirrors/ma/marzipano 你想在网页上创建令人惊叹的360度全景体验吗?Marzipano正是你…

作者头像 李华