feat: Add basic persistence layer + migration framework

This commit is contained in:
overflowerror 2023-11-24 21:06:53 +01:00
parent bd94138991
commit b69be24727
6 changed files with 130 additions and 4 deletions

4
.gitignore vendored
View file

@ -1 +1,3 @@
.idea .idea
credentials.php

View file

@ -7,9 +7,12 @@ define("MAINTENANCE_MODE", require(ROOT . "/maintenance.php"));
if (MAINTENANCE_MODE) { if (MAINTENANCE_MODE) {
require(ROOT . "/templates/maintenance.php"); require(ROOT . "/templates/maintenance.php");
} else { } else {
$connection = require_once(ROOT . "/persistence/connection.php");
(require(ROOT . "/persistence/migrate.php"))($connection);
$router = require(ROOT . "/router/Router.php"); $router = require(ROOT . "/router/Router.php");
(require(ROOT . "/controllers/routes.php"))($router); (require(ROOT . "/controllers/routes.php"))($router);
$router->execute([
$router->execute(); "DB_CONNECTION" => $connection,
]);
} }

7
credentials.templ.php Normal file
View file

@ -0,0 +1,7 @@
<?php
// not constants so we can minimize their scope
$DB_DSN = "{DB_DSN}";
$DB_SCHEMA = "{DB_SCHEMA}";
$DB_USERNAME = "{DB_USERNAME}";
$DB_PASSWORD = "{DB_PASSWORD}";

View file

@ -0,0 +1,7 @@
<?php
return (function() {
require(ROOT . "/credentials.php");
return new PDO($DB_DSN, $DB_USERNAME, $DB_PASSWORD);
})();

107
persistence/migrate.php Normal file
View file

@ -0,0 +1,107 @@
<?php
function ensureDbStructureForMigrations(PDO $connection) {
require(ROOT . "/credentials.php");
if ($connection->query(<<<EOF
SELECT
TABLE_NAME
FROM
information_schema.TABLES
WHERE
TABLE_SCHEMA LIKE '${DB_SCHEMA}' AND
TABLE_TYPE LIKE 'BASE TABLE' AND
TABLE_NAME = 'ua_migrations'
EOF)->rowCount() == 0
) {
if ($connection->exec(<<<EOF
CREATE TABLE `ua_migrations` (
`id` INTEGER PRIMARY KEY,
`file` VARCHAR(255),
`applied` DATETIME DEFAULT CURRENT_TIMESTAMP
);
EOF) === false
) {
die("unable to bootstrap migrations: " . $connection->errorCode());
}
}
}
function getAllMigrations() {
$files = scandir(ROOT . "/persistence/migrations/");
$files = array_values(array_filter($files, fn($f) => $f[0] != "."));
sort($files);
$migrations = [];
foreach ($files as $file) {
$delimiterPos = strpos($file, "_");
$migrations[intval(substr($file, 0, $delimiterPos))] = $file;
}
return $migrations;
}
function getAppliedMigrations(PDO $connection) {
$result = $connection->query(<<<EOF
SELECT * FROM `ua_migrations`
EOF);
$migrations = [];
foreach ($result->fetchAll() as $row) {
$migrations[$row["id"]] = $row["file"];
}
return $migrations;
}
function getMigrationsToApply(PDO $connection) {
$all = getAllMigrations();
$applied = getAppliedMigrations($connection);
foreach ($applied as $id => $file) {
unset($all[$id]);
}
return $all;
}
function executeSqlScript(PDO $connection, string $sql, string $file) {
if ($connection->exec($sql) === false) {
die("failed to apply migration " . $file . ": " . $connection->errorCode());
}
}
function applyMigration(PDO $connection, int $id, string $file) {
$connection->beginTransaction();
$sql = file_get_contents(ROOT . "/persistence/migrations/" . $file);
if (!$sql) {
die("Unable to read migration file: " . $file);
}
executeSqlScript($connection, $sql, $file);
$statement = $connection->prepare(<<<EOF
INSERT INTO `ua_migrations`
(`id`, `file`) VALUES
(?, ?)
EOF);
$statement->execute([$id, $file]);
try {
$connection->commit();
} catch (PDOException $e) {
// this might happen if the migration script contains a DDL statement
// -> ignore
}
}
return function(PDO $connection) {
ensureDbStructureForMigrations($connection);
$migrations = getMigrationsToApply($connection);
foreach ($migrations as $id => $file) {
applyMigration($connection, $id, $file);
}
};

View file