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");
}
}
?>
好的迁移系统让数据库版本管理变得简单。每个迁移文件都是小型的、可测试的、可回滚的。团队协作时不会出现数据库结构冲突,部署时也能自动更新数据库结构。
PHP数据库迁移与版本管理
张小明
前端开发工程师
告别繁琐安装:新手利用快马平台零配置开启python编程第一课
快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容: 请生成一个面向绝对新手的python入门学习项目。项目需包含:1、一个打印‘hello world’并接受用户输入名字的欢迎程序。2、一个简单的计算器,能执行加、减、…
Qt网络请求卡住怎么办?给你的应用加个带取消按钮的智能Loading提示框
Qt网络请求卡住怎么办?给你的应用加个带取消按钮的智能Loading提示框在开发Qt应用程序时,网络请求卡住是一个常见但令人头疼的问题。想象一下,用户点击了一个按钮发起网络请求,然后界面就卡在那里,没有任何反馈&#x…
51单片机PID温控从入门到放弃?手把手教你用Proteus搞定仿真(附完整源码)
51单片机PID温控实战:Proteus仿真全流程拆解第一次接触PID温控系统时,我被那些数学公式和参数调试搞得头晕目眩。直到在毕业设计中被迫啃下这块硬骨头,才发现只要掌握几个关键技巧,用51单片机实现温度控制并没有想象中那么难。本文…
5G NR PDSCH TBSize计算保姆级教程:从N_info量化到查表避坑
5G NR PDSCH TBSize计算实战指南:从协议公式到工程实现在5G NR物理层开发中,PDSCH(物理下行共享信道)的传输块大小(TBSize)计算是每个工程师必须掌握的硬核技能。不同于简单的查表操作,真实的TB…
用K210+STM32做个智能门禁:从硬件选型到代码调试的完整避坑指南
K210STM32智能门禁实战:从硬件选型到模型部署的避坑全记录去年帮朋友改造工作室门禁时,我原以为用现成的开发板搭建人脸识别系统会很简单,结果在电源干扰问题上栽了跟头——舵机动作时整个系统重启了三次。这个经历让我意识到,嵌入…
5分钟快速上手:免费开源的Marzipano全景图工具完整指南
5分钟快速上手:免费开源的Marzipano全景图工具完整指南 【免费下载链接】marzipano A 360 media viewer for the modern web. 项目地址: https://gitcode.com/gh_mirrors/ma/marzipano 你想在网页上创建令人惊叹的360度全景体验吗?Marzipano正是你…