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.
100 lines
2.7 KiB
PHP
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;
|
|
}
|
|
}
|