Completed
Push — master ( 882947...a4a53d )
by Mike
13:52 queued 13:46
created

Configuration::resolveVersionAlias()   C

Complexity

Conditions 8
Paths 8

Size

Total Lines 23
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 8.0155

Importance

Changes 0
Metric Value
dl 0
loc 23
ccs 15
cts 16
cp 0.9375
rs 6.1403
c 0
b 0
f 0
cc 8
eloc 18
nc 8
nop 1
crap 8.0155
1
<?php
2
3
namespace Doctrine\DBAL\Migrations\Configuration;
4
5
use Doctrine\Common\EventArgs;
6
use Doctrine\DBAL\Connection;
7
use Doctrine\DBAL\Connections\MasterSlaveConnection;
8
use Doctrine\DBAL\Migrations\FileQueryWriter;
9
use Doctrine\DBAL\Migrations\Finder\MigrationDeepFinderInterface;
10
use Doctrine\DBAL\Migrations\MigrationException;
11
use Doctrine\DBAL\Migrations\OutputWriter;
12
use Doctrine\DBAL\Migrations\QueryWriter;
13
use Doctrine\DBAL\Migrations\Version;
14
use Doctrine\DBAL\Migrations\Finder\MigrationFinderInterface;
15
use Doctrine\DBAL\Migrations\Finder\RecursiveRegexFinder;
16
use Doctrine\DBAL\Schema\Column;
17
use Doctrine\DBAL\Schema\Table;
18
use Doctrine\DBAL\Types\Type;
19
20
/**
21
 * Default Migration Configuration object used for configuring an instance of
22
 * the Migration class. Set the connection, version table name, register migration
23
 * classes/versions, etc.
24
 *
25
 * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
26
 * @link        www.doctrine-project.org
27
 * @since       2.0
28
 * @author      Jonathan H. Wage <[email protected]>
29
 */
30
class Configuration
31
{
32
    /**
33
     * Configure versions to be organized by year.
34
     */
35
    const VERSIONS_ORGANIZATION_BY_YEAR = 'year';
36
37
    /**
38
     * Configure versions to be organized by year and month.
39
     *
40
     * @var string
41
     */
42
    const VERSIONS_ORGANIZATION_BY_YEAR_AND_MONTH = 'year_and_month';
43
44
    /**
45
     * The date format for new version numbers
46
     */
47
    const VERSION_FORMAT = 'YmdHis';
48
49
    /**
50
     * Name of this set of migrations
51
     *
52
     * @var string
53
     */
54
    private $name;
55
56
    /**
57
     * Flag for whether or not the migration table has been created
58
     *
59
     * @var boolean
60
     */
61
    private $migrationTableCreated = false;
62
63
    /**
64
     * Connection instance to use for migrations
65
     *
66
     * @var Connection
67
     */
68
    private $connection;
69
70
    /**
71
     * OutputWriter instance for writing output during migrations
72
     *
73
     * @var OutputWriter
74
     */
75
    private $outputWriter;
76
77
    /**
78
     * The migration finder implementation -- used to load migrations from a
79
     * directory.
80
     *
81
     * @var MigrationFinderInterface
82
     */
83
    private $migrationFinder;
84
85
    /**
86
     * @var QueryWriter
87
     */
88
    private $queryWriter;
89
90
    /**
91
     * The migration table name to track versions in
92
     *
93
     * @var string
94
     */
95
    private $migrationsTableName = 'doctrine_migration_versions';
96
97
    /**
98
     * The migration column name to track versions in
99
     *
100
     * @var string
101
     */
102
    private $migrationsColumnName = 'version';
103
104
    /**
105
     * The path to a directory where new migration classes will be written
106
     *
107
     * @var string
108
     */
109
    private $migrationsDirectory;
110
111
    /**
112
     * Namespace the migration classes live in
113
     *
114
     * @var string
115
     */
116
    private $migrationsNamespace;
117
118
    /**
119
     * Array of the registered migrations
120
     *
121
     * @var Version[]
122
     */
123
    private $migrations = [];
124
125
    /**
126
     * Versions are organized by year.
127
     *
128
     * @var boolean
129
     */
130
    private $migrationsAreOrganizedByYear = false;
131
132
    /**
133
     * Versions are organized by year and month.
134
     *
135
     * @var boolean
136
     */
137
    private $migrationsAreOrganizedByYearAndMonth = false;
138
139
    /**
140
     * The custom template path to be used in generate command
141
     *
142
     * @var string
143
     */
144
    private $customTemplate;
145
146
    /**
147
     * Construct a migration configuration object.
148
     *
149
     * @param Connection               $connection   A Connection instance
150
     * @param OutputWriter             $outputWriter A OutputWriter instance
151
     * @param MigrationFinderInterface $finder       Migration files finder
152
     * @param QueryWriter|null         $queryWriter
153
     */
154 278
    public function __construct(
155
        Connection $connection,
156
        OutputWriter $outputWriter = null,
157
        MigrationFinderInterface $finder = null,
158
        QueryWriter $queryWriter = null
159
    ) {
160 278
        $this->connection      = $connection;
161 278
        $this->outputWriter    = $outputWriter ?? new OutputWriter();
162 278
        $this->migrationFinder = $finder ?? new RecursiveRegexFinder();
163 278
        $this->queryWriter     = $queryWriter;
164 278
    }
165
166
    /**
167
     * @return bool
168
     */
169 22
    public function areMigrationsOrganizedByYear()
170
    {
171 22
        return $this->migrationsAreOrganizedByYear;
172
    }
173
174
    /**
175
     * @return bool
176
     */
177 22
    public function areMigrationsOrganizedByYearAndMonth()
178
    {
179 22
        return $this->migrationsAreOrganizedByYearAndMonth;
180
    }
181
182
    /**
183
     * Validation that this instance has all the required properties configured
184
     *
185
     * @throws MigrationException
186
     */
187 146
    public function validate()
188
    {
189 146
        if ( ! $this->migrationsNamespace) {
190 10
            throw MigrationException::migrationsNamespaceRequired();
191
        }
192 136
        if ( ! $this->migrationsDirectory) {
193 10
            throw MigrationException::migrationsDirectoryRequired();
194
        }
195 126
    }
196
197
    /**
198
     * Set the name of this set of migrations
199
     *
200
     * @param string $name The name of this set of migrations
201
     */
202 63
    public function setName($name)
203
    {
204 63
        $this->name = $name;
205 63
    }
206
207
    /**
208
     * Returns the name of this set of migrations
209
     *
210
     * @return string $name The name of this set of migrations
211
     */
212 16
    public function getName()
213
    {
214 16
        return $this->name;
215
    }
216
217
    /**
218
     * Sets the output writer.
219
     *
220
     * @param OutputWriter $outputWriter
221
     */
222 3
    public function setOutputWriter(OutputWriter $outputWriter)
223
    {
224 3
        $this->outputWriter = $outputWriter;
225 3
    }
226
227
    /**
228
     * Returns the OutputWriter instance
229
     *
230
     * @return OutputWriter $outputWriter  The OutputWriter instance
231
     */
232 118
    public function getOutputWriter()
233
    {
234 118
        return $this->outputWriter;
235
    }
236
237
    /**
238
     * Returns a timestamp version as a formatted date
239
     *
240
     * @param string $version
241
     *
242
     * @return string The formatted version
243
     * @deprecated
244
     */
245 14
    public function formatVersion($version)
246
    {
247 14
        return $this->getDateTime($version);
248
    }
249
250
    /**
251
     * Returns the datetime of a migration
252
     *
253
     * @param $version
254
     * @return string
255
     */
256 22
    public function getDateTime($version)
257
    {
258 22
        $datetime = str_replace('Version', '', $version);
259 22
        $datetime = \DateTime::createFromFormat('YmdHis', $datetime);
260
261 22
        if ($datetime === false) {
262 8
            return '';
263
        }
264
265 15
        return $datetime->format('Y-m-d H:i:s');
266
    }
267
268
    /**
269
     * Returns the Connection instance
270
     *
271
     * @return Connection $connection  The Connection instance
272
     */
273 121
    public function getConnection()
274
    {
275 121
        return $this->connection;
276
    }
277
278
    /**
279
     * Set the migration table name
280
     *
281
     * @param string $tableName The migration table name
282
     */
283 85
    public function setMigrationsTableName($tableName)
284
    {
285 85
        $this->migrationsTableName = $tableName;
286 85
    }
287
288
    /**
289
     * Returns the migration table name
290
     *
291
     * @return string $migrationsTableName The migration table name
292
     */
293 60
    public function getMigrationsTableName()
294
    {
295 60
        return $this->migrationsTableName;
296
    }
297
298
    /**
299
     * Set the migration column name
300
     *
301
     * @param string $columnName The migration column name
302
     */
303 56
    public function setMigrationsColumnName($columnName)
304
    {
305 56
        $this->migrationsColumnName = $columnName;
306 56
    }
307
308
    /**
309
     * Returns the migration column name
310
     *
311
     * @return string $migrationsColumnName The migration column name
312
     */
313 54
    public function getMigrationsColumnName()
314
    {
315 54
        return $this->migrationsColumnName;
316
    }
317
318
    /**
319
     * Set the new migrations directory where new migration classes are generated
320
     *
321
     * @param string $migrationsDirectory The new migrations directory
322
     */
323 183
    public function setMigrationsDirectory($migrationsDirectory)
324
    {
325 183
        $this->migrationsDirectory = $migrationsDirectory;
326 183
    }
327
328
    /**
329
     * Returns the new migrations directory where new migration classes are generated
330
     *
331
     * @return string $migrationsDirectory The new migrations directory
332
     */
333 65
    public function getMigrationsDirectory()
334
    {
335 65
        return $this->migrationsDirectory;
336
    }
337
338
    /**
339
     * Set the migrations namespace
340
     *
341
     * @param string $migrationsNamespace The migrations namespace
342
     */
343 195
    public function setMigrationsNamespace($migrationsNamespace)
344
    {
345 195
        $this->migrationsNamespace = $migrationsNamespace;
346 195
    }
347
348
    /**
349
     * Returns the migrations namespace
350
     *
351
     * @return string $migrationsNamespace The migrations namespace
352
     */
353 92
    public function getMigrationsNamespace()
354
    {
355 92
        return $this->migrationsNamespace;
356
    }
357
358
    /**
359
     * Returns the custom template path
360
     *
361
     * @return string $customTemplate The custom template path
362
     */
363 4
    public function getCustomTemplate() : ?string
364
    {
365 4
        return $this->customTemplate;
366
    }
367
368
    /**
369
     * Set the custom template path
370
     *
371
     * @param string $customTemplate The custom template path
372
     */
373 5
    public function setCustomTemplate(?string $customTemplate) : void
374
    {
375 5
        $this->customTemplate = $customTemplate;
376 5
    }
377
378
    /**
379
     * Set the implementation of the migration finder.
380
     *
381
     * @param MigrationFinderInterface $finder The new migration finder
382
     * @throws MigrationException
383
     */
384 8
    public function setMigrationsFinder(MigrationFinderInterface $finder)
385
    {
386 8
        if (($this->migrationsAreOrganizedByYear || $this->migrationsAreOrganizedByYearAndMonth)
387 8
            && ! ($finder instanceof MigrationDeepFinderInterface)) {
388 4
            throw MigrationException::configurationIncompatibleWithFinder(
389 4
                'organize-migrations',
390 4
                $finder
391
            );
392
        }
393
394 4
        $this->migrationFinder = $finder;
395 4
    }
396
397
    /**
398
     * Register migrations from a given directory. Recursively finds all files
399
     * with the pattern VersionYYYYMMDDHHMMSS.php as the filename and registers
400
     * them as migrations.
401
     *
402
     * @param string $path The root directory to where some migration classes live.
403
     *
404
     * @return Version[] The array of migrations registered.
405
     */
406 103
    public function registerMigrationsFromDirectory($path)
407
    {
408 103
        $this->validate();
409
410 87
        return $this->registerMigrations($this->findMigrations($path));
411
    }
412
413
    /**
414
     * Register a single migration version to be executed by a AbstractMigration
415
     * class.
416
     *
417
     * @param string $version The version of the migration in the format YYYYMMDDHHMMSS.
418
     * @param string $class   The migration class to execute for the version.
419
     *
420
     * @return Version
421
     *
422
     * @throws MigrationException
423
     */
424 69
    public function registerMigration($version, $class)
425
    {
426 69
        $this->ensureMigrationClassExists($class);
427
428 68
        $version = (string) $version;
429 68
        $class   = (string) $class;
430 68
        if (isset($this->migrations[$version])) {
431 1
            throw MigrationException::duplicateMigrationVersion($version, get_class($this->migrations[$version]));
432
        }
433 68
        $version                                  = new Version($this, $version, $class);
434 68
        $this->migrations[$version->getVersion()] = $version;
435 68
        ksort($this->migrations, SORT_STRING);
436
437 68
        return $version;
438
    }
439
440
    /**
441
     * Register an array of migrations. Each key of the array is the version and
442
     * the value is the migration class name.
443
     *
444
     *
445
     * @param array $migrations
446
     *
447
     * @return Version[]
448
     */
449 91
    public function registerMigrations(array $migrations)
450
    {
451 91
        $versions = [];
452 91
        foreach ($migrations as $version => $class) {
453 34
            $versions[] = $this->registerMigration($version, $class);
454
        }
455
456 90
        return $versions;
457
    }
458
459
    /**
460
     * Get the array of registered migration versions.
461
     *
462
     * @return Version[] $migrations
463
     */
464 35
    public function getMigrations()
465
    {
466 35
        return $this->migrations;
467
    }
468
469
    /**
470
     * Returns the Version instance for a given version in the format YYYYMMDDHHMMSS.
471
     *
472
     * @param string $version The version string in the format YYYYMMDDHHMMSS.
473
     *
474
     * @return Version
475
     *
476
     * @throws MigrationException Throws exception if migration version does not exist.
477
     */
478 20
    public function getVersion($version)
479
    {
480 20
        if (empty($this->migrations)) {
481 7
            $this->registerMigrationsFromDirectory($this->getMigrationsDirectory());
482
        }
483
484 20
        if ( ! isset($this->migrations[$version])) {
485 2
            throw MigrationException::unknownMigrationVersion($version);
486
        }
487
488 18
        return $this->migrations[$version];
489
    }
490
491
    /**
492
     * Check if a version exists.
493
     *
494
     * @param string $version
495
     *
496
     * @return boolean
497
     */
498 22
    public function hasVersion($version)
499
    {
500 22
        if (empty($this->migrations)) {
501 4
            $this->registerMigrationsFromDirectory($this->getMigrationsDirectory());
502
        }
503
504 20
        return isset($this->migrations[$version]);
505
    }
506
507
    /**
508
     * Check if a version has been migrated or not yet
509
     *
510
     * @param Version $version
511
     *
512
     * @return boolean
513
     */
514 21
    public function hasVersionMigrated(Version $version)
515
    {
516 21
        $this->connect();
517 21
        $this->createMigrationTable();
518
519 21
        $version = $this->connection->fetchColumn(
520 21
            "SELECT " . $this->migrationsColumnName . " FROM " . $this->migrationsTableName . " WHERE " . $this->migrationsColumnName . " = ?",
521 21
            [$version->getVersion()]
522
        );
523
524 21
        return $version !== false;
525
    }
526
527
    /**
528
     * Returns all migrated versions from the versions table, in an array.
529
     *
530
     * @return Version[]
531
     */
532 41
    public function getMigratedVersions()
533
    {
534 41
        $this->connect();
535 41
        $this->createMigrationTable();
536
537 41
        $ret = $this->connection->fetchAll("SELECT " . $this->migrationsColumnName . " FROM " . $this->migrationsTableName);
538
539 41
        return array_map('current', $ret);
540
    }
541
542
    /**
543
     * Returns an array of available migration version numbers.
544
     *
545
     * @return array
546
     */
547 16
    public function getAvailableVersions()
548
    {
549 16
        $availableVersions = [];
550
551 16
        if (empty($this->migrations)) {
552 3
            $this->registerMigrationsFromDirectory($this->getMigrationsDirectory());
553
        }
554
555 14
        foreach ($this->migrations as $migration) {
556 14
            $availableVersions[] = $migration->getVersion();
557
        }
558
559 14
        return $availableVersions;
560
    }
561
562
    /**
563
     * Returns the current migrated version from the versions table.
564
     *
565
     * @return string
566
     */
567 43
    public function getCurrentVersion()
568
    {
569 43
        $this->connect();
570 43
        $this->createMigrationTable();
571
572 41
        if (empty($this->migrations)) {
573 15
            $this->registerMigrationsFromDirectory($this->getMigrationsDirectory());
574
        }
575
576 41
        $where = null;
577 41
        if ( ! empty($this->migrations)) {
578 37
            $migratedVersions = [];
579 37
            foreach ($this->migrations as $migration) {
580 37
                $migratedVersions[] = sprintf("'%s'", $migration->getVersion());
581
            }
582 37
            $where = " WHERE " . $this->migrationsColumnName . " IN (" . implode(', ', $migratedVersions) . ")";
583
        }
584
585 41
        $sql = sprintf(
586 41
            "SELECT %s FROM %s%s ORDER BY %s DESC",
587 41
            $this->migrationsColumnName,
588 41
            $this->migrationsTableName,
589 41
            $where,
590 41
            $this->migrationsColumnName
591
        );
592
593 41
        $sql    = $this->connection->getDatabasePlatform()->modifyLimitQuery($sql, 1);
594 41
        $result = $this->connection->fetchColumn($sql);
595
596 41
        return $result !== false ? (string) $result : '0';
597
    }
598
599
    /**
600
     * Returns the version prior to the current version.
601
     *
602
     * @return string|null A version string, or null if the current version is
603
     *                     the first.
604
     */
605 10
    public function getPrevVersion()
606
    {
607 10
        return $this->getRelativeVersion($this->getCurrentVersion(), -1);
608
    }
609
610
    /**
611
     * Returns the version following the current version.
612
     *
613
     * @return string|null A version string, or null if the current version is
614
     *                     the latest.
615
     */
616 10
    public function getNextVersion()
617
    {
618 10
        return $this->getRelativeVersion($this->getCurrentVersion(), 1);
619
    }
620
621
    /**
622
     * Returns the version with the specified offset to the specified version.
623
     *
624
     * @return string|null A version string, or null if the specified version
625
     *                     is unknown or the specified delta is not within the
626
     *                     list of available versions.
627
     */
628 16
    public function getRelativeVersion($version, $delta)
629
    {
630 16
        if (empty($this->migrations)) {
631 5
            $this->registerMigrationsFromDirectory($this->getMigrationsDirectory());
632
        }
633
634 14
        $versions = array_map('strval', array_keys($this->migrations));
635 14
        array_unshift($versions, '0');
636 14
        $offset = array_search((string) $version, $versions);
637 14
        if ($offset === false || ! isset($versions[$offset + $delta])) {
638
            // Unknown version or delta out of bounds.
639 11
            return null;
640
        }
641
642 12
        return $versions[$offset + $delta];
643
    }
644
645
    /**
646
     * Returns the version with the specified to the current version.
647
     *
648
     * @return string|null A version string, or null if the specified delta is
649
     *                     not within the list of available versions.
650
     */
651 1
    public function getDeltaVersion($delta)
652
    {
653 1
        $symbol = substr($delta, 0, 1);
654 1
        $number = (int) substr($delta, 1);
655
656 1
        if ($number <= 0) {
657
            return null;
658
        }
659
660 1
        if ($symbol == "+" || $symbol == "-") {
661 1
            return $this->getRelativeVersion($this->getCurrentVersion(), (int) $delta);
662
        }
663
664
        return null;
665
    }
666
667
    /**
668
     * Returns the version number from an alias.
669
     *
670
     * Supported aliases are:
671
     * - first: The very first version before any migrations have been run.
672
     * - current: The current version.
673
     * - prev: The version prior to the current version.
674
     * - next: The version following the current version.
675
     * - latest: The latest available version.
676
     *
677
     * If an existing version number is specified, it is returned verbatimly.
678
     *
679
     * @return string|null A version number, or null if the specified alias
680
     *                     does not map to an existing version, e.g. if "next"
681
     *                     is passed but the current version is already the
682
     *                     latest.
683
     */
684 9
    public function resolveVersionAlias($alias)
685
    {
686 9
        if ($this->hasVersion($alias)) {
687 1
            return $alias;
688
        }
689
        switch ($alias) {
690 9
            case 'first':
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
691 1
                return '0';
692 9
            case 'current':
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
693 9
                return $this->getCurrentVersion();
694 9
            case 'prev':
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
695 9
                return $this->getPrevVersion();
696 9
            case 'next':
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
697 9
                return $this->getNextVersion();
698 9
            case 'latest':
699 9
                return $this->getLatestVersion();
700
            default:
701 1
                if (substr($alias, 0, 7) == 'current') {
702
                    return $this->getDeltaVersion(substr($alias, 7));
703
                }
704 1
                return null;
705
        }
706
    }
707
708
    /**
709
     * Returns the total number of executed migration versions
710
     *
711
     * @return integer
712
     */
713 1
    public function getNumberOfExecutedMigrations()
714
    {
715 1
        $this->connect();
716 1
        $this->createMigrationTable();
717
718 1
        $result = $this->connection->fetchColumn("SELECT COUNT(" . $this->migrationsColumnName . ") FROM " . $this->migrationsTableName);
719
720 1
        return $result !== false ? $result : 0;
721
    }
722
723
    /**
724
     * Returns the total number of available migration versions
725
     *
726
     * @return integer
727
     */
728 5
    public function getNumberOfAvailableMigrations()
729
    {
730 5
        if (empty($this->migrations)) {
731 4
            $this->registerMigrationsFromDirectory($this->getMigrationsDirectory());
732
        }
733
734 3
        return count($this->migrations);
735
    }
736
737
    /**
738
     * Returns the latest available migration version.
739
     *
740
     * @return string The version string in the format YYYYMMDDHHMMSS.
741
     */
742 28
    public function getLatestVersion()
743
    {
744 28
        if (empty($this->migrations)) {
745 7
            $this->registerMigrationsFromDirectory($this->getMigrationsDirectory());
746
        }
747
748 26
        $versions = array_keys($this->migrations);
749 26
        $latest   = end($versions);
750
751 26
        return $latest !== false ? (string) $latest : '0';
752
    }
753
754
    /**
755
     * Create the migration table to track migrations with.
756
     *
757
     * @return boolean Whether or not the table was created.
758
     */
759 64
    public function createMigrationTable()
760
    {
761 64
        $this->validate();
762
763 62
        if ($this->migrationTableCreated) {
764 51
            return false;
765
        }
766
767 62
        $this->connect();
768 62
        if ($this->connection->getSchemaManager()->tablesExist([$this->migrationsTableName])) {
769 4
            $this->migrationTableCreated = true;
770
771 4
            return false;
772
        }
773
774
        $columns = [
775 61
            $this->migrationsColumnName => new Column($this->migrationsColumnName, Type::getType('string'), ['length' => 255]),
776
        ];
777 61
        $table   = new Table($this->migrationsTableName, $columns);
778 61
        $table->setPrimaryKey([$this->migrationsColumnName]);
779 61
        $this->connection->getSchemaManager()->createTable($table);
780
781 61
        $this->migrationTableCreated = true;
782
783 61
        return true;
784
    }
785
786
    /**
787
     * Returns the array of migrations to executed based on the given direction
788
     * and target version number.
789
     *
790
     * @param string $direction The direction we are migrating.
791
     * @param string $to        The version to migrate to.
792
     *
793
     * @return Version[] $migrations   The array of migrations we can execute.
794
     */
795 39
    public function getMigrationsToExecute($direction, $to)
796
    {
797 39
        if (empty($this->migrations)) {
798 11
            $this->registerMigrationsFromDirectory($this->getMigrationsDirectory());
799
        }
800
801 33
        if ($direction === Version::DIRECTION_DOWN) {
802 7
            if (count($this->migrations)) {
803 7
                $allVersions = array_reverse(array_keys($this->migrations));
804 7
                $classes     = array_reverse(array_values($this->migrations));
805 7
                $allVersions = array_combine($allVersions, $classes);
806
            } else {
807 7
                $allVersions = [];
808
            }
809
        } else {
810 31
            $allVersions = $this->migrations;
811
        }
812 33
        $versions = [];
813 33
        $migrated = $this->getMigratedVersions();
814 33
        foreach ($allVersions as $version) {
815 31
            if ($this->shouldExecuteMigration($direction, $version, $to, $migrated)) {
816 31
                $versions[$version->getVersion()] = $version;
817
            }
818
        }
819
820 33
        return $versions;
821
    }
822
823
    /**
824
     * Use the connection's event manager to emit an event.
825
     *
826
     * @param string $eventName The event to emit.
827
     * @param $args The event args instance to emit.
828
     */
829 46
    public function dispatchEvent($eventName, EventArgs $args = null)
830
    {
831 46
        $this->connection->getEventManager()->dispatchEvent($eventName, $args);
832 46
    }
833
834
    /**
835
     * Find all the migrations in a given directory.
836
     *
837
     * @param   string $path the directory to search.
838
     * @return  array
839
     */
840 87
    protected function findMigrations($path)
841
    {
842 87
        return $this->migrationFinder->findMigrations($path, $this->getMigrationsNamespace());
843
    }
844
845
    /**
846
     * @param bool $migrationsAreOrganizedByYear
847
     * @throws MigrationException
848
     */
849 9
    public function setMigrationsAreOrganizedByYear($migrationsAreOrganizedByYear = true)
850
    {
851 9
        $this->ensureOrganizeMigrationsIsCompatibleWithFinder();
852
853 5
        $this->migrationsAreOrganizedByYear = $migrationsAreOrganizedByYear;
854 5
    }
855
856
    /**
857
     * @param bool $migrationsAreOrganizedByYearAndMonth
858
     * @throws MigrationException
859
     */
860 10
    public function setMigrationsAreOrganizedByYearAndMonth($migrationsAreOrganizedByYearAndMonth = true)
861
    {
862 10
        $this->ensureOrganizeMigrationsIsCompatibleWithFinder();
863
864 10
        $this->migrationsAreOrganizedByYear         = $migrationsAreOrganizedByYearAndMonth;
865 10
        $this->migrationsAreOrganizedByYearAndMonth = $migrationsAreOrganizedByYearAndMonth;
866 10
    }
867
868
    /**
869
     * Generate a new migration version. A version is (usually) a datetime string.
870
     *
871
     * @param \DateTimeInterface|null $now Defaults to the current time in UTC
872
     * @return string The newly generated version
873
     */
874 9
    public function generateVersionNumber(\DateTimeInterface $now = null)
875
    {
876 9
        $now = $now ?: new \DateTime('now', new \DateTimeZone('UTC'));
877
878 9
        return $now->format(self::VERSION_FORMAT);
879
    }
880
881
    /**
882
     * Explicitely opens the database connection. This is done to play nice
883
     * with DBAL's MasterSlaveConnection. Which, in some cases, connects to a
884
     * follower when fetching the executed migrations. If a follower is lagging
885
     * significantly behind that means the migrations system may see unexecuted
886
     * migrations that were actually executed earlier.
887
     *
888
     * @return bool The same value returned from the `connect` method
889
     */
890 64
    protected function connect()
891
    {
892 64
        if ($this->connection instanceof MasterSlaveConnection) {
893 1
            return $this->connection->connect('master');
894
        }
895
896 63
        return $this->connection->connect();
897
    }
898
899
    /**
900
     * @throws MigrationException
901
     */
902 19
    private function ensureOrganizeMigrationsIsCompatibleWithFinder()
903
    {
904 19
        if ( ! ($this->migrationFinder instanceof MigrationDeepFinderInterface)) {
905 4
            throw MigrationException::configurationIncompatibleWithFinder(
906 4
                'organize-migrations',
907 4
                $this->migrationFinder
908
            );
909
        }
910 15
    }
911
912
    /**
913
     * Check if we should execute a migration for a given direction and target
914
     * migration version.
915
     *
916
     * @param string  $direction The direction we are migrating.
917
     * @param Version $version   The Version instance to check.
918
     * @param string  $to        The version we are migrating to.
919
     * @param array   $migrated  Migrated versions array.
920
     *
921
     * @return boolean
922
     */
923 31
    private function shouldExecuteMigration($direction, Version $version, $to, $migrated)
924
    {
925 31
        if ($direction === Version::DIRECTION_DOWN) {
926 7
            if ( ! in_array($version->getVersion(), $migrated)) {
927 4
                return false;
928
            }
929
930 5
            return $version->getVersion() > $to;
931
        }
932
933 29
        if ($direction === Version::DIRECTION_UP) {
934 29
            if (in_array($version->getVersion(), $migrated)) {
935 8
                return false;
936
            }
937
938 28
            return $version->getVersion() <= $to;
939
        }
940
    }
941
942
    /**
943
     * @param string $class
944
     */
945 69
    private function ensureMigrationClassExists($class)
946
    {
947 69
        if ( ! class_exists($class)) {
948 1
            throw MigrationException::migrationClassNotFound($class, $this->getMigrationsNamespace());
949
        }
950 68
    }
951
952 8
    public function getQueryWriter() : QueryWriter
953
    {
954 8
        if ($this->queryWriter === null) {
955 7
            $this->queryWriter = new FileQueryWriter(
956 7
                $this->migrationsColumnName,
957 7
                $this->migrationsTableName,
958 7
                $this->outputWriter
959
            );
960
        }
961
962 8
        return $this->queryWriter;
963
    }
964
}
965