Files
urlshort-qr-gen/src/Migrator.php
T
florian.wilde 2c9fbdec87 feat(core): Konfiguration, PDO-Datenbankschicht und Migrationen
SQLite als Standard (keine Einrichtung noetig, WAL aktiviert), optional
MySQL/MariaDB. Beide Treiber nutzen dieselben vorbereiteten Statements.

Die Migrationen laufen automatisch beim ersten Request; jede Migration
liefert das SQL je Treiber. Damit entfaellt ein manueller Schritt beim
Deployment ueber FTP.

data/ enthaelt eine eigene .htaccess als zweite Absicherung, falls das
Verzeichnis beim Hoster doch einmal im Webroot landet.
2026-08-03 18:11:38 +02:00

100 lines
2.7 KiB
PHP

<?php
declare(strict_types=1);
namespace App;
/**
* Sehr einfache Migrationen: jede Migration ist eine PHP-Datei in
* /migrations, die ein Array ['sqlite' => [...SQL], 'mysql' => [...SQL]]
* zurueckgibt. Bereits ausgefuehrte Migrationen werden in der Tabelle
* "migrations" vermerkt. Laeuft automatisch beim ersten Request.
*/
final class Migrator
{
public function __construct(
private readonly Database $db,
private readonly string $migrationsDir,
) {
}
/**
* @return list<string> Namen der ausgefuehrten Migrationen
*/
public function migrate(): array
{
$this->ensureMigrationsTable();
$applied = [];
$already = $this->appliedMigrations();
foreach ($this->availableMigrations() as $name => $file) {
if (in_array($name, $already, true)) {
continue;
}
/** @var array<string,list<string>> $definition */
$definition = require $file;
$statements = $definition[$this->db->driver()] ?? $definition['sqlite'] ?? [];
foreach ($statements as $sql) {
$sql = trim($sql);
if ($sql !== '') {
$this->db->pdo()->exec($sql);
}
}
$this->db->execute(
'INSERT INTO migrations (name, applied_at) VALUES (?, ?)',
[$name, gmdate('Y-m-d H:i:s')],
);
$applied[] = $name;
}
return $applied;
}
private function ensureMigrationsTable(): void
{
$sql = $this->db->driver() === 'mysql'
? 'CREATE TABLE IF NOT EXISTS migrations (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(190) NOT NULL UNIQUE,
applied_at DATETIME NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4'
: 'CREATE TABLE IF NOT EXISTS migrations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
applied_at TEXT NOT NULL
)';
$this->db->pdo()->exec($sql);
}
/**
* @return list<string>
*/
private function appliedMigrations(): array
{
$rows = $this->db->fetchAll('SELECT name FROM migrations');
return array_map(static fn (array $row): string => (string) $row['name'], $rows);
}
/**
* @return array<string,string> name => absoluter Pfad
*/
private function availableMigrations(): array
{
$files = glob($this->migrationsDir . '/*.php') ?: [];
sort($files, SORT_STRING);
$result = [];
foreach ($files as $file) {
$result[basename($file, '.php')] = $file;
}
return $result;
}
}