Completed
Push — master ( a79949...86c63e )
by Joao
12s queued 10s
created

Migration::isDatabaseVersioned()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
3
namespace ByJG\DbMigration;
4
5
use ByJG\AnyDataset\Db\DbDriverInterface;
6
use ByJG\DbMigration\Database\DatabaseInterface;
7
use ByJG\DbMigration\Exception\DatabaseDoesNotRegistered;
8
use ByJG\DbMigration\Exception\DatabaseIsIncompleteException;
9
use ByJG\DbMigration\Exception\InvalidMigrationFile;
10
use Psr\Http\Message\UriInterface;
11
12
class Migration
13
{
14
    const VERSION_STATUS_UNKNOWN = "unknown";
15
    const VERSION_STATUS_PARTIAL = "partial";
16
    const VERSION_STATUS_COMPLETE = "complete";
17
18
    /**
19
     * @var UriInterface
20
     */
21
    protected $uri;
22
23
    /**
24
     * @var string
25
     */
26
    protected $folder;
27
28
    /**
29
     * @var DbDriverInterface
30
     */
31
    protected $dbDriver;
32
33
    /**
34
     * @var DatabaseInterface
35
     */
36
    protected $dbCommand;
37
38
    /**
39
     * @var Callable
40
     */
41
    protected $callableProgress;
42
43
    /**
44
     * @var array
45
     */
46
    protected $databases = [];
47
    /**
48
     * @var string
49
     */
50
    private $migrationTable;
51
52
    /**
53
     * Migration constructor.
54
     *
55
     * @param UriInterface $uri
56
     * @param string $folder
57
     * @param bool $requiredBase Define if base.sql is required
58
     * @param string $migrationTable
59
     * @throws InvalidMigrationFile
60
     */
61
    public function __construct(UriInterface $uri, $folder, $requiredBase = true, $migrationTable = 'migration_version')
62
    {
63
        $this->uri = $uri;
64
        $this->folder = $folder;
65
        if ($requiredBase && !file_exists($this->folder . '/base.sql')) {
66
            throw new InvalidMigrationFile("Migration script '{$this->folder}/base.sql' not found");
67
        }
68
        $this->migrationTable = $migrationTable;
69
    }
70
71
    /**
72
     * @param $scheme
73
     * @param $className
74
     * @return $this
75
     */
76
    public function registerDatabase($scheme, $className)
77
    {
78
        $this->databases[$scheme] = $className;
79
        return $this;
80
    }
81
82
    /**
83
     * @return DbDriverInterface
84
     * @throws \ByJG\DbMigration\Exception\DatabaseDoesNotRegistered
85
     */
86
    public function getDbDriver()
87
    {
88
        return $this->getDbCommand()->getDbDriver();
89
    }
90
91
    /**
92
     * @return DatabaseInterface
93
     * @throws \ByJG\DbMigration\Exception\DatabaseDoesNotRegistered
94
     */
95
    public function getDbCommand()
96
    {
97
        if (is_null($this->dbCommand)) {
98
            $class = $this->getDatabaseClassName();
99
            $this->dbCommand = new $class($this->uri, $this->migrationTable);
100
        }
101
        return $this->dbCommand;
102
    }
103
104
    public function getMigrationTable()
105
    {
106
        return $this->migrationTable;
107
    }
108
109
    /**
110
     * @return mixed
111
     * @throws \ByJG\DbMigration\Exception\DatabaseDoesNotRegistered
112
     */
113
    protected function getDatabaseClassName()
114
    {
115
        if (isset($this->databases[$this->uri->getScheme()])) {
116
            return $this->databases[$this->uri->getScheme()];
117
        }
118
        throw new DatabaseDoesNotRegistered(
119
            'Scheme "' . $this->uri->getScheme() . '" does not found. Did you registered it?'
120
        );
121
    }
122
123
    /**
124
     * Get the full path and name of the "base.sql" script
125
     *
126
     * @return string
127
     */
128
    public function getBaseSql()
129
    {
130
        return $this->folder . "/base.sql";
131
    }
132
133
    /**
134
     * Get the full path script based on the version
135
     *
136
     * @param $version
137
     * @param $increment
138
     * @return string
139
     * @throws \ByJG\DbMigration\Exception\InvalidMigrationFile
140
     */
141
    public function getMigrationSql($version, $increment)
142
    {
143
        // I could use the GLOB_BRACE but it is not supported on ALPINE distros.
144
        // So, I have to call multiple times to simulate the braces.
145
146
        if (intval($version) != $version) {
147
            throw new \InvalidArgumentException("Version '$version' should be a integer number");
148
        }
149
        $version = intval($version);
150
151
        $filePattern = $this->folder
152
            . "/migrations"
153
            . "/" . ($increment < 0 ? "down" : "up")
154
            . "/*.sql";
155
156
        $result = array_filter(glob($filePattern), function ($file) use ($version) {
157
            return preg_match("/^0*$version(-dev)?\.sql$/", basename($file));
158
        });
159
160
        // Valid values are 0 or 1
161
        if (count($result) > 1) {
162
            throw new InvalidMigrationFile("You have two files with the same version number '$version'");
163
        }
164
165
        foreach ($result as $file) {
166
            if (intval(basename($file)) === $version) {
167
                return $file;
168
            }
169
        }
170
        return null;
171
    }
172
173
    /**
174
     * Create the database it it does not exists. Does not use this methos in a production environment
175
     *
176
     * @throws \ByJG\DbMigration\Exception\DatabaseDoesNotRegistered
177
     */
178
    public function prepareEnvironment()
179
    {
180
        $class = $this->getDatabaseClassName();
181
        $class::prepareEnvironment($this->uri);
182
    }
183
184
    /**
185
     * Restore the database using the "base.sql" script and run all migration scripts
186
     * Note: the database must exists. If dont exist run the method prepareEnvironment
187
     *
188
     * @param int $upVersion
189
     * @throws \ByJG\DbMigration\Exception\DatabaseDoesNotRegistered
190
     * @throws \ByJG\DbMigration\Exception\DatabaseIsIncompleteException
191
     * @throws \ByJG\DbMigration\Exception\DatabaseNotVersionedException
192
     * @throws \ByJG\DbMigration\Exception\InvalidMigrationFile
193
     * @throws \ByJG\DbMigration\Exception\OldVersionSchemaException
194
     */
195
    public function reset($upVersion = null)
196
    {
197
        if ($this->callableProgress) {
198
            call_user_func_array($this->callableProgress, ['reset', 0]);
199
        }
200
        $this->getDbCommand()->dropDatabase();
201
        $this->getDbCommand()->createDatabase();
202
        $this->getDbCommand()->createVersion();
203
204
        if (file_exists($this->getBaseSql())) {
205
            $this->getDbCommand()->executeSql(file_get_contents($this->getBaseSql()));
206
        }
207
208
        $this->getDbCommand()->setVersion(0, Migration::VERSION_STATUS_COMPLETE);
209
        $this->up($upVersion);
210
    }
211
212
    /**
213
     * @throws \ByJG\DbMigration\Exception\DatabaseDoesNotRegistered
214
     */
215
    public function createVersion()
216
    {
217
        $this->getDbCommand()->createVersion();
218
    }
219
220
    /**
221
     * @throws \ByJG\DbMigration\Exception\DatabaseDoesNotRegistered
222
     */
223
    public function updateTableVersion()
224
    {
225
        $this->getDbCommand()->updateVersionTable();
226
    }
227
228
    /**
229
     * Get the current database version
230
     *
231
     * @return string[] The current 'version' and 'status' as an associative array
232
     * @throws \ByJG\DbMigration\Exception\DatabaseDoesNotRegistered
233
     * @throws \ByJG\DbMigration\Exception\DatabaseNotVersionedException
234
     * @throws \ByJG\DbMigration\Exception\OldVersionSchemaException
235
     */
236
    public function getCurrentVersion()
237
    {
238
        return $this->getDbCommand()->getVersion();
239
    }
240
241
    /**
242
     * @param $currentVersion
243
     * @param $upVersion
244
     * @param $increment
245
     * @return bool
246
     */
247
    protected function canContinue($currentVersion, $upVersion, $increment)
248
    {
249
        $existsUpVersion = ($upVersion !== null);
250
        $compareVersion =
251
            intval($currentVersion) < intval($upVersion)
252
                ? -1
253
                : (intval($currentVersion) > intval($upVersion) ? 1 : 0);
254
255
        return !($existsUpVersion && ($compareVersion === intval($increment)));
256
    }
257
258
    /**
259
     * Method for execute the migration.
260
     *
261
     * @param int $upVersion
262
     * @param int $increment Can accept 1 for UP or -1 for down
263
     * @param bool $force
264
     * @throws \ByJG\DbMigration\Exception\DatabaseDoesNotRegistered
265
     * @throws \ByJG\DbMigration\Exception\DatabaseIsIncompleteException
266
     * @throws \ByJG\DbMigration\Exception\DatabaseNotVersionedException
267
     * @throws \ByJG\DbMigration\Exception\InvalidMigrationFile
268
     * @throws \ByJG\DbMigration\Exception\OldVersionSchemaException
269
     */
270
    protected function migrate($upVersion, $increment, $force)
271
    {
272
        $versionInfo = $this->getCurrentVersion();
273
        $currentVersion = intval($versionInfo['version']) + $increment;
274
275
        if (strpos($versionInfo['status'], Migration::VERSION_STATUS_PARTIAL) !== false && !$force) {
276
            throw new DatabaseIsIncompleteException('Database was not fully updated. Use --force for migrate.');
277
        }
278
279
        while ($this->canContinue($currentVersion, $upVersion, $increment)
280
            && file_exists($file = $this->getMigrationSql($currentVersion, $increment))
281
        ) {
282
            if ($this->callableProgress) {
283
                call_user_func_array($this->callableProgress, ['migrate', $currentVersion]);
284
            }
285
286
            $this->getDbCommand()->setVersion($currentVersion, Migration::VERSION_STATUS_PARTIAL . ' ' . ($increment>0 ? 'up' : 'down'));
287
            $this->getDbCommand()->executeSql(file_get_contents($file));
288
            $this->getDbCommand()->setVersion($currentVersion, Migration::VERSION_STATUS_COMPLETE);
289
            $currentVersion = $currentVersion + $increment;
290
        }
291
    }
292
293
    /**
294
     * Run all scripts to up the database version from current up to latest version or the specified version.
295
     *
296
     * @param int $upVersion
297
     * @param bool $force
298
     * @throws \ByJG\DbMigration\Exception\DatabaseDoesNotRegistered
299
     * @throws \ByJG\DbMigration\Exception\DatabaseIsIncompleteException
300
     * @throws \ByJG\DbMigration\Exception\DatabaseNotVersionedException
301
     * @throws \ByJG\DbMigration\Exception\InvalidMigrationFile
302
     * @throws \ByJG\DbMigration\Exception\OldVersionSchemaException
303
     */
304
    public function up($upVersion = null, $force = false)
305
    {
306
        $this->migrate($upVersion, 1, $force);
307
    }
308
309
    /**
310
     * Run all scripts to up or down the database version from current up to latest version or the specified version.
311
     *
312
     * @param int $upVersion
313
     * @param bool $force
314
     * @throws \ByJG\DbMigration\Exception\DatabaseDoesNotRegistered
315
     * @throws \ByJG\DbMigration\Exception\DatabaseIsIncompleteException
316
     * @throws \ByJG\DbMigration\Exception\DatabaseNotVersionedException
317
     * @throws \ByJG\DbMigration\Exception\InvalidMigrationFile
318
     * @throws \ByJG\DbMigration\Exception\OldVersionSchemaException
319
     */
320
    public function update($upVersion = null, $force = false)
321
    {
322
        $versionInfo = $this->getCurrentVersion();
323
        $version = intval($versionInfo['version']);
324
        $increment = 1;
325
        if ($upVersion !== null && $upVersion < $version) {
326
            $increment = -1;
327
        }
328
        $this->migrate($upVersion, $increment, $force);
329
    }
330
331
    /**
332
     * Run all scripts to down the database version from current version up to the specified version.
333
     *
334
     * @param int $upVersion
335
     * @param bool $force
336
     * @throws \ByJG\DbMigration\Exception\DatabaseDoesNotRegistered
337
     * @throws \ByJG\DbMigration\Exception\DatabaseIsIncompleteException
338
     * @throws \ByJG\DbMigration\Exception\DatabaseNotVersionedException
339
     * @throws \ByJG\DbMigration\Exception\InvalidMigrationFile
340
     * @throws \ByJG\DbMigration\Exception\OldVersionSchemaException
341
     */
342
    public function down($upVersion, $force = false)
343
    {
344
        $this->migrate($upVersion, -1, $force);
345
    }
346
347
    /**
348
     * @param callable $callable
349
     */
350
    public function addCallbackProgress(callable $callable)
351
    {
352
        $this->callableProgress = $callable;
353
    }
354
355
    public function isDatabaseVersioned()
356
    {
357
        return $this->getDbCommand()->isDatabaseVersioned();
358
    }
359
}
360