PluginProbe
Metricool – Social media and site statistics / trunk
Metricool – Social media and site statistics vtrunk
2.1.0 2.0.2 2.0.1 2.0.0 1.27 trunk
metricool / app / Controllers / MigrationsController.php

MigrationsController.php in Metricool – Social media and site statistics trunk, at app/Controllers/MigrationsController.php

174 lines 5.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 declare(strict_types=1);
4
5 namespace Metricool\Controllers;
6
7 if (!defined('ABSPATH')) {
8 exit;
9 }
10
11 use Metricool\Interfaces\MigrationInterface;
12 use Metricool\Interfaces\ControllerInterface;
13 use Metricool\Support\Helpers\Storages\EnvironmentConfig;
14
15 class MigrationsController implements ControllerInterface
16 {
17 private EnvironmentConfig $env;
18 private ?string $toVersion = null;
19 private ?string $fromVersion = null;
20
21 public function __construct(EnvironmentConfig $env)
22 {
23 $this->env = $env;
24 }
25
26 public function register(): void
27 {
28 add_action('metricool_plugin_version_upgrade', [$this, 'runMigrations'], 10, 2);
29 }
30
31 /**
32 * Run the migrations that apply for the given version upgrade.
33 */
34 public function runMigrations(string $previousVersion, string $newVersion): void
35 {
36 $this->fromVersion = $previousVersion;
37 $this->toVersion = $newVersion;
38
39 $migrations = $this->getAllMigrations();
40
41 foreach ($migrations as $migration) {
42 $this->run($migration);
43 }
44
45 $this->afterMigrate();
46 }
47
48 /**
49 * Run a single migration. Silently skips the migration if it does not fit
50 * the version range.
51 */
52 private function run(MigrationInterface $migration): void
53 {
54 if ($this->shouldRunMigration($migration) === false) {
55 return;
56 }
57
58 if ($this->isUpgrading()) {
59 $migration->up();
60 }
61 }
62
63 /**
64 * Determine if a migration should run based on version comparison.
65 *
66 * When upgrading: run migration if version is between fromVersion and
67 * toVersion or equal to toVersion. Makes sure up() is run when upgrading to
68 * the exact version of the migration to apply all changes up to that
69 * version.
70 *
71 * When downgrading: the version is less than fromVersion, but greater than
72 * toVersion. Migrations cannot run in this case, because the current code
73 * does not know about the changes that were made in future versions.
74 *
75 * @return bool True if migration should run
76 * @throws \InvalidArgumentException When migration version is invalid
77 */
78 private function shouldRunMigration(MigrationInterface $migration): bool
79 {
80 if (version_compare($migration->version(), '0.0.1', '>=') === false) {
81 throw new \InvalidArgumentException('Migration version must be a valid version number string.');
82 }
83
84 if ($this->isUpgrading()) {
85 return version_compare($migration->version(), $this->fromVersion, '>')
86 && version_compare($migration->version(), $this->toVersion, '<=');
87 }
88
89 // We cannot revert migrations from the future. Can you see the future?
90 if ($this->isDowngrading()) {
91 return false;
92 }
93
94 return false;
95 }
96
97 /**
98 * Get all migration files from the migrations directory, sorted by version.
99 * @return array<int, MigrationInterface>
100 */
101 private function getAllMigrations(): array
102 {
103 $migrationsPath = $this->env->getString('plugin.migrations_path');
104 if (!is_dir($migrationsPath)) {
105 return [];
106 }
107
108 $files = glob($migrationsPath . '*.php');
109 if ($files === false) {
110 return [];
111 }
112
113 $migrations = [];
114 foreach ($files as $file) {
115 $migration = require $file;
116 $migrations[] = $migration;
117 }
118
119 // Sort migrations by version, lowest to highest
120 usort($migrations, function ($a, $b) {
121 return version_compare($a->version(), $b->version());
122 });
123
124 return $migrations;
125 }
126
127 /**
128 * Determine if the migration is a downgrade. A downgrade occurs when
129 * the toVersion is less than the fromVersion.
130 * @throws \RuntimeException When developer is doing something wrong
131 */
132 private function isDowngrading(): bool
133 {
134 if ($this->fromVersion === null || $this->toVersion === null) {
135 throw new \RuntimeException('From and To versions must be set before checking downgrade status.');
136 }
137
138 return version_compare($this->toVersion, $this->fromVersion, '<');
139 }
140
141 /**
142 * Determine if the migration is an upgrade. An upgrade occurs when
143 * the toVersion is greater than the fromVersion.
144 * @throws \RuntimeException When developer is doing something wrong
145 */
146 private function isUpgrading(): bool
147 {
148 if ($this->fromVersion === null || $this->toVersion === null) {
149 throw new \RuntimeException('From and To versions must be set before checking upgrade status.');
150 }
151
152 return version_compare($this->toVersion, $this->fromVersion, '>');
153 }
154
155 /**
156 * Cleanup internal state after migrations have run.
157 */
158 private function cleanup(): void
159 {
160 $this->fromVersion = null;
161 $this->toVersion = null;
162 }
163
164 /**
165 * Actions to perform after all migrations have run. This includes
166 * cleaning up internal state and firing an action hook.
167 */
168 private function afterMigrate(): void
169 {
170 $this->cleanup();
171 do_action('metricool_migrations_run');
172 }
173 }
174