From 81c9e5cfc50788df7ca62506329a830bd126577d Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Wed, 2 Aug 2023 18:25:08 +0200 Subject: [PATCH] Introduce `MigrationManager` class --- .../Icinga/Application/MigrationManager.php | 246 ++++++++++++++++++ 1 file changed, 246 insertions(+) create mode 100644 library/Icinga/Application/MigrationManager.php diff --git a/library/Icinga/Application/MigrationManager.php b/library/Icinga/Application/MigrationManager.php new file mode 100644 index 000000000..b1065261e --- /dev/null +++ b/library/Icinga/Application/MigrationManager.php @@ -0,0 +1,246 @@ + All pending migration hooks */ + protected $pendingMigrations; + + /** @var MigrationManager */ + private static $instance; + + private function __construct() + { + } + + /** + * Get the instance of this manager + * + * @return $this + */ + public static function instance(): self + { + if (self::$instance === null) { + self::$instance = new self(); + } + + return self::$instance; + } + + /** + * Get all pending migrations + * + * @return array + */ + public function getPendingMigrations(): array + { + if ($this->pendingMigrations === null) { + $this->load(); + } + + return $this->pendingMigrations; + } + + /** + * Get whether there are any pending migrations + * + * @return bool + */ + public function hasPendingMigrations(): bool + { + return $this->count() > 0; + } + + public function hasMigrations(string $module): bool + { + if (! $this->hasPendingMigrations()) { + return false; + } + + return isset($this->getPendingMigrations()[$module]); + } + + /** + * Get pending migration matching the given module name + * + * @param string $module + * + * @return MigrationHook + * + * @throws NotFoundError When there are no pending PHP code migrations matching the given module name + */ + public function getMigration(string $module): MigrationHook + { + if (! $this->hasMigrations($module)) { + throw new NotFoundError('There are no pending migrations matching the given name: %s', $module); + } + + return $this->getPendingMigrations()[$module]; + } + + /** + * Get the number of all pending migrations + * + * @return int + */ + public function count(): int + { + return count($this->getPendingMigrations()); + } + + /** + * Apply all pending migrations matching the given migration module name + * + * @param string $module + * + * @return bool + */ + public function applyByName(string $module): bool + { + $migration = $this->getMigration($module); + if ($migration->isModule() && $this->hasMigrations(MigrationHook::DEFAULT_MODULE)) { + return false; + } + + return $this->apply($migration); + } + + /** + * Apply the given migration hook + * + * @param MigrationHook $hook + * + * @return bool + */ + public function apply(MigrationHook $hook): bool + { + if ($hook->isModule() && $this->hasMigrations(MigrationHook::DEFAULT_MODULE)) { + Logger::error( + 'Please apply the Icinga Web pending migration(s) first or apply all the migrations instead' + ); + + return false; + } + + if ($hook->run()) { + unset($this->pendingMigrations[$hook->getModuleName()]); + + Logger::info('Applied pending %s migrations successfully', $hook->getName()); + + return true; + } + + return false; + } + + /** + * Apply all pending modules/framework migrations + * + * @return bool + */ + public function applyAll(): bool + { + $default = MigrationHook::DEFAULT_MODULE; + if ($this->hasMigrations($default)) { + $migration = $this->getMigration($default); + if (! $this->apply($migration)) { + return false; + } + } + + $succeeded = true; + foreach ($this->getPendingMigrations() as $migration) { + if (! $this->apply($migration) && $succeeded) { + $succeeded = false; + } + } + + return $succeeded; + } + + /** + * Yield module and framework pending migrations separately + * + * @param bool $modules + * + * @return Generator + */ + public function yieldMigrations(bool $modules = false): Generator + { + foreach ($this->getPendingMigrations() as $migration) { + if ($modules === $migration->isModule()) { + yield $migration; + } + } + } + + protected function load(): void + { + $this->pendingMigrations = []; + + /** @var MigrationHook $hook */ + foreach (Hook::all('migration') as $hook) { + if (empty($hook->getMigrations())) { + continue; + } + + $this->pendingMigrations[$hook->getModuleName()] = $hook; + } + + ksort($this->pendingMigrations); + } + + /** + * Get all pending migrations as an array + * + * @return array + */ + public function toArray(): array + { + $framework = []; + $serialize = function (MigrationHook $hook): array { + $serialized = [ + 'name' => $hook->getName(), + 'module' => $hook->getModuleName(), + 'isModule' => $hook->isModule(), + 'migrated_version' => $hook->getVersion(), + 'migrations' => [] + ]; + + foreach ($hook->getMigrations() as $migration) { + $serialized['migrations'][$migration->getVersion()] = [ + 'path' => $migration->getScriptPath(), + 'error' => $migration->getLastState() + ]; + } + + return $serialized; + }; + + foreach ($this->yieldMigrations() as $migration) { + $framework[] = $serialize($migration); + } + + $modules = []; + foreach ($this->yieldMigrations(true) as $migration) { + $modules[] = $serialize($migration); + } + + return ['System' => $framework, 'Modules' => $modules]; + } +}