MigrationService::scanForMigrations()   A
last analyzed

Complexity

Conditions 5
Paths 8

Size

Total Lines 17
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 5
eloc 11
c 2
b 0
f 0
nc 8
nop 1
dl 0
loc 17
rs 9.6111
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Suricate\Migrations;
6
7
use Exception;
8
use Suricate\Interfaces\IMigration;
9
use Suricate\Service;
10
use Suricate\Suricate;
11
12
/**
13
 * DB Migration extension for Suricate
14
 *
15
 * @package Suricate
16
 * @author  Mathieu LESNIAK <[email protected]>
17
 *
18
 */
19
20
class MigrationService extends Service
21
{
22
    protected array $registeredMigrations = [];
23
24
    public function registerMigration(IMigration $migration)
25
    {
26
        $configName = $migration->getConfigName() === '' ? 'default' : $migration->getConfigName();
27
        $this->registeredMigrations[$configName][$migration->getName()] = $migration->getSQL();
28
    }
29
30
    public function scanForMigrations($path = null)
31
    {
32
        $qualifiedPath = $path;
33
        if ($path === null) {
34
            $qualifiedPath = app_path('migrations/');
35
        }
36
37
        $files = glob($qualifiedPath . '*.php');
38
39
        foreach ($files as $file) {
40
            $migrationClassName = str_replace('.php', '', basename($file));
41
            include($file);
42
            // Class is defined inside the file
43
            if (class_exists($migrationClassName)) {
44
                $migration = new $migrationClassName();
45
                if ($migration instanceof IMigration) {
46
                    $this->registerMigration($migration);
47
                }
48
            }
49
        }
50
    }
51
52
    public function initMigrationTable(string $configName): int
53
    {
54
        $migrationModel = new MigrationModel();
55
        $migrationModel->setDBConfig($configName);
56
        return $migrationModel->createMigrationTable();
57
    }
58
59
    public function listMigrations()
60
    {
61
        $db = Suricate::Database(true);
62
        $dbConfigs = $db->getConfigs();
63
        unset($db);
64
        $result = [];
65
66
        $this->scanForMigrations();
67
        $suricateServices = Suricate::listServices();
68
69
        foreach ($suricateServices as $service) {
70
            // Check all registered Suricate services if their
71
            // migration handler has migrations to register
72
            $serviceInstance = Suricate::$service();
73
            if ($serviceInstance instanceof Service) {
74
                $serviceInstance->registerMigrations();
75
            }
76
        }
77
78
        // Iterate through all databases configuration
79
        foreach (array_keys($dbConfigs) as $dbConfigName) {
80
            $result[$dbConfigName] = [];
81
82
            // Create migration table if needed
83
            $res = $this->initMigrationTable($dbConfigName);
84
            switch ($res) {
85
                case 0:
86
                    echo '[Migration] ✅ Migration table created successfully for config "' . $dbConfigName . '"' . "\n";
87
                    break;
88
                case 1:
89
                    echo '[Migration] ❌ Unsupported database type (config: "' . $dbConfigName . '")' . "\n";
90
                    break;
91
            }
92
93
            // Load all DB listed migration for config
94
            $migrations = MigrationModelList::loadAllWithConfig($dbConfigName);
95
            $alreadyMigrated = [];
96
97
            foreach ($migrations as $migration) {
98
                $alreadyMigrated[$migration->name] = true;
99
                $result[$dbConfigName][$migration->name] = $migration->date_added;
100
            }
101
102
            // 'ALL' config name for migrations that should me applied to all configs (eg: media-manager)
103
            $confChecks = ['ALL', $dbConfigName];
104
            foreach ($confChecks as $currentConfigName) {
105
                if (isset($this->registeredMigrations[$currentConfigName])) {
106
                    foreach (array_keys($this->registeredMigrations[$currentConfigName]) as $regMigrationName) {
107
                        if (isset($alreadyMigrated[$regMigrationName])) {
108
                            continue;
109
                        }
110
                        $result[$dbConfigName][$regMigrationName] = false;
111
                    }
112
                }
113
            }
114
            ksort($result[$dbConfigName]);
115
        }
116
117
        return $result;
118
    }
119
120
    public function doMigrations()
121
    {
122
        echo "[Migration] Starting migrations\n";
123
124
        $globalMigrations = $this->listMigrations();
125
        $migrationsToDo = [];
126
        foreach ($globalMigrations as $migrations) {
127
            foreach ($migrations as $migrationName => $migrationDate) {
128
                if ($migrationDate === false) {
129
                    $migrationsToDo[] = $migrationName;
130
                }
131
            }
132
        }
133
134
        if (count($migrationsToDo) === 0) {
135
            echo "[Migration] Nothing to migrate\n";
136
            return true;
137
        }
138
        foreach ($migrationsToDo as $migrationName) {
139
            echo "[Migration] Migration $migrationName:\n";
140
            $migration = new $migrationName();
141
            $sql = trim($migration->getSQL());
142
            if ($sql === '') {
143
                // Ignore
144
                continue;
145
            }
146
            $db = Suricate::Database(true);
147
            $db->setConfig($migration->getConfigName());
148
            try {
149
                $db->query($migration->getSQL());
150
            } catch (Exception $e) {
151
                echo "[Migration] ❌ Failed to execute migration: " . $e->getMessage() . "\n";
152
                continue;
153
            }
154
155
            echo "[Migration] ✅ migration OK\n";
156
            $migrationCheck = new MigrationModel();
157
            $migrationCheck->setDBConfig($migration->getConfigName());
158
            $migrationCheck->name = $migration->getName();
0 ignored issues
show
Bug Best Practice introduced by
The property name does not exist on Suricate\Migrations\MigrationModel. Since you implemented __set, consider adding a @property annotation.
Loading history...
159
            $migrationCheck->save();
160
        }
161
    }
162
163
    public function createMigration(): string|bool
164
    {
165
        $migrationName = 'v' . date('Ymdhis');
166
167
        $template = <<<EOD
168
<?php
169
170
use Suricate\Interfaces\IMigration;
171
172
class {$migrationName} implements IMigration
173
{
174
    public function getName(): string
175
    {
176
        return __CLASS__;
177
    }
178
179
    public function getSQL(): string
180
    {
181
        return '';
182
    }
183
184
    public function getConfigName(): string
185
    {
186
        return 'default';
187
    }
188
}
189
EOD;
190
        $filename = app_path('migrations/' . $migrationName . '.php');
191
        $directory = pathinfo($filename, PATHINFO_DIRNAME);
192
        if (!is_dir($directory)) {
0 ignored issues
show
Bug introduced by
It seems like $directory can also be of type array; however, parameter $filename of is_dir() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

192
        if (!is_dir(/** @scrutinizer ignore-type */ $directory)) {
Loading history...
193
            $ret = mkdir($directory, 0755, true);
0 ignored issues
show
Bug introduced by
It seems like $directory can also be of type array; however, parameter $directory of mkdir() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

193
            $ret = mkdir(/** @scrutinizer ignore-type */ $directory, 0755, true);
Loading history...
194
            if (!$ret) {
195
                return false;
196
            }
197
        }
198
        $fp = fopen($filename, 'w');
199
        if ($fp === false) {
200
            return false;
201
        }
202
        $ret = fputs($fp, $template);
203
        fclose($fp);
204
        if ($ret !== false) {
205
            return $migrationName;
206
        }
207
        return false;
208
    }
209
}
210