[...SQL], 'mysql' => [...SQL]] * zurueckgibt. Bereits ausgefuehrte Migrationen werden in der Tabelle * "migrations" vermerkt. Laeuft automatisch beim ersten Request. */ final class Migrator { public function __construct( private readonly Database $db, private readonly string $migrationsDir, ) { } /** * @return list Namen der ausgefuehrten Migrationen */ public function migrate(): array { $this->ensureMigrationsTable(); $applied = []; $already = $this->appliedMigrations(); foreach ($this->availableMigrations() as $name => $file) { if (in_array($name, $already, true)) { continue; } /** @var array> $definition */ $definition = require $file; $statements = $definition[$this->db->driver()] ?? $definition['sqlite'] ?? []; foreach ($statements as $sql) { $sql = trim($sql); if ($sql !== '') { $this->db->pdo()->exec($sql); } } $this->db->execute( 'INSERT INTO migrations (name, applied_at) VALUES (?, ?)', [$name, gmdate('Y-m-d H:i:s')], ); $applied[] = $name; } return $applied; } private function ensureMigrationsTable(): void { $sql = $this->db->driver() === 'mysql' ? 'CREATE TABLE IF NOT EXISTS migrations ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(190) NOT NULL UNIQUE, applied_at DATETIME NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4' : 'CREATE TABLE IF NOT EXISTS migrations ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL UNIQUE, applied_at TEXT NOT NULL )'; $this->db->pdo()->exec($sql); } /** * @return list */ private function appliedMigrations(): array { $rows = $this->db->fetchAll('SELECT name FROM migrations'); return array_map(static fn (array $row): string => (string) $row['name'], $rows); } /** * @return array name => absoluter Pfad */ private function availableMigrations(): array { $files = glob($this->migrationsDir . '/*.php') ?: []; sort($files, SORT_STRING); $result = []; foreach ($files as $file) { $result[basename($file, '.php')] = $file; } return $result; } }