Completed
Push — master ( 405448...a9f96c )
by Gaetano
05:21
created

Migration::loadMigrationsByPaths()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 3
dl 0
loc 3
ccs 0
cts 0
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Kaliop\eZMigrationBundle\Core\StorageHandler\Database;
4
5
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
6
use Doctrine\DBAL\Schema\Schema;
7
use eZ\Publish\Core\Persistence\Database\DatabaseHandler;
8
use eZ\Publish\Core\Persistence\Database\QueryException;
9
use eZ\Publish\Core\Persistence\Database\SelectQuery;
10
use Kaliop\eZMigrationBundle\API\StorageHandlerInterface;
11
use Kaliop\eZMigrationBundle\API\Collection\MigrationCollection;
12
use Kaliop\eZMigrationBundle\API\Value\Migration as APIMigration;
13
use Kaliop\eZMigrationBundle\API\Value\MigrationDefinition;
14
15
use Kaliop\eZMigrationBundle\API\ConfigResolverInterface;
16
17
/**
18
 * Database-backed storage for info on executed migrations
19
 *
20
 * @todo replace all usage of the ezcdb api with the doctrine dbal one, so that we only depend on one
21
 */
22
class Migration extends TableStorage implements StorageHandlerInterface
23
{
24
    protected $fieldList = 'migration, md5, path, execution_date, status, execution_error';
25
26
    /**
27
     * @param DatabaseHandler $dbHandler
28
     * @param string $tableNameParameter
29
     * @param ConfigResolverInterface $configResolver
30
     * @param array $tableCreationOptions
31 96
     * @throws \Exception
32
     */
33 96
    public function __construct(DatabaseHandler $dbHandler, $tableNameParameter = 'kaliop_migrations', ConfigResolverInterface $configResolver = null, $tableCreationOptions = array())
34 96
    {
35
        parent::__construct($dbHandler, $tableNameParameter, $configResolver, $tableCreationOptions);
36
    }
37
38
    /**
39
     * @param int $limit
40
     * @param int $offset
41 93
     * @return MigrationCollection
42
     */
43 93
    public function loadMigrations($limit = null, $offset = null)
44
    {
45
        return $this->loadMigrationsInner(null, null, $limit, $offset);
46
    }
47
48
    /**
49
     * @param int $status
50
     * @param int $limit
51
     * @param int $offset
52 1
     * @return MigrationCollection
53
     */
54 1
    public function loadMigrationsByStatus($status, $limit = null, $offset = null)
55
    {
56
        return $this->loadMigrationsInner($status, null, $limit, $offset);
57
    }
58
59
    public function loadMigrationsByPaths($paths, $limit = null, $offset = null)
60
    {
61
        return $this->loadMigrationsInner(null, $paths, $limit, $offset);
62
    }
63 93
64
    /**
65 93
     * @param int $status
66
     * @param null|string[] $paths
67
     * @param int $limit
68 93
     * @param int $offset
69 93
     * @return MigrationCollection
70 93
     */
71 93
    protected function loadMigrationsInner($status = null, $paths = array(), $limit = null, $offset = null)
72 93
    {
73 1
        $this->createTableIfNeeded();
74
75 93
        /** @var \eZ\Publish\Core\Persistence\Database\SelectQuery $q */
76 1
        $q = $this->dbHandler->createSelectQuery();
77
        $q->select($this->fieldList)
0 ignored issues
show
Unused Code introduced by
The call to eZ\Publish\Core\Persiste...e\SelectQuery::select() has too many arguments starting with $this->fieldList. ( Ignorable by Annotation )

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

77
        $q->/** @scrutinizer ignore-call */ 
78
            select($this->fieldList)

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
78
            ->from($this->tableName)
0 ignored issues
show
Unused Code introduced by
The call to eZ\Publish\Core\Persiste...ase\SelectQuery::from() has too many arguments starting with $this->tableName. ( Ignorable by Annotation )

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

78
            ->/** @scrutinizer ignore-call */ from($this->tableName)

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
79 1
            ->orderBy('migration', SelectQuery::ASC);
80 1
        if ($status !== null || (is_array($paths) && count($paths))) {
81
            $exps = [];
82 1
            if ($status !== null) {
83
                $exps[] = $q->expr->eq('status', $q->bindValue($status));
84 93
            }
85 93
            if (is_array($paths) && count($paths)) {
86 93
                $pexps = array();
87
                foreach($paths as $path) {
88 93
                    /// @todo use a proper db-aware escaping function
89 93
                    $pexps[] = $q->expr->like('path', "'" . str_replace(array('_', '%', "'"), array('\_', '\%', "''"), $path).'%' . "'");
90 59
                }
91
                $exps[] = $q->expr->lor($pexps);
0 ignored issues
show
Unused Code introduced by
The call to eZ\Publish\Core\Persiste...abase\Expression::lOr() has too many arguments starting with $pexps. ( Ignorable by Annotation )

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

91
                /** @scrutinizer ignore-call */ 
92
                $exps[] = $q->expr->lor($pexps);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
92
            }
93 93
            $q->where($q->expr->land($exps));
0 ignored issues
show
Unused Code introduced by
The call to eZ\Publish\Core\Persiste...base\Expression::lAnd() has too many arguments starting with $exps. ( Ignorable by Annotation )

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

93
            $q->where($q->expr->/** @scrutinizer ignore-call */ land($exps));

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
Unused Code introduced by
The call to eZ\Publish\Core\Persiste...se\SelectQuery::where() has too many arguments starting with $q->expr->land($exps). ( Ignorable by Annotation )

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

93
            $q->/** @scrutinizer ignore-call */ 
94
                where($q->expr->land($exps));

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
94
        }
95
        if ($limit > 0 || $offset > 0) {
96
            if ($limit <= 0) {
97
                $limit = null;
98
            }
99
            if ($offset == 0) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $offset of type integer|null to 0; this is ambiguous as not only 0 == 0 is true, but null == 0 is true, too. Consider using a strict comparison ===.
Loading history...
100 62
                $offset = null;
101
            }
102 62
            $q->limit($limit, $offset);
103
        }
104
        $stmt = $q->prepare();
105 62
        $stmt->execute();
106 62
        $results = $stmt->fetchAll();
107 62
108 62
        $migrations = array();
109 62
        foreach ($results as $result) {
110 62
            $migrations[$result['migration']] = $this->arrayToMigration($result);
111 62
        }
112
113 62
        return new MigrationCollection($migrations);
114 60
    }
115
116
    /**
117 60
     * @param string $migrationName
118
     * @return APIMigration|null
119
     */
120
    public function loadMigration($migrationName)
121
    {
122
        $this->createTableIfNeeded();
123
124
        /** @var \eZ\Publish\Core\Persistence\Database\SelectQuery $q */
125
        $q = $this->dbHandler->createSelectQuery();
126 61
        $q->select($this->fieldList)
0 ignored issues
show
Unused Code introduced by
The call to eZ\Publish\Core\Persiste...e\SelectQuery::select() has too many arguments starting with $this->fieldList. ( Ignorable by Annotation )

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

126
        $q->/** @scrutinizer ignore-call */ 
127
            select($this->fieldList)

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
127
            ->from($this->tableName)
0 ignored issues
show
Unused Code introduced by
The call to eZ\Publish\Core\Persiste...ase\SelectQuery::from() has too many arguments starting with $this->tableName. ( Ignorable by Annotation )

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

127
            ->/** @scrutinizer ignore-call */ from($this->tableName)

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
128 61
            ->where($q->expr->eq('migration', $q->bindValue($migrationName)));
0 ignored issues
show
Unused Code introduced by
The call to eZ\Publish\Core\Persiste...se\SelectQuery::where() has too many arguments starting with $q->expr->eq('migration'...dValue($migrationName)). ( Ignorable by Annotation )

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

128
            ->/** @scrutinizer ignore-call */ where($q->expr->eq('migration', $q->bindValue($migrationName)));

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
129
        $stmt = $q->prepare();
130 61
        $stmt->execute();
131
        $result = $stmt->fetch(\PDO::FETCH_ASSOC);
132 61
133 61
        if (is_array($result) && !empty($result)) {
134 61
            return $this->arrayToMigration($result);
135 61
        }
136 61
137 61
        return null;
138
    }
139
140 61
    /**
141
     * Creates and stores a new migration (leaving it in TODO status)
142
     * @param MigrationDefinition $migrationDefinition
143
     * @return APIMigration
144
     * @throws \Exception If the migration exists already (we rely on the PK for that)
145 61
     */
146
    public function addMigration(MigrationDefinition $migrationDefinition)
147
    {
148
        $this->createTableIfNeeded();
149
150
        $conn = $this->getConnection();
151
152
        $migration = new APIMigration(
153
            $migrationDefinition->name,
154
            md5($migrationDefinition->rawDefinition),
155
            $migrationDefinition->path,
156
            null,
157 49
            APIMigration::STATUS_TODO
158
        );
159 49
        try {
160
            $conn->insert($this->tableName, $this->migrationToArray($migration));
161
        } catch (UniqueConstraintViolationException $e) {
162
            throw new \Exception("Migration '{$migrationDefinition->name}' already exists");
163
        }
164
165
        return $migration;
166
    }
167
168
    /**
169
     * Starts a migration, given its definition: stores its status in the db, returns the Migration object
170
     *
171
     * @param MigrationDefinition $migrationDefinition
172
     * @param bool $force when true, starts migrations even if they already exist in DONE, SKIPPED status
173
     * @return APIMigration
174
     * @throws \Exception if migration was already executing or already done
175
     * @todo add a parameter to allow re-execution of already-done migrations
176 49
     */
177
    public function startMigration(MigrationDefinition $migrationDefinition, $force = false)
178 49
    {
179
        return $this->createMigration($migrationDefinition, APIMigration::STATUS_STARTED, 'started', $force);
180
    }
181
182 49
    /**
183
     * Stops a migration by storing it in the db. Migration status can not be 'started'
184
     *
185
     * NB: if this call happens within another DB transaction which has already been flagged for rollback, the result
186
     * will be that a RuntimeException is thrown, as Doctrine does not allow to call commit() after rollback().
187
     * One way to fix the problem would be not to use a transaction and select-for-update here, but since that is the
188 49
     * best way to insure atomic updates, I am loath to remove it.
189
     * A known workaround is to call the Doctrine Connection method setNestTransactionsWithSavepoints(true); this can
190 49
     * be achieved as simply as setting the parameter 'use_savepoints' in the doctrine connection configuration.
191 49
     *
192 49
     * @param APIMigration $migration
193 49
     * @param bool $force When true, the migration will be updated even if it was not in 'started' status
194 49
     * @throws \Exception If the migration was not started (unless $force=true)
195
     */
196 49
    public function endMigration(APIMigration $migration, $force = false)
197
    {
198 49
        if ($migration->status == APIMigration::STATUS_STARTED) {
199 49
            throw new \Exception($this->getEntityName($migration)." '{$migration->name}' can not be ended as its status is 'started'...");
200
        }
201
202
        $this->createTableIfNeeded();
203 49
204
        // select for update
205
206
        // annoyingly enough, neither Doctrine nor EZP provide built in support for 'FOR UPDATE' in their query builders...
207
        // at least the doctrine one allows us to still use parameter binding when we add our sql particle
208
        $conn = $this->getConnection();
209 49
210
        $qb = $conn->createQueryBuilder();
211
        $qb->select('*')
212
            ->from($this->tableName, 'm')
213
            ->where('migration = ?');
214
        $sql = $qb->getSQL() . ' FOR UPDATE';
215 49
216 49
        $conn->beginTransaction();
217
218 49
        $stmt = $conn->executeQuery($sql, array($migration->name));
219
        $existingMigrationData = $stmt->fetch(\PDO::FETCH_ASSOC);
220 49
221 49
        // fail if it was not executing
222
223 49
        if (!is_array($existingMigrationData)) {
224
            // commit to release the lock
225
            $conn->commit();
226 49
            throw new \Exception($this->getEntityName($migration)." '{$migration->name}' can not be ended as it is not found");
227 49
        }
228
229
        if (($existingMigrationData['status'] != APIMigration::STATUS_STARTED) && !$force) {
230
            // commit to release the lock
231
            $conn->commit();
232
            throw new \Exception($this->getEntityName($migration)." '{$migration->name}' can not be ended as it is not executing");
233
        }
234 58
235
        $conn->update(
236 58
            $this->tableName,
237 58
            array(
238 58
                'status' => $migration->status,
239 58
                /// @todo use mb_substr (if all dbs we support count col length not in bytes but in chars...)
240
                'execution_error' => substr($migration->executionError, 0, 4000),
241
                'execution_date' => $migration->executionDate
242
            ),
243
            array('migration' => $migration->name)
244
        );
245
246
        $conn->commit();
247
    }
248
249
    /**
250
     * Removes a Migration from the table - regardless of its state!
251
     *
252
     * @param APIMigration $migration
253
     */
254
    public function deleteMigration(APIMigration $migration)
255
    {
256
        $this->createTableIfNeeded();
257
        $conn = $this->getConnection();
258
        $conn->delete($this->tableName, array('migration' => $migration->name));
259
    }
260
261 49
    /**
262
     * Skips a migration by storing it in the db. Migration status can not be 'started'
263 49
     *
264
     * @param MigrationDefinition $migrationDefinition
265
     * @return APIMigration
266
     * @throws \Exception If the migration was already executed or executing
267
     */
268
    public function skipMigration(MigrationDefinition $migrationDefinition)
269 49
    {
270
        return $this->createMigration($migrationDefinition, APIMigration::STATUS_SKIPPED, 'skipped');
271 49
    }
272 49
273 49
    /**
274 49
     * @param MigrationDefinition $migrationDefinition
275 49
     * @param int $status
276
     * @param string $action
277 49
     * @param bool $force
278
     * @return APIMigration
279 49
     * @throws \Exception
280 49
     */
281
    protected function createMigration(MigrationDefinition $migrationDefinition, $status, $action, $force = false)
282 49
    {
283
        $this->createTableIfNeeded();
284
285
        // select for update
286 47
287
        // annoyingly enough, neither Doctrine nor EZP provide built in support for 'FOR UPDATE' in their query builders...
288
        // at least the doctrine one allows us to still use parameter binding when we add our sql particle
289
        $conn = $this->getConnection();
290
291
        $qb = $conn->createQueryBuilder();
292 47
        $qb->select('*')
293 45
            ->from($this->tableName, 'm')
294
            ->where('migration = ?');
295
        $sql = $qb->getSQL() . ' FOR UPDATE';
296
297
        $conn->beginTransaction();
298 45
299
        $stmt = $conn->executeQuery($sql, array($migrationDefinition->name));
300
        $existingMigrationData = $stmt->fetch(\PDO::FETCH_ASSOC);
301
302
        if (is_array($existingMigrationData)) {
303
            // migration exists
304
305
            // fail if it was already executing
306 47
            if ($existingMigrationData['status'] == APIMigration::STATUS_STARTED) {
307 47
                // commit to release the lock
308 47
                $conn->commit();
309 47
                throw new \Exception("Migration '{$migrationDefinition->name}' can not be $action as it is already executing");
310 47
            }
311 47
            // fail if it was already already done, unless in 'force' mode
312
            if (!$force) {
313 47
                if ($existingMigrationData['status'] == APIMigration::STATUS_DONE) {
314 47
                    // commit to release the lock
315
                    $conn->commit();
316 47
                    throw new \Exception("Migration '{$migrationDefinition->name}' can not be $action as it was already executed");
317 47
                }
318
                if ($existingMigrationData['status'] == APIMigration::STATUS_SKIPPED) {
319
                    // commit to release the lock
320 47
                    $conn->commit();
321
                    throw new \Exception("Migration '{$migrationDefinition->name}' can not be $action as it was already skipped");
322 47
                }
323
            }
324
325
            // do not set migration start date if we are skipping it
326
            $migration = new APIMigration(
327
                $migrationDefinition->name,
328 2
                md5($migrationDefinition->rawDefinition),
329
                $migrationDefinition->path,
330 2
                ($status == APIMigration::STATUS_SKIPPED ? null : time()),
331 2
                $status
332 2
            );
333 2
            $conn->update(
334 2
                $this->tableName,
335 2
                array(
336
                    'execution_date' => $migration->executionDate,
337 2
                    'status' => $status,
338
                    'execution_error' => null
339
                ),
340 49
                array('migration' => $migrationDefinition->name)
341
            );
342
            $conn->commit();
343
344
        } else {
345
            // migration did not exist. Create it!
346
347
            // commit immediately, to release the lock and avoid deadlocks
348
            $conn->commit();
349
350
            $migration = new APIMigration(
351
                $migrationDefinition->name,
352
                md5($migrationDefinition->rawDefinition),
353
                $migrationDefinition->path,
354
                ($status == APIMigration::STATUS_SKIPPED ? null : time()),
355
                $status
356
            );
357
            $conn->insert($this->tableName, $this->migrationToArray($migration));
358
        }
359
360
        return $migration;
361
    }
362
363
    public function resumeMigration(APIMigration $migration)
364
    {
365
        $this->createTableIfNeeded();
366
367
        // select for update
368
369
        // annoyingly enough, neither Doctrine nor EZP provide built in support for 'FOR UPDATE' in their query builders...
370
        // at least the doctrine one allows us to still use parameter binding when we add our sql particle
371
        $conn = $this->getConnection();
372
373
        $qb = $conn->createQueryBuilder();
374
        $qb->select('*')
375
            ->from($this->tableName, 'm')
376
            ->where('migration = ?');
377
        $sql = $qb->getSQL() . ' FOR UPDATE';
378
379
        $conn->beginTransaction();
380
381
        $stmt = $conn->executeQuery($sql, array($migration->name));
382
        $existingMigrationData = $stmt->fetch(\PDO::FETCH_ASSOC);
383
384
        if (!is_array($existingMigrationData)) {
385
            // commit immediately, to release the lock and avoid deadlocks
386
            $conn->commit();
387
            throw new \Exception($this->getEntityName($migration)." '{$migration->name}' can not be resumed as it is not found");
388
        }
389
390
        // migration exists
391
392
        // fail if it was not suspended
393
        if ($existingMigrationData['status'] != APIMigration::STATUS_SUSPENDED) {
394
            // commit to release the lock
395
            $conn->commit();
396
            throw new \Exception($this->getEntityName($migration)." '{$migration->name}' can not be resumed as it is not suspended");
397
        }
398
399
        $migration = new APIMigration(
400
            $migration->name,
401
            $migration->md5,
402
            $migration->path,
403
            time(),
404
            APIMigration::STATUS_STARTED
405
        );
406
407
        $conn->update(
408
            $this->tableName,
409 1
            array(
410
                'execution_date' => $migration->executionDate,
411
                'status' => APIMigration::STATUS_STARTED,
412 1
                'execution_error' => null
413 1
            ),
414
            array('migration' => $migration->name)
415 1
        );
416
        $conn->commit();
417 1
418 1
        return $migration;
419 1
    }
420 1
421 1
    /**
422 1
     * Removes all migration from storage (regardless of their status)
423 1
     */
424 1
    public function deleteMigrations()
425
    {
426
        $this->drop();
427
    }
428
429
    /**
430 1
     * @throws QueryException
431 1
     */
432
    public function createTable()
433 1
    {
434
        /** @var \Doctrine\DBAL\Schema\AbstractSchemaManager $sm */
435 62
        $sm = $this->getConnection()->getSchemaManager();
436
        $dbPlatform = $sm->getDatabasePlatform();
437
438 62
        $schema = new Schema();
439 62
440 62
        $t = $schema->createTable($this->tableName);
441 62
        $t->addColumn('migration', 'string', array('length' => 255));
442 62
        $t->addColumn('path', 'string', array('length' => 4000));
443 62
        $t->addColumn('md5', 'string', array('length' => 32));
444
        $t->addColumn('execution_date', 'integer', array('notnull' => false));
445
        $t->addColumn('status', 'integer', array('default ' => APIMigration::STATUS_TODO));
446
        $t->addColumn('execution_error', 'string', array('length' => 4000, 'notnull' => false));
447 61
        $t->setPrimaryKey(array('migration'));
448
        // in case users want to look up migrations by their full path
449 61
        // NB: disabled for the moment, as it causes problems on some versions of mysql which limit index length to 767 bytes,
450 61
        // and 767 bytes can be either 255 chars or 191 chars depending on charset utf8 or utf8mb4...
451 61
        //$t->addIndex(array('path'));
452 61
453 61
        $this->injectTableCreationOptions($t);
454 61
455 61
        foreach ($schema->toSql($dbPlatform) as $sql) {
456
            try {
457
                $this->dbHandler->exec($sql);
458
            } catch(QueryException $e) {
459
                // work around limitations in both Mysql and Doctrine
460
                // @see https://github.com/kaliop-uk/ezmigrationbundle/issues/176
461
                if (strpos($e->getMessage(), '1071 Specified key was too long; max key length is 767 bytes') !== false &&
462
                    strpos($sql, 'PRIMARY KEY(migration)') !== false) {
463
                    $this->dbHandler->exec(str_replace('PRIMARY KEY(migration)', 'PRIMARY KEY(migration(191))', $sql));
464
                } else {
465
                    throw $e;
466
                }
467
            }
468
        }
469
    }
470
471
    protected function migrationToArray(APIMigration $migration)
472
    {
473
        return array(
474
            'migration' => $migration->name,
475
            'md5' => $migration->md5,
476
            'path' => $migration->path,
477
            'execution_date' => $migration->executionDate,
478
            'status' => $migration->status,
479
            'execution_error' => $migration->executionError
480
        );
481
    }
482
483
    protected function arrayToMigration(array $data)
484
    {
485
        return new APIMigration(
486
            $data['migration'],
487
            $data['md5'],
488
            $data['path'],
489
            $data['execution_date'],
490
            $data['status'],
491
            $data['execution_error']
492
        );
493
    }
494
495
    protected function getEntityName($migration)
496
    {
497
        $arr = explode('\\', get_class($migration));
498
        return end($arr);
499
    }
500
}
501