PHP项目迁移与种子管理完整指南
项目迁移最佳实践
迁移前评估
# 检查PHP版本兼容性 php -v # 检查扩展依赖 php -m | grep -E "pdo|mysql|redis|mbstring" # 代码静态分析 phpstan analyze --level=max src/
数据库迁移策略
Schema迁移 (使用Phinx)

// migrations/20240101000001_create_users.php
use Phinx\Migration\AbstractMigration;
class CreateUsers extends AbstractMigration
{
public function up()
{
$table = $this->table('users');
$table->addColumn('username', 'string', ['limit' => 50])
->addColumn('email', 'string', ['limit' => 100])
->addColumn('created_at', 'datetime')
->create();
}
public function down()
{
$this->dropTable('users');
}
}
数据迁移脚本
// migrations/20240102000001_import_legacy_data.php
class ImportLegacyData extends AbstractMigration
{
public function up()
{
$legacy = new PDO('mysql:host=old_host;dbname=old_db', 'user', 'pass');
$new = $this->getAdapter()->getConnection();
// 分批处理避免内存溢出
$offset = 0;
$limit = 1000;
while ($rows = $legacy->query("SELECT * FROM old_users LIMIT $offset, $limit")) {
foreach ($rows as $row) {
$stmt = $new->prepare("INSERT INTO users (username, email) VALUES (?, ?)");
$stmt->execute([$row['name'], $row['email']]);
}
$offset += $limit;
}
}
}
环境配置管理
使用环境变量
// config/app.php
return [
'db' => [
'host' => $_ENV['DB_HOST'] ?? 'localhost',
'database' => $_ENV['DB_NAME'],
'username' => $_ENV['DB_USER'],
'password' => $_ENV['DB_PASS'],
],
'redis' => [
'host' => $_ENV['REDIS_HOST'] ?? '127.0.0.1',
'port' => $_ENV['REDIS_PORT'] ?? 6379,
]
];
多环境配置文件
# .env.example
APP_ENV=development
DB_HOST=localhost
DB_NAME=myapp_dev
DB_USER=dev_user
DB_PASS=dev_pass
REDIS_HOST=127.0.0.1
# .env.production
APP_ENV=production
DB_HOST=prod-db.example.com
DB_NAME=myapp
DB_USER=prod_user
DB_PASS=${DB_PASSWORD}
种子数据管理
使用Seeder类 (Laravel风格)
// database/seeds/UserSeeder.php
use Illuminate\Database\Seeder;
use App\Models\User;
use Illuminate\Support\Facades\Hash;
use Faker\Factory as Faker;
class UserSeeder extends Seeder
{
public function run()
{
$faker = Faker::create();
// 创建管理员账户
User::create([
'username' => 'admin',
'email' => 'admin@example.com',
'password' => Hash::make('password'),
'role' => 'admin'
]);
// 创建100个测试用户
for ($i = 0; $i < 100; $i++) {
User::create([
'username' => $faker->userName,
'email' => $faker->unique()->email,
'password' => Hash::make('password'),
'role' => 'user'
]);
}
}
}
自定义种子管理工具
// SeedManager.php
class SeedManager
{
private $seeders = [];
public function registerSeeder($name, callable $callback)
{
$this->seeders[$name] = $callback;
return $this;
}
public function run($seederName = null)
{
if ($seederName) {
if (!isset($this->seeders[$seederName])) {
throw new Exception("Seeder '$seederName' not found");
}
$this->executeSeeder($seederName);
} else {
foreach ($this->seeders as $name => $callback) {
$this->executeSeeder($name);
}
}
}
private function executeSeeder($name)
{
echo "Running seeder: $name...\n";
$start = microtime(true);
call_user_func($this->seeders[$name]);
$elapsed = microtime(true) - $start;
echo "Seeder '$name' completed in " . round($elapsed, 2) . "s\n";
}
}
// 使用示例
$manager = new SeedManager();
$manager->registerSeeder('users', function () {
// 用户种子逻辑
});
$manager->registerSeeder('products', function () {
// 产品种子逻辑
});
// 运行所有种子
$manager->run();
// 运行特定种子
$manager->run('users');
数据工厂模式
// UserFactory.php
class UserFactory
{
private $faker;
private $count = 1;
private $overrides = [];
public function __construct()
{
$this->faker = Faker\Factory::create();
}
public function count($n)
{
$this->count = $n;
return $this;
}
public function state(array $override)
{
$this->overrides = $override;
return $this;
}
public function make()
{
$users = [];
for ($i = 0; $i < $this->count; $i++) {
$users[] = array_merge([
'username' => $this->faker->userName,
'email' => $this->faker->unique()->email,
'password' => password_hash('password', PASSWORD_DEFAULT),
'created_at' => new DateTime()
], $this->overrides);
}
return $users;
}
public function create()
{
$users = $this->make();
// 批量插入数据库
$this->batchInsert($users);
return $users;
}
}
// 使用
$factory = new UserFactory();
$admin = $factory->count(1)->state(['role' => 'admin'])->create();
$users = $factory->count(50)->state(['role' => 'user'])->create();
迁移脚本执行系统
// MigrationRunner.php
class MigrationRunner
{
private $db;
private $migrationsDir;
private $tableName = 'migrations';
public function __construct(PDO $db, $migrationsDir)
{
$this->db = $db;
$this->migrationsDir = $migrationsDir;
$this->createMigrationsTable();
}
private function createMigrationsTable()
{
$this->db->exec("CREATE TABLE IF NOT EXISTS {$this->tableName} (
id INT AUTO_INCREMENT PRIMARY KEY,
migration VARCHAR(255) NOT NULL,
batch INT NOT NULL,
executed_at DATETIME DEFAULT CURRENT_TIMESTAMP
)");
}
public function migrate()
{
$executed = $this->getExecutedMigrations();
$files = glob($this->migrationsDir . '/*.php');
$batch = $this->getNextBatchNumber();
$migrated = 0;
foreach ($files as $file) {
$migration = basename($file, '.php');
if (in_array($migration, $executed)) {
continue;
}
require_once $file;
$className = $this->getClassName($migration);
if (!class_exists($className)) {
throw new Exception("Migration class '$className' not found");
}
$instance = new $className();
$instance->up($this->db);
$this->recordMigration($migration, $batch);
echo "Migrated: $migration\n";
$migrated++;
}
echo "Migrated $migrated migration(s)\n";
}
public function rollback($steps = 1)
{
$lastBatch = $this->getLastBatchNumber();
$migrations = $this->getMigrationsByBatch($lastBatch);
$rolled = 0;
foreach (array_reverse($migrations) as $migration) {
if ($rolled >= $steps) break;
require_once $this->migrationsDir . '/' . $migration . '.php';
$className = $this->getClassName($migration);
$instance = new $className();
$instance->down($this->db);
$this->removeMigrationRecord($migration);
echo "Rolled back: $migration\n";
$rolled++;
}
}
private function getClassName($migration)
{
// 将文件名转换为类名: 20240101000001_create_users => CreateUsers
$parts = explode('_', $migration);
array_shift($parts); // 移除时间戳部分
return implode('', array_map('ucfirst', $parts));
}
private function getExecutedMigrations()
{
$stmt = $this->db->query("SELECT migration FROM {$this->tableName}");
return $stmt->fetchAll(PDO::FETCH_COLUMN);
}
private function getNextBatchNumber()
{
$stmt = $this->db->query("SELECT MAX(batch) FROM {$this->tableName}");
return ($stmt->fetchColumn() ?? 0) + 1;
}
private function recordMigration($migration, $batch)
{
$stmt = $this->db->prepare(
"INSERT INTO {$this->tableName} (migration, batch) VALUES (?, ?)"
);
$stmt->execute([$migration, $batch]);
}
private function removeMigrationRecord($migration)
{
$stmt = $this->db->prepare(
"DELETE FROM {$this->tableName} WHERE migration = ?"
);
$stmt->execute([$migration]);
}
}
高级场景处理
数据迁移验证
// MigrationValidator.php
class MigrationValidator
{
public function validate($sourceDb, $targetDb, $tables)
{
$results = [];
foreach ($tables as $table) {
$results[$table] = $this->compareTables($sourceDb, $targetDb, $table);
}
return $results;
}
private function compareTables($source, $target, $table)
{
$sourceCount = $source->query("SELECT COUNT(*) FROM $table")->fetchColumn();
$targetCount = $target->query("SELECT COUNT(*) FROM $table")->fetchColumn();
// 抽样验证
if ($sourceCount == $targetCount) {
$sample = $source->query("SELECT * FROM $table LIMIT 100")->fetchAll();
foreach ($sample as $row) {
$id = $row['id'];
$targetRow = $target->query("SELECT * FROM $table WHERE id = $id")->fetch();
if ($row != $targetRow) {
return ['status' => 'FAIL', 'message' => "Row $id mismatch"];
}
}
return ['status' => 'PASS', 'count' => $sourceCount];
}
return ['status' => 'FAIL', 'message' => "Count mismatch: $sourceCount vs $targetCount"];
}
}
回滚策略
// Migration with safety checks
class SafeMigration
{
private $backup;
public function beforeMigrate($db)
{
// 创建备份
$this->backup = $db->query("SELECT * FROM users")->fetchAll();
}
public function rollback($db)
{
// 清空并恢复数据
$db->exec("TRUNCATE TABLE users");
$stmt = $db->prepare("INSERT INTO users (id, username, email) VALUES (?, ?, ?)");
foreach ($this->backup as $row) {
$stmt->execute([$row['id'], $row['username'], $row['email']]);
}
}
}
性能优化建议
-
批量操作
- 使用
INSERT ... VALUES (...), (...), ...批量插入 - 使用事务包含大量操作
- 使用
-
索引管理
- 迁移后重建索引
- 使用
pt-online-schema-change进行在线DDL
-
分批处理
// 使用游标处理大量数据 $stmt = $db->prepare("SELECT * FROM big_table WHERE id > :lastId LIMIT 1000"); $lastId = 0; while (true) { $stmt->execute(['lastId' => $lastId]); $rows = $stmt->fetchAll(); if (empty($rows)) break; foreach ($rows as $row) { // 处理数据 $lastId = $row['id']; } } -
监控与日志
// 记录迁移日志 class MigrationLogger { public function log($message, $level = 'INFO') { $logEntry = sprintf( "[%s] [%s] %s\n", date('Y-m-d H:i:s'), $level, $message ); file_put_contents('migration.log', $logEntry, FILE_APPEND); } }
这套完整的迁移和种子管理方案涵盖了从开发到生产的全部流程,可以根据项目具体需求进行调整和扩展。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播