connection = $this->getConnection(); $this->migrationsPath = __DIR__ . '/../../database/migrations'; } /** * Get database connection */ protected function getConnection(): Connection { $config = include __DIR__ . '/../../Config/database.php'; $connectionConfig = $config['connections'][$config['default']]; return new Connection($connectionConfig); } /** * Run all pending migrations */ public function run(): void { $this->createMigrationsTable(); $migrations = $this->getPendingMigrations(); foreach ($migrations as $migration) { $this->runMigration($migration); } echo "Migrations completed successfully!\n"; } /** * Create migrations table */ private function createMigrationsTable(): void { $sql = "CREATE TABLE IF NOT EXISTS migrations ( id INT AUTO_INCREMENT PRIMARY KEY, migration VARCHAR(255) NOT NULL, batch INT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP )"; $this->connection->execute($sql); } /** * Get pending migrations */ private function getPendingMigrations(): array { $migrationFiles = glob($this->migrationsPath . '/*.php'); $migratedFiles = $this->getMigratedFiles(); $pending = []; foreach ($migrationFiles as $file) { $filename = basename($file); if (!in_array($filename, $migratedFiles)) { $pending[] = $file; } } sort($pending); return $pending; } /** * Get already migrated files */ private function getMigratedFiles(): array { $sql = "SELECT migration FROM migrations ORDER BY id"; $results = $this->connection->fetchAll($sql); return array_column($results, 'migration'); } /** * Run single migration */ private function runMigration(string $file): void { $filename = basename($file); $className = $this->getMigrationClassName($filename); require_once $file; if (!class_exists($className)) { throw new \Exception("Migration class {$className} not found in {$file}"); } $migration = new $className(); $migration->up(); // Record migration $this->recordMigration($filename); echo "✓ {$filename}\n"; } /** * Get migration class name */ private function getMigrationClassName(string $filename): string { $name = pathinfo($filename, PATHINFO_FILENAME); $parts = explode('_', $name); // Remove timestamp array_shift($parts); // Convert to PascalCase $className = ''; foreach ($parts as $part) { $className .= ucfirst($part); } return $className; } /** * Record migration */ private function recordMigration(string $filename): void { $batch = $this->getNextBatchNumber(); $sql = "INSERT INTO migrations (migration, batch) VALUES (?, ?)"; $this->connection->execute($sql, [$filename, $batch]); } /** * Get next batch number */ private function getNextBatchNumber(): int { $sql = "SELECT MAX(batch) as max_batch FROM migrations"; $result = $this->connection->fetch($sql); return ($result['max_batch'] ?? 0) + 1; } /** * Rollback last batch */ public function rollback(): void { $lastBatch = $this->getLastBatchNumber(); if (!$lastBatch) { echo "No migrations to rollback.\n"; return; } $migrations = $this->getMigrationsByBatch($lastBatch); foreach (array_reverse($migrations) as $migration) { $this->rollbackMigration($migration); } echo "Rollback completed successfully!\n"; } /** * Get last batch number */ private function getLastBatchNumber(): ?int { $sql = "SELECT MAX(batch) as max_batch FROM migrations"; $result = $this->connection->fetch($sql); return $result['max_batch'] ?? null; } /** * Get migrations by batch */ private function getMigrationsByBatch(int $batch): array { $sql = "SELECT migration FROM migrations WHERE batch = ? ORDER BY id DESC"; $results = $this->connection->fetchAll($sql, [$batch]); return array_column($results, 'migration'); } /** * Rollback single migration */ private function rollbackMigration(string $filename): void { $file = $this->migrationsPath . '/' . $filename; if (!file_exists($file)) { echo "Warning: Migration file {$filename} not found.\n"; return; } $className = $this->getMigrationClassName($filename); require_once $file; if (!class_exists($className)) { echo "Warning: Migration class {$className} not found.\n"; return; } $migration = new $className(); $migration->down(); // Remove migration record $sql = "DELETE FROM migrations WHERE migration = ?"; $this->connection->execute($sql, [$filename]); echo "✓ Rolled back {$filename}\n"; } /** * Get migration status */ public function status(): void { $migrationFiles = glob($this->migrationsPath . '/*.php'); $migratedFiles = $this->getMigratedFiles(); echo "Migration Status:\n"; echo "================\n"; foreach ($migrationFiles as $file) { $filename = basename($file); $status = in_array($filename, $migratedFiles) ? '✓' : '✗'; echo "{$status} {$filename}\n"; } } }