pdo = $connection->getPdo(); $this->migrationsDir = __DIR__ . '/../../../database/migrations'; } /** * Run all pending migrations. */ public function migrate(): void { $this->createMigrationsTable(); $executed = $this->getExecutedMigrations(); if (!is_dir($this->migrationsDir)) { mkdir($this->migrationsDir, 0755, true); } $files = glob($this->migrationsDir . '/*.sql'); if ($files === false) { $files = []; } sort($files); $count = 0; foreach ($files as $file) { $name = basename($file); if (!in_array($name, $executed)) { echo "Running migration: {$name}...\n"; $this->executeSqlFile($file); $this->logMigration($name); echo "Successfully ran: {$name}\n"; $count++; } } if ($count === 0) { echo "Nothing to migrate. Database is up to date!\n"; } } /** * Create the tracking migrations table if it does not exist. */ private function createMigrationsTable(): void { $sql = "CREATE TABLE IF NOT EXISTS migrations ( id INT AUTO_INCREMENT PRIMARY KEY, migration VARCHAR(255) NOT NULL, executed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;"; $this->pdo->exec($sql); } /** * Get list of already executed migrations. */ private function getExecutedMigrations(): array { $stmt = $this->pdo->query("SELECT migration FROM migrations"); $results = $stmt->fetchAll(PDO::FETCH_COLUMN); return $results ?: []; } /** * Execute SQL file. */ private function executeSqlFile(string $filePath): void { $sql = file_get_contents($filePath); if (!$sql) { return; } try { // Run SQL commands $this->pdo->exec($sql); } catch (\PDOException $e) { echo "Error running migration from file: " . basename($filePath) . "\n"; echo "Details: " . $e->getMessage() . "\n"; throw $e; } } /** * Log executed migration. */ private function logMigration(string $name): void { $stmt = $this->pdo->prepare("INSERT INTO migrations (migration) VALUES (?)"); $stmt->execute([$name]); } }