Passed
Push — master ( a506e2...902f0a )
by Joao
05:33 queued 11s
created

Migration   B

Complexity

Total Complexity 45

Size/Duplication

Total Lines 383
Duplicated Lines 0 %

Importance

Changes 19
Bugs 3 Features 0
Metric Value
wmc 45
eloc 102
c 19
b 3
f 0
dl 0
loc 383
rs 8.8

21 Methods

Rating   Name   Duplication   Size   Complexity  
A down() 0 3 1
A __construct() 0 8 3
B migrate() 0 25 7
A isDatabaseVersioned() 0 3 1
A getDatabaseClassName() 0 7 2
A createVersion() 0 3 1
A registerDatabase() 0 4 1
A canContinue() 0 9 4
A updateTableVersion() 0 3 1
A getDbCommand() 0 7 2
A getMigrationSql() 0 30 6
A update() 0 9 3
A up() 0 3 1
A getMigrationTable() 0 3 1
A addCallbackProgress() 0 3 1
A reset() 0 17 3
A getDbDriver() 0 3 1
A getCurrentVersion() 0 3 1
A getFileContent() 0 23 3
A getBaseSql() 0 3 1
A prepareEnvironment() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like Migration often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Migration, and based on these observations, apply Extract Interface, too.

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
     * Get the file contents and metainfo
175
     * @param $file
176
     * @return array
177
     */
178
    public function getFileContent($file)
179
    {
180
        $data = [
181
            "file" => $file,
182
            "description" => "no description provided. Pro tip: use `-- @description:` to define one.",
183
            "exists" => false,
184
            "checksum" => null,
185
            "content" => null,
186
        ];
187
        if (!file_exists($file)) {
188
            return $data;
189
        }
190
191
        $data["content"] = file_get_contents($file);
192
193
        if (preg_match("/--\s*@description:\s*(?<name>.*)/", $data["content"], $description)) {
194
            $data["description"] = $description["name"];
195
        }
196
197
        $data["exists"] = true;
198
        $data["checksum"] = sha1($data["content"]);
199
200
        return $data;
201
    }
202
203
    /**
204
     * Create the database it it does not exists. Does not use this methos in a production environment
205
     *
206
     * @throws \ByJG\DbMigration\Exception\DatabaseDoesNotRegistered
207
     */
208
    public function prepareEnvironment()
209
    {
210
        $class = $this->getDatabaseClassName();
211
        $class::prepareEnvironment($this->uri);
212
    }
213
214
    /**
215
     * Restore the database using the "base.sql" script and run all migration scripts
216
     * Note: the database must exists. If dont exist run the method prepareEnvironment
217
     *
218
     * @param int $upVersion
219
     * @throws \ByJG\DbMigration\Exception\DatabaseDoesNotRegistered
220
     * @throws \ByJG\DbMigration\Exception\DatabaseIsIncompleteException
221
     * @throws \ByJG\DbMigration\Exception\DatabaseNotVersionedException
222
     * @throws \ByJG\DbMigration\Exception\InvalidMigrationFile
223
     * @throws \ByJG\DbMigration\Exception\OldVersionSchemaException
224
     */
225
    public function reset($upVersion = null)
226
    {
227
        $fileInfo = $this->getFileContent($this->getBaseSql());
228
229
        if ($this->callableProgress) {
230
            call_user_func_array($this->callableProgress, ['reset', 0, $fileInfo]);
231
        }
232
        $this->getDbCommand()->dropDatabase();
233
        $this->getDbCommand()->createDatabase();
234
        $this->getDbCommand()->createVersion();
235
236
        if ($fileInfo["exists"]) {
237
            $this->getDbCommand()->executeSql($fileInfo["content"]);
238
        }
239
240
        $this->getDbCommand()->setVersion(0, Migration::VERSION_STATUS_COMPLETE);
241
        $this->up($upVersion);
242
    }
243
244
    /**
245
     * @throws \ByJG\DbMigration\Exception\DatabaseDoesNotRegistered
246
     */
247
    public function createVersion()
248
    {
249
        $this->getDbCommand()->createVersion();
250
    }
251
252
    /**
253
     * @throws \ByJG\DbMigration\Exception\DatabaseDoesNotRegistered
254
     */
255
    public function updateTableVersion()
256
    {
257
        $this->getDbCommand()->updateVersionTable();
258
    }
259
260
    /**
261
     * Get the current database version
262
     *
263
     * @return string[] The current 'version' and 'status' as an associative array
264
     * @throws \ByJG\DbMigration\Exception\DatabaseDoesNotRegistered
265
     * @throws \ByJG\DbMigration\Exception\DatabaseNotVersionedException
266
     * @throws \ByJG\DbMigration\Exception\OldVersionSchemaException
267
     */
268
    public function getCurrentVersion()
269
    {
270
        return $this->getDbCommand()->getVersion();
271
    }
272
273
    /**
274
     * @param $currentVersion
275
     * @param $upVersion
276
     * @param $increment
277
     * @return bool
278
     */
279
    protected function canContinue($currentVersion, $upVersion, $increment)
280
    {
281
        $existsUpVersion = ($upVersion !== null);
282
        $compareVersion =
283
            intval($currentVersion) < intval($upVersion)
284
                ? -1
285
                : (intval($currentVersion) > intval($upVersion) ? 1 : 0);
286
287
        return !($existsUpVersion && ($compareVersion === intval($increment)));
288
    }
289
290
    /**
291
     * Method for execute the migration.
292
     *
293
     * @param int $upVersion
294
     * @param int $increment Can accept 1 for UP or -1 for down
295
     * @param bool $force
296
     * @throws \ByJG\DbMigration\Exception\DatabaseDoesNotRegistered
297
     * @throws \ByJG\DbMigration\Exception\DatabaseIsIncompleteException
298
     * @throws \ByJG\DbMigration\Exception\DatabaseNotVersionedException
299
     * @throws \ByJG\DbMigration\Exception\InvalidMigrationFile
300
     * @throws \ByJG\DbMigration\Exception\OldVersionSchemaException
301
     */
302
    protected function migrate($upVersion, $increment, $force)
303
    {
304
        $versionInfo = $this->getCurrentVersion();
305
        $currentVersion = intval($versionInfo['version']) + $increment;
306
307
        if (strpos($versionInfo['status'], Migration::VERSION_STATUS_PARTIAL) !== false && !$force) {
308
            throw new DatabaseIsIncompleteException('Database was not fully updated. Use --force for migrate.');
309
        }
310
311
        while ($this->canContinue($currentVersion, $upVersion, $increment)
312
        ) {
313
            $fileInfo = $this->getFileContent($this->getMigrationSql($currentVersion, $increment));
314
315
            if (!$fileInfo["exists"]) {
316
                break;
317
            }
318
319
            if ($this->callableProgress) {
320
                call_user_func_array($this->callableProgress, ['migrate', $currentVersion, $fileInfo]);
321
            }
322
323
            $this->getDbCommand()->setVersion($currentVersion, Migration::VERSION_STATUS_PARTIAL . ' ' . ($increment>0 ? 'up' : 'down'));
324
            $this->getDbCommand()->executeSql($fileInfo["content"]);
325
            $this->getDbCommand()->setVersion($currentVersion, Migration::VERSION_STATUS_COMPLETE);
326
            $currentVersion = $currentVersion + $increment;
327
        }
328
    }
329
330
    /**
331
     * Run all scripts to up the database version from current up to latest version or the specified version.
332
     *
333
     * @param int $upVersion
334
     * @param bool $force
335
     * @throws \ByJG\DbMigration\Exception\DatabaseDoesNotRegistered
336
     * @throws \ByJG\DbMigration\Exception\DatabaseIsIncompleteException
337
     * @throws \ByJG\DbMigration\Exception\DatabaseNotVersionedException
338
     * @throws \ByJG\DbMigration\Exception\InvalidMigrationFile
339
     * @throws \ByJG\DbMigration\Exception\OldVersionSchemaException
340
     */
341
    public function up($upVersion = null, $force = false)
342
    {
343
        $this->migrate($upVersion, 1, $force);
344
    }
345
346
    /**
347
     * Run all scripts to up or down the database version from current up to latest version or the specified version.
348
     *
349
     * @param int $upVersion
350
     * @param bool $force
351
     * @throws \ByJG\DbMigration\Exception\DatabaseDoesNotRegistered
352
     * @throws \ByJG\DbMigration\Exception\DatabaseIsIncompleteException
353
     * @throws \ByJG\DbMigration\Exception\DatabaseNotVersionedException
354
     * @throws \ByJG\DbMigration\Exception\InvalidMigrationFile
355
     * @throws \ByJG\DbMigration\Exception\OldVersionSchemaException
356
     */
357
    public function update($upVersion = null, $force = false)
358
    {
359
        $versionInfo = $this->getCurrentVersion();
360
        $version = intval($versionInfo['version']);
361
        $increment = 1;
362
        if ($upVersion !== null && $upVersion < $version) {
363
            $increment = -1;
364
        }
365
        $this->migrate($upVersion, $increment, $force);
366
    }
367
368
    /**
369
     * Run all scripts to down the database version from current version up to the specified version.
370
     *
371
     * @param int $upVersion
372
     * @param bool $force
373
     * @throws \ByJG\DbMigration\Exception\DatabaseDoesNotRegistered
374
     * @throws \ByJG\DbMigration\Exception\DatabaseIsIncompleteException
375
     * @throws \ByJG\DbMigration\Exception\DatabaseNotVersionedException
376
     * @throws \ByJG\DbMigration\Exception\InvalidMigrationFile
377
     * @throws \ByJG\DbMigration\Exception\OldVersionSchemaException
378
     */
379
    public function down($upVersion, $force = false)
380
    {
381
        $this->migrate($upVersion, -1, $force);
382
    }
383
384
    /**
385
     * @param callable $callable
386
     */
387
    public function addCallbackProgress(callable $callable)
388
    {
389
        $this->callableProgress = $callable;
390
    }
391
392
    public function isDatabaseVersioned()
393
    {
394
        return $this->getDbCommand()->isDatabaseVersioned();
395
    }
396
}
397