<?php
declare(strict_types=1);
namespace Machweis\Backend;
use PDO;
final readonly class MigrationRunner
{
public function __construct(private PDO $pdo, private string $directory) {}
public function run(): void
{
$this->pdo->exec('CREATE TABLE IF NOT EXISTS schema_migrations (migration VARCHAR(255) PRIMARY KEY, applied_at DATETIME NOT NULL)');
$applied = $this->pdo->query('SELECT migration FROM schema_migrations')->fetchAll(PDO::FETCH_COLUMN);
$files = glob($this->directory . '/*.sql') ?: [];
sort($files, SORT_STRING);
foreach ($files as $file) {
$name = basename($file);
if (in_array($name, $applied, true)) {
continue;
}
$sql = file_get_contents($file);
if ($sql === false) {
throw new \RuntimeException('Migration cannot be read: ' . $name);
}
try {
// MySQL implicitly commits DDL. Do not wrap SQL migrations in a
// transaction; every DDL migration must therefore be repeatable.
$this->pdo->exec($sql);
$statement = $this->pdo->prepare('INSERT INTO schema_migrations (migration, applied_at) VALUES (?, UTC_TIMESTAMP())');
$statement->execute([$name]);
} catch (\Throwable $error) {
throw new \RuntimeException('Migration failed: ' . $name . ' (' . $error->getMessage() . ')', 0, $error);
}
}
}
}
