Completed
Push — master ( 824ab5...b7a7bb )
by Arman
16s queued 13s
created

MigrationManager::prepareUpMigrations()   A

Complexity

Conditions 6
Paths 5

Size

Total Lines 20
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 10
c 0
b 0
f 0
nc 5
nop 1
dl 0
loc 20
rs 9.2222
1
<?php
2
3
/**
4
 * Quantum PHP Framework
5
 *
6
 * An open source software development framework for PHP
7
 *
8
 * @package Quantum
9
 * @author Arman Ag. <[email protected]>
10
 * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org)
11
 * @link http://quantum.softberg.org/
12
 * @since 2.7.0
13
 */
14
15
namespace Quantum\Migration;
16
17
use Quantum\Exceptions\MigrationException;
18
use Quantum\Libraries\Database\Database;
19
use Quantum\Libraries\Storage\FileSystem;
20
use Quantum\Factory\TableFactory;
21
22
/**
23
 * Class MigrationManager
24
 * @package Quantum\Migration
25
 */
26
class MigrationManager
27
{
28
29
    /**
30
     * Migration direction for upgrade
31
     */
32
    const UPGRADE = 'up';
33
34
    /**
35
     * Migration direction for downgrade
36
     */
37
    const DOWNGRADE = 'down';
38
39
    /**
40
     * @var array
41
     */
42
    private $actions = ['create', 'alter', 'rename', 'drop'];
43
44
    /**
45
     * @var array
46
     */
47
    private $drivers = ['mysql', 'pgsql', 'sqlite'];
48
49
    /**
50
     * @var array
51
     */
52
    private $migrations = [];
53
54
    /**
55
     * @var TableFactory
56
     */
57
    private $tableFactory;
58
59
    /**
60
     * @var string
61
     */
62
    private $migrationFolder;
63
64
    /**
65
     * @var FileSystem
66
     */
67
    private $fs;
68
69
    /**
70
     * @var Database
71
     */
72
    private $db;
73
74
    /**
75
     * MigrationManager constructor.
76
     */
77
    public function __construct()
78
    {
79
        $this->fs = new FileSystem();
80
81
        $this->db = Database::getInstance();
82
83
        $this->tableFactory = new TableFactory();
84
85
        $this->migrationFolder = base_dir() . DS . 'migrations';
86
    }
87
88
    /**
89
     * Generates new migration file
90
     * @param string $table
91
     * @param string $action
92
     * @return string
93
     * @throws Quantum\Exceptions\MigrationException
94
     */
95
    public function generateMigration(string $table, string $action)
96
    {
97
        if (!in_array($action, $this->actions)) {
98
            throw MigrationException::unsupportedAction($action);
99
        }
100
101
        $migrationName = $action . '_table_' . strtolower($table) . '_' . time();
102
103
        $migrationTemplate = MigrationTemplate::{$action}($migrationName, strtolower($table));
104
105
        $this->fs->put($this->migrationFolder . DS . $migrationName . '.php', $migrationTemplate);
106
107
        return $migrationName;
108
    }
109
110
    /**
111
     * Applies migrations
112
     * @param string $direction
113
     * @param int|null $step
114
     * @return int|null
115
     * @throws Quantum\Exceptions\MigrationException
116
     */
117
    public function applyMigrations(string $direction, ?int $step = null): ?int
118
    {
119
        $databaseDriver = $this->db->getConfigs()['driver'];
120
121
        if (!in_array($databaseDriver, $this->drivers)) {
122
            throw MigrationException::unsupportedDriver($databaseDriver);
123
        }
124
125
        switch ($direction) {
126
            case self::UPGRADE:
127
                $migrated = $this->upgrade($step);
128
                break;
129
            case self::DOWNGRADE:
130
                $migrated = $this->downgrade($step);
131
                break;
132
            default:
133
                throw MigrationException::wrongDirection();
134
        }
135
136
        return $migrated;
137
    }
138
139
    /**
140
     * Runs up migrations
141
     * @param int|null $step
142
     * @return int
143
     * @throws Quantum\Exceptions\MigrationException
144
     */
145
    private function upgrade(?int $step = null): int
146
    {
147
        if (!$this->tableFactory->checkTableExists(MigrationTable::TABLE)) {
148
            $migrationTable = new MigrationTable();
149
            $migrationTable->up($this->tableFactory);
150
        }
151
152
        $this->prepareUpMigrations($step);
153
154
        if (empty($this->migrations)) {
155
            throw MigrationException::nothingToMigrate();
156
        }
157
158
        $migratedEntries = [];
159
160
        foreach ($this->migrations as $migrationFile) {
161
            $this->fs->require($migrationFile);
162
163
            $migrationClassName = pathinfo($migrationFile, PATHINFO_FILENAME);
164
165
            $migration = new $migrationClassName();
166
167
            $migration->up($this->tableFactory);
168
169
            array_push($migratedEntries, $migrationClassName);
170
        }
171
172
        $this->addMigratedEntreis($migratedEntries);
173
174
        return count($migratedEntries);
175
    }
176
177
    /**
178
     * Runs down migrations
179
     * @param int|null $step
180
     * @return int
181
     * @throws Quantum\Exceptions\MigrationException
182
     */
183
    private function downgrade(?int $step): int
184
    {
185
        $this->prepareDownMigrations($step);
186
187
        if (empty($this->migrations)) {
188
            throw MigrationException::nothingToMigrate();
189
        }
190
191
        $migratedEntries = [];
192
193
        foreach ($this->migrations as $migrationFile) {
194
            $this->fs->require($migrationFile);
195
196
            $migrationClassName = pathinfo($migrationFile, PATHINFO_FILENAME);
197
198
            $migration = new $migrationClassName();
199
200
            $migration->down($this->tableFactory);
201
202
            array_push($migratedEntries, $migrationClassName);
203
        }
204
205
        $this->removeMigratedEntries($migratedEntries);
206
207
        return count($migratedEntries);
208
    }
209
210
    /**
211
     * Prepares up migrations
212
     * @param int|null $step
213
     * @throws Quantum\Exceptions\MigrationException
214
     */
215
    private function prepareUpMigrations(?int $step = null)
216
    {
217
        $migratedEntries = $this->getMigaratedEntries();
218
        $migrationFiles = $this->getMigrationFiles();
219
220
        if (empty($migratedEntries) && empty($migrationFiles)) {
221
            throw MigrationException::nothingToMigrate();
222
        }
223
224
        foreach ($migrationFiles as $timestamp => $migrationFile) {
225
            foreach ($migratedEntries as $migratedEntry) {
226
                if (pathinfo($migrationFile, PATHINFO_FILENAME) == $migratedEntry['migration']) {
227
                    continue 2;
228
                }
229
            }
230
231
            $this->migrations[$timestamp] = $migrationFile;
232
        }
233
234
        ksort($this->migrations);
235
    }
236
237
    /**
238
     * Prepares down migrations
239
     * @param int|null $step
240
     * @throws Quantum\Exceptions\MigrationException
241
     */
242
    private function prepareDownMigrations(?int $step = null)
243
    {
244
        $migratedEntries = $this->getMigaratedEntries();
245
246
        if (empty($migratedEntries)) {
247
            throw MigrationException::nothingToMigrate();
248
        }
249
250
        foreach ($migratedEntries as $migratedEntry) {
251
            $exploded = explode('_', $migratedEntry['migration']);
252
            $this->migrations[array_pop($exploded)] = $this->migrationFolder . DS . $migratedEntry['migration'] . '.php';
253
        }
254
255
        if (!is_null($step)) {
256
            $this->migrations = array_slice($this->migrations, count($this->migrations) - $step, $step, true);
257
        }
258
259
        krsort($this->migrations);
260
    }
261
262
    /**
263
     * Gets migration files
264
     * @return array
265
     */
266
    private function getMigrationFiles(): array
267
    {
268
        $migrationsFiles = $this->fs->glob($this->migrationFolder . DS . '*.php');
269
270
        $migrations = [];
271
272
        if (!empty($migrationsFiles)) {
273
            foreach ($migrationsFiles as $migration) {
274
                $exploded = explode('_', pathinfo($migration, PATHINFO_FILENAME));
0 ignored issues
show
Bug introduced by
It seems like pathinfo($migration, Qua...tion\PATHINFO_FILENAME) can also be of type array; however, parameter $string of explode() 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

274
                $exploded = explode('_', /** @scrutinizer ignore-type */ pathinfo($migration, PATHINFO_FILENAME));
Loading history...
275
                $migrations[array_pop($exploded)] = $migration;
276
            }
277
        }
278
279
        return $migrations;
280
    }
281
282
    /**
283
     * Gets migrated entries from migrations table
284
     * @return array
285
     */
286
    private function getMigaratedEntries(): array
287
    {
288
        return Database::query("SELECT * FROM " . MigrationTable::TABLE);
289
    }
290
291
    /**
292
     * Adds migrated entries to migrations table
293
     * @param array $entries
294
     */
295
    private function addMigratedEntreis(array $entries)
296
    {
297
        foreach ($entries as $entry) {
298
            Database::execute('INSERT INTO ' . MigrationTable::TABLE . '(migration) VALUES(:migration)', ['migration' => $entry]);
299
        }
300
    }
301
302
    /**
303
     * Removes migrated entries from migrations table
304
     * @param array $entries
305
     */
306
    private function removeMigratedEntries(array $entries)
307
    {
308
        foreach ($entries as $entry) {
309
            Database::execute('DELETE FROM ' . MigrationTable::TABLE . ' WHERE migration=:migration', ['migration' => $entry]);
310
        }
311
    }
312
313
}
314