Passed
Push — master ( 705452...770324 )
by Mathieu
35:33 queued 25:29
created

MigrationService::scanForMigrations()   A

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
        $this->registeredMigrations[$migration->getConfigName()][$migration->getName()] = $migration->getSQL();
27
    }
28
29
    public function scanForMigrations($path = null)
30
    {
31
        $qualifiedPath = $path;
32
        if ($path === null) {
33
            $qualifiedPath = app_path('migrations/');
34
        }
35
36
        $files = glob($qualifiedPath . '*.php');
37
38
        foreach ($files as $file) {
39
            $migrationClassName = str_replace('.php', '', basename($file));
40
            include($file);
41
            // Class is defined inside the file
42
            if (class_exists($migrationClassName)) {
43
                $migration = new $migrationClassName();
44
                if ($migration instanceof IMigration) {
45
                    $this->registerMigration($migration);
46
                }
47
            }
48
        }
49
    }
50
51
    public function initMigrationTable(string $configName): int
52
    {
53
        $migrationModel = new MigrationModel();
54
        $migrationModel->setDBConfig($configName);
55
        return $migrationModel->createMigrationTable();
56
    }
57
58
    public function listMigrations()
59
    {
60
        $db = Suricate::Database(true);
61
        $dbConfigs = $db->getConfigs();
62
        unset($db);
63
        $result = [];
64
65
        $this->scanForMigrations();
66
        $suricateServices = Suricate::listServices();
67
68
        foreach ($suricateServices as $service) {
69
            // Check all registered Suricate services if their
70
            // migration handler has migrations to register
71
            $serviceInstance = Suricate::$service();
72
            if ($serviceInstance instanceof Service) {
73
                $serviceInstance->registerMigrations();
74
            }
75
        }
76
77
        // Iterate through all databases configuration
78
        foreach (array_keys($dbConfigs) as $dbConfigName) {
79
            $result[$dbConfigName] = [];
80
81
            // Create migration table if needed
82
            $res = $this->initMigrationTable($dbConfigName);
83
            switch ($res) {
84
                case 0:
85
                    echo '[Migration] ✅ Migration table created successfully for config "' . $dbConfigName . '"' . "\n";
86
                    break;
87
                case 1:
88
                    echo '[Migration] ❌ Unsupported database type (config: "' . $dbConfigName . '")' . "\n";
89
                    break;
90
            }
91
92
            // Load all DB listed migration for config
93
            $migrations = MigrationModelList::loadAllWithConfig($dbConfigName);
94
            $alreadyMigrated = [];
95
96
            foreach ($migrations as $migration) {
97
                $alreadyMigrated[$migration->name] = true;
98
                $result[$dbConfigName][$migration->name] = $migration->date_added;
99
            }
100
101
            // 'ALL' config name for migrations that should me applied to all configs (eg: media-manager)
102
            $confChecks = ['ALL', $dbConfigName];
103
            foreach ($confChecks as $currentConfigName) {
104
                if (isset($this->registeredMigrations[$currentConfigName])) {
105
                    foreach (array_keys($this->registeredMigrations[$currentConfigName]) as $regMigrationName) {
106
                        if (isset($alreadyMigrated[$regMigrationName])) {
107
                            continue;
108
                        }
109
                        $result[$dbConfigName][$regMigrationName] = false;
110
                    }
111
                }
112
            }
113
            ksort($result[$dbConfigName]);
114
        }
115
116
        return $result;
117
    }
118
119
    public function doMigrations()
120
    {
121
        echo "[Migration] Starting migrations\n";
122
123
        $globalMigrations = $this->listMigrations();
124
        $migrationsToDo = [];
125
        foreach ($globalMigrations as $migrations) {
126
            foreach ($migrations as $migrationName => $migrationDate) {
127
                if ($migrationDate === false) {
128
                    $migrationsToDo[] = $migrationName;
129
                }
130
            }
131
        }
132
133
        if (count($migrationsToDo) === 0) {
134
            echo "[Migration] Nothing to migrate\n";
135
            return true;
136
        }
137
        foreach ($migrationsToDo as $migrationName) {
138
            echo "[Migration] Migration $migrationName:\n";
139
            $migration = new $migrationName();
140
            $sql = trim($migration->getSQL());
141
            if ($sql === '') {
142
                // Ignore
143
                continue;
144
            }
145
            $db = Suricate::Database(true);
146
            $db->setConfig($migration->getConfigName());
147
            try {
148
                $db->query($migration->getSQL());
149
            } catch (Exception $e) {
150
                echo "[Migration] ❌ Failed to execute migration: " . $e->getMessage() . "\n";
151
                continue;
152
            }
153
154
            echo "[Migration] ✅ migration OK\n";
155
            $migrationCheck = new MigrationModel();
156
            $migrationCheck->setDBConfig($migration->getConfigName());
157
            $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...
158
            $migrationCheck->save();
159
        }
160
    }
161
162
    public function createMigration(): string|bool
163
    {
164
        $migrationName = 'v' . date('Ymdhis');
165
166
        $template = <<<EOD
167
<?php
168
169
use Suricate\Interfaces\IMigration;
170
171
class {$migrationName} implements IMigration
172
{
173
    public function getName(): string
174
    {
175
        return __CLASS__;
176
    }
177
178
    public function getSQL(): string
179
    {
180
        return '';
181
    }
182
183
    public function getConfigName(): string
184
    {
185
        return 'default';
186
    }
187
}
188
EOD;
189
        $filename = app_path('migrations/' . $migrationName . '.php');
190
        $directory = pathinfo($filename, PATHINFO_DIRNAME);
191
        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

191
        if (!is_dir(/** @scrutinizer ignore-type */ $directory)) {
Loading history...
192
            $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

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