tableExists($this->tableConfig->tableName)) { return; } $sql = $this->createMigrationsTableSQL($this->tableConfig->tableName); // PostgreSQL doesn't support multiple statements in prepared statements // Split by semicolon and execute each statement separately if ($this->platform->getName() === 'PostgreSQL') { $statements = array_filter( array_map('trim', explode(';', $sql)), fn($stmt) => !empty($stmt) ); foreach ($statements as $statement) { $this->connection->execute(SqlQuery::create($statement)); } } else { $this->connection->execute(SqlQuery::create($sql)); } } public function recordMigrationExecution(Migration $migration, string $version): void { $now = $this->clock->now()->format('Y-m-d H:i:s'); $sql = "INSERT INTO {$this->tableConfig->tableName} (version, description, executed_at) VALUES (?, ?, ?)"; $this->connection->execute(SqlQuery::create($sql, [$version, $migration->getDescription(), $now])); } public function recordMigrationRollback(string $version): void { $sql = "DELETE FROM {$this->tableConfig->tableName} WHERE version = ?"; $this->connection->execute(SqlQuery::create($sql, [$version])); } public function getAppliedVersions(): array { $this->ensureMigrationsTable(); $sql = "SELECT version FROM {$this->tableConfig->tableName} ORDER BY executed_at ASC"; return $this->connection->queryColumn(SqlQuery::create($sql)); } public function tableExists(string $tableName): bool { try { $sql = $this->platform->getTableExistsSQL($tableName); $result = $this->connection->queryScalar(SqlQuery::create($sql)); return (bool) $result; } catch (\Throwable $e) { return false; } } private function createMigrationsTableSQL(string $tableName): string { return match ($this->platform->getName()) { 'mysql' => "CREATE TABLE {$tableName} ( id INT AUTO_INCREMENT PRIMARY KEY, version VARCHAR(255) NOT NULL UNIQUE, description TEXT NOT NULL, executed_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, INDEX idx_version (version), INDEX idx_executed_at (executed_at) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci", 'PostgreSQL' => "CREATE TABLE {$tableName} ( id SERIAL PRIMARY KEY, version VARCHAR(255) NOT NULL UNIQUE, description TEXT NOT NULL, executed_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX idx_{$tableName}_version ON {$tableName} (version); CREATE INDEX idx_{$tableName}_executed_at ON {$tableName} (executed_at);", 'sqlite' => "CREATE TABLE {$tableName} ( id INTEGER PRIMARY KEY AUTOINCREMENT, version TEXT NOT NULL UNIQUE, description TEXT NOT NULL, executed_at TEXT NOT NULL DEFAULT (datetime('now')) ); CREATE INDEX idx_{$tableName}_version ON {$tableName} (version); CREATE INDEX idx_{$tableName}_executed_at ON {$tableName} (executed_at);", default => throw new \RuntimeException("Unsupported database platform: {$this->platform->getName()}") }; } }