pdo = $pdo; $this->dir = rtrim($dir, '/\\'); } /** * 应用所有未执行的迁移,返回本次执行的迁移名列表。 * 注意:MySQL/MariaDB 的 DDL(CREATE/ALTER)会隐式提交,事务包裹无意义, * 故直接顺序执行;迁移 SQL 遵循幂等约定(IF NOT EXISTS / ON DUPLICATE KEY 等), * 失败重跑安全。 */ public function up(): array { $this->ensureTable(); $applied = $this->applied(); $executed = []; foreach ($this->files() as $name => $sql) { if (isset($applied[$name])) { continue; } try { $this->pdo->exec($sql); $stmt = $this->pdo->prepare('INSERT INTO g_migrations (name) VALUES (?)'); $stmt->execute([$name]); $executed[] = $name; } catch (\Throwable $e) { throw new \RuntimeException("迁移 {$name} 失败: " . $e->getMessage(), 0, $e); } } return $executed; } /** 全部迁移的应用状态。 */ public function status(): array { $this->ensureTable(); $applied = $this->applied(); $result = []; foreach ($this->files() as $name => $sql) { $result[] = ['name' => $name, 'applied' => isset($applied[$name])]; } return $result; } /** @return array 按编号排序的迁移 SQL。 */ private function files(): array { $files = []; foreach (glob($this->dir . '/*.sql') ?: [] as $file) { if (str_ends_with($file, '.down.sql')) { continue; // 回滚文件由 down 流程单独处理(本期未启用) } $files[basename($file)] = (string) file_get_contents($file); } ksort($files); return $files; } /** @return array 已应用迁移名集合。 */ private function applied(): array { $rows = $this->pdo->query('SELECT name FROM g_migrations')->fetchAll(PDO::FETCH_COLUMN); return array_fill_keys($rows, true); } private function ensureTable(): void { $this->pdo->exec( "CREATE TABLE IF NOT EXISTS g_migrations ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL UNIQUE, applied_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci" ); } }