Fixes db migration code

main
Yasen Pramatarov 2025-09-24 21:29:31 +03:00
parent 08953c6272
commit f22fa76987
4 changed files with 47 additions and 17 deletions

View File

@ -139,6 +139,10 @@ class Feedback {
'type' => self::TYPE_ERROR, 'type' => self::TYPE_ERROR,
'dismissible' => false 'dismissible' => false
], ],
'MIGRATIONS_PENDING' => [
'type' => self::TYPE_WARNING,
'dismissible' => true
],
]; ];
private static $strings = null; private static $strings = null;

View File

@ -7,12 +7,28 @@ use Exception;
class MigrationRunner class MigrationRunner
{ {
private PDO $db; private PDO $pdo;
private string $migrationsDir; private string $migrationsDir;
public function __construct(PDO $db, string $migrationsDir) /**
* @param mixed $db Either a PDO instance or the application's Database wrapper
* @param string $migrationsDir Directory containing .sql migrations
*/
public function __construct($db, string $migrationsDir)
{ {
$this->db = $db; // Normalize to PDO
if ($db instanceof PDO) {
$this->pdo = $db;
} elseif (is_object($db) && method_exists($db, 'getConnection')) {
$pdo = $db->getConnection();
if (!$pdo instanceof PDO) {
throw new Exception('Database wrapper did not return a PDO instance');
}
$this->pdo = $pdo;
} else {
$type = is_object($db) ? get_class($db) : gettype($db);
throw new Exception("Unsupported database type: {$type}");
}
$this->migrationsDir = rtrim($migrationsDir, '/'); $this->migrationsDir = rtrim($migrationsDir, '/');
if (!is_dir($this->migrationsDir)) { if (!is_dir($this->migrationsDir)) {
throw new Exception("Migrations directory not found: {$this->migrationsDir}"); throw new Exception("Migrations directory not found: {$this->migrationsDir}");
@ -22,12 +38,21 @@ class MigrationRunner
private function ensureMigrationsTable(): void private function ensureMigrationsTable(): void
{ {
$sql = "CREATE TABLE IF NOT EXISTS migrations ( $driver = $this->pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
id INT AUTO_INCREMENT PRIMARY KEY, if ($driver === 'sqlite') {
migration VARCHAR(255) NOT NULL UNIQUE, $sql = "CREATE TABLE IF NOT EXISTS migrations (
applied_at DATETIME NOT NULL id INTEGER PRIMARY KEY AUTOINCREMENT,
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"; migration TEXT NOT NULL UNIQUE,
$this->db->exec($sql); applied_at TEXT NOT NULL
)";
} else {
$sql = "CREATE TABLE IF NOT EXISTS migrations (
id INT AUTO_INCREMENT PRIMARY KEY,
migration VARCHAR(255) NOT NULL UNIQUE,
applied_at DATETIME NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4";
}
$this->pdo->exec($sql);
} }
public function listAllMigrations(): array public function listAllMigrations(): array
@ -39,7 +64,7 @@ class MigrationRunner
public function listAppliedMigrations(): array public function listAppliedMigrations(): array
{ {
$stmt = $this->db->query('SELECT migration FROM migrations ORDER BY migration ASC'); $stmt = $this->pdo->query('SELECT migration FROM migrations ORDER BY migration ASC');
return $stmt->fetchAll(PDO::FETCH_COLUMN) ?: []; return $stmt->fetchAll(PDO::FETCH_COLUMN) ?: [];
} }
@ -64,7 +89,7 @@ class MigrationRunner
} }
try { try {
$this->db->beginTransaction(); $this->pdo->beginTransaction();
foreach ($pending as $migration) { foreach ($pending as $migration) {
$path = $this->migrationsDir . '/' . $migration; $path = $this->migrationsDir . '/' . $migration;
$sql = file_get_contents($path); $sql = file_get_contents($path);
@ -75,16 +100,16 @@ class MigrationRunner
$statements = array_filter(array_map('trim', preg_split('/;\s*\n/', $sql))); $statements = array_filter(array_map('trim', preg_split('/;\s*\n/', $sql)));
foreach ($statements as $stmtSql) { foreach ($statements as $stmtSql) {
if ($stmtSql === '') continue; if ($stmtSql === '') continue;
$this->db->exec($stmtSql); $this->pdo->exec($stmtSql);
} }
$ins = $this->db->prepare('INSERT INTO migrations (migration, applied_at) VALUES (:m, NOW())'); $ins = $this->pdo->prepare('INSERT INTO migrations (migration, applied_at) VALUES (:m, NOW())');
$ins->execute([':m' => $migration]); $ins->execute([':m' => $migration]);
$appliedNow[] = $migration; $appliedNow[] = $migration;
} }
$this->db->commit(); $this->pdo->commit();
} catch (Exception $e) { } catch (Exception $e) {
if ($this->db->inTransaction()) { if ($this->pdo->inTransaction()) {
$this->db->rollBack(); $this->pdo->rollBack();
} }
throw $e; throw $e;
} }

View File

@ -44,5 +44,6 @@ return [
'DB_ERROR' => 'Error connecting to the database: %s', 'DB_ERROR' => 'Error connecting to the database: %s',
'DB_CONNECT_ERROR' => 'Error connecting to DB: %s', 'DB_CONNECT_ERROR' => 'Error connecting to DB: %s',
'DB_UNKNOWN_TYPE' => 'Error: unknown database type "%s"', 'DB_UNKNOWN_TYPE' => 'Error: unknown database type "%s"',
'MIGRATIONS_PENDING' => '%s',
], ],
]; ];

View File

@ -194,7 +194,7 @@ try {
$msg = 'Database schema is out of date. Pending migrations: ' . implode(', ', $pending) . '. Run: php scripts/migrate.php up'; $msg = 'Database schema is out of date. Pending migrations: ' . implode(', ', $pending) . '. Run: php scripts/migrate.php up';
// Log and show as a system message // Log and show as a system message
$logObject->log('warning', $msg, ['scope' => 'system']); $logObject->log('warning', $msg, ['scope' => 'system']);
Feedback::flash('DB', 'MIGRATIONS_PENDING', $msg, false, true); Feedback::flash('SYSTEM', 'MIGRATIONS_PENDING', $msg, false, true);
} }
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {