deploy
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
<?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 '';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user