Files
desktop/docs/Umsetzungsanweisung/Old-Nexus/src/App/ModuleMigrationService.php
Lars Gebhardt-Kusche 2cdd14c400
All checks were successful
Deploy / deploy-staging (push) Successful in 24s
Deploy / deploy-production (push) Has been skipped
deploy
2026-06-20 01:50:07 +02:00

173 lines
5.5 KiB
PHP

<?php
declare(strict_types=1);
namespace App;
final class ModuleMigrationService
{
public function __construct(
private \PDO $basePdo,
private ModuleManager $modules
) {}
public function status(array $module): array
{
$migrations = $this->discover($module);
$applied = $this->applied((string)$module['name']);
$pending = [];
$changed = [];
foreach ($migrations as $migration) {
$id = $migration['id'];
if (!isset($applied[$id])) {
$pending[] = $migration;
continue;
}
if (($applied[$id]['checksum'] ?? '') !== $migration['checksum']) {
$changed[] = $migration;
}
}
return [
'available' => count($migrations),
'applied' => count($applied),
'pending' => $pending,
'changed' => $changed,
];
}
public function applyPending(array $module): array
{
$status = $this->status($module);
$applied = [];
$moduleName = (string)$module['name'];
$context = new ModuleMigrationContext($this->basePdo, $this->modules, $module);
foreach ($status['pending'] as $migration) {
$this->basePdo->beginTransaction();
try {
$runner = require $migration['path'];
if (is_object($runner) && method_exists($runner, 'up')) {
$runner->up($context);
} elseif (is_callable($runner)) {
$runner($context);
} else {
throw new \RuntimeException('Migration does not return a callable or object with up().');
}
$this->record($moduleName, $migration);
$this->basePdo->commit();
$applied[] = $migration;
} catch (\Throwable $e) {
if ($this->basePdo->inTransaction()) {
$this->basePdo->rollBack();
}
throw new \RuntimeException(
'Migration fehlgeschlagen: ' . $moduleName . '/' . $migration['id'] . ' - ' . $e->getMessage(),
0,
$e
);
}
}
if ($applied !== []) {
$this->recordModuleVersion($moduleName, (string)($module['version'] ?? ''));
}
return $applied;
}
private function discover(array $module): array
{
$path = (string)($module['path'] ?? '') . '/migrations';
if (!is_dir($path)) {
return [];
}
$items = [];
foreach (glob($path . '/*.php') ?: [] as $file) {
$id = basename($file, '.php');
$items[] = [
'id' => $id,
'version' => $this->versionFromId($id),
'path' => $file,
'checksum' => hash_file('sha256', $file) ?: '',
];
}
usort($items, static fn(array $a, array $b): int => strnatcmp($a['id'], $b['id']));
return $items;
}
private function applied(string $moduleName): array
{
$stmt = $this->basePdo->prepare(
"SELECT migration, version, checksum, applied_at
FROM nexus_module_migrations
WHERE module_name = :module
ORDER BY migration ASC"
);
$stmt->execute(['module' => $moduleName]);
$items = [];
foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
$items[(string)$row['migration']] = $row;
}
return $items;
}
private function record(string $moduleName, array $migration): void
{
$driver = (string)$this->basePdo->getAttribute(\PDO::ATTR_DRIVER_NAME);
if ($driver === 'pgsql' || $driver === 'sqlite') {
$sql = "INSERT INTO nexus_module_migrations (module_name, migration, version, checksum, applied_at)
VALUES (:module, :migration, :version, :checksum, CURRENT_TIMESTAMP)
ON CONFLICT(module_name, migration) DO UPDATE SET
version = excluded.version,
checksum = excluded.checksum,
applied_at = CURRENT_TIMESTAMP";
} else {
$sql = "INSERT INTO nexus_module_migrations (module_name, migration, version, checksum, applied_at)
VALUES (:module, :migration, :version, :checksum, CURRENT_TIMESTAMP)
ON DUPLICATE KEY UPDATE
version = VALUES(version),
checksum = VALUES(checksum),
applied_at = CURRENT_TIMESTAMP";
}
$stmt = $this->basePdo->prepare($sql);
$stmt->execute([
'module' => $moduleName,
'migration' => $migration['id'],
'version' => $migration['version'],
'checksum' => $migration['checksum'],
]);
}
private function recordModuleVersion(string $moduleName, string $version): void
{
if ($version === '') {
return;
}
$stmt = $this->basePdo->prepare(
"UPDATE nexus_modules
SET version = :version,
updated_at = CURRENT_TIMESTAMP
WHERE name = :module"
);
$stmt->execute([
'module' => $moduleName,
'version' => $version,
]);
}
private function versionFromId(string $id): string
{
if (preg_match('/(?:^|_)(\d+\.\d+\.\d+)(?:_|$)/', $id, $m)) {
return $m[1];
}
return '';
}
}