PluginProbe
Metricool – Social media and site statistics / 2.1.1
Metricool – Social media and site statistics v2.1.1
2.1.1 2.1.0 2.0.2 2.0.1 2.0.0 1.27 trunk
← All changes | app/Controllers/MigrationsController.php +173 -0 2.0.0 → 2.1.1 View file →
@@ -1,0 +1,173 @@
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 +}