Migration   B
last analyzed

Complexity

Total Complexity 52

Size/Duplication

Total Lines 487
Duplicated Lines 0 %

Test Coverage

Coverage 87.04%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 207
dl 0
loc 487
ccs 188
cts 216
cp 0.8704
rs 7.44
c 2
b 0
f 0
wmc 52

18 Methods

Rating   Name   Duplication   Size   Complexity  
A loadMigrationsByPaths() 0 3 1
A startMigration() 0 3 1
A getEntityName() 0 4 1
A addMigration() 0 20 2
A deleteMigration() 0 5 1
A __construct() 0 3 1
A migrationToArray() 0 9 1
A endMigration() 0 51 5
A loadMigrations() 0 3 1
A createTable() 0 34 5
A deleteMigrations() 0 3 1
A loadMigrationsByStatus() 0 3 1
A skipMigration() 0 3 1
C loadMigrationsInner() 0 52 15
A loadMigration() 0 18 3
A arrayToMigration() 0 9 1
A resumeMigration() 0 56 3
B createMigration() 0 80 8

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 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\Exception\MigrationBundleException;
13
use Kaliop\eZMigrationBundle\API\Value\Migration as APIMigration;
14
use Kaliop\eZMigrationBundle\API\Value\MigrationDefinition;
15
16
use Kaliop\eZMigrationBundle\API\ConfigResolverInterface;
17
18
/**
19
 * Database-backed storage for info on executed migrations
20
 *
21
 * @todo replace all usage of the ezcdb api with the doctrine dbal one, so that we only depend on one
22
 */
23
class Migration extends TableStorage implements StorageHandlerInterface
24
{
25
    protected $fieldList = 'migration, md5, path, execution_date, status, execution_error';
26
27
    /**
28
     * @param DatabaseHandler $dbHandler
29
     * @param string $tableNameParameter
30
     * @param ConfigResolverInterface $configResolver
31
     * @param array $tableCreationOptions
32
     * @throws \Exception
33
     */
34 149
    public function __construct(DatabaseHandler $dbHandler, $tableNameParameter = 'kaliop_migrations', ConfigResolverInterface $configResolver = null, $tableCreationOptions = array())
35
    {
36 149
        parent::__construct($dbHandler, $tableNameParameter, $configResolver, $tableCreationOptions);
37 149
    }
38
39
    /**
40
     * @param int $limit
41
     * @param int $offset
42
     * @return MigrationCollection
43
     */
44 99
    public function loadMigrations($limit = null, $offset = null)
45
    {
46 99
        return $this->loadMigrationsInner(null, null, $limit, $offset);
47
    }
48
49
    /**
50
     * @param int $status
51
     * @param int $limit
52
     * @param int $offset
53
     * @return MigrationCollection
54
     */
55 2
    public function loadMigrationsByStatus($status, $limit = null, $offset = null)
56
    {
57 2
        return $this->loadMigrationsInner($status, null, $limit, $offset);
58
    }
59
60 47
    public function loadMigrationsByPaths($paths, $limit = null, $offset = null)
61
    {
62 47
        return $this->loadMigrationsInner(null, $paths, $limit, $offset);
63
    }
64
65
    /**
66
     * @param int $status
67
     * @param null|string[] $paths
68
     * @param int $limit
69
     * @param int $offset
70
     * @return MigrationCollection
71
     */
72 144
    protected function loadMigrationsInner($status = null, $paths = array(), $limit = null, $offset = null)
73
    {
74 144
        $this->createTableIfNeeded();
75
76
        /** @var \eZ\Publish\Core\Persistence\Database\SelectQuery $q */
77 144
        $q = $this->dbHandler->createSelectQuery();
78 144
        $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

78
        $q->/** @scrutinizer ignore-call */ 
79
            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...
79 144
            ->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

79
            ->/** @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...
80 144
            ->orderBy('migration', SelectQuery::ASC);
81 144
        if ($status !== null || (is_array($paths) && count($paths))) {
82 5
            $exps = [];
83 5
            if ($status !== null) {
84 2
                $exps[] = $q->expr->eq('status', $q->bindValue($status));
85
            }
86 5
            if (is_array($paths)) {
87 3
                foreach ($paths as $i => $path) {
88 3
                    if ($path === '') {
89
                        unset($paths[$i]);
90
                    }
91
                }
92 3
                if (count($paths)) {
93 3
                    $pexps = array();
94 3
                    foreach ($paths as $path) {
95
                        // NB: this works fine only as long both the paths stored in the db and the ones passed in follow
96
                        //     the same convention regarding path normalization
97
                        /// @todo use a proper db-aware escaping function
98 3
                        $pexps[] = $q->expr->like('path', "'" . str_replace(array('_', '%', "'"), array('\_', '\%', "''"), $path) . '%' . "'");
99
                    }
100
                }
101 3
                $exps[] = $q->expr->lor($pexps);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $pexps does not seem to be defined for all execution paths leading up to this point.
Loading history...
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

101
                /** @scrutinizer ignore-call */ 
102
                $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...
102
            }
103 5
            $q->where($q->expr->land($exps));
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->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

103
            $q->/** @scrutinizer ignore-call */ 
104
                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...
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

103
            $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...
104
        }
105 144
        if ($limit > 0 || $offset > 0) {
106 1
            if ($limit <= 0) {
107
                $limit = null;
108
            }
109 1
            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...
110 1
                $offset = null;
111
            }
112 1
            $q->limit($limit, $offset);
113
        }
114 144
        $stmt = $q->prepare();
115 144
        $stmt->execute();
116 144
        $results = $stmt->fetchAll();
117
118 144
        $migrations = array();
119 144
        foreach ($results as $result) {
120 84
            $migrations[$result['migration']] = $this->arrayToMigration($result);
121
        }
122
123 144
        return new MigrationCollection($migrations);
124
    }
125
126
    /**
127
     * @param string $migrationName
128
     * @return APIMigration|null
129
     */
130 108
    public function loadMigration($migrationName)
131
    {
132 108
        $this->createTableIfNeeded();
133
134
        /** @var \eZ\Publish\Core\Persistence\Database\SelectQuery $q */
135 108
        $q = $this->dbHandler->createSelectQuery();
136 108
        $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

136
        $q->/** @scrutinizer ignore-call */ 
137
            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...
137 108
            ->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

137
            ->/** @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...
138 108
            ->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

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