Failed Conditions
Push — master ( b92363...10368b )
by Mike
08:33
created

Configuration::setIsDryRun()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 3
cts 3
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
crap 1
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
     * Prevent write queries.
148
     *
149
     * @var bool
150
     */
151
    private $isDryRun = false;
152
153
    /**
154
     * Construct a migration configuration object.
155
     *
156
     * @param Connection               $connection   A Connection instance
157
     * @param OutputWriter             $outputWriter A OutputWriter instance
158
     * @param MigrationFinderInterface $finder       Migration files finder
159
     * @param QueryWriter|null         $queryWriter
160
     */
161 279
    public function __construct(
162
        Connection $connection,
163
        OutputWriter $outputWriter = null,
164
        MigrationFinderInterface $finder = null,
165
        QueryWriter $queryWriter = null
166
    ) {
167 279
        $this->connection      = $connection;
168 279
        $this->outputWriter    = $outputWriter ?? new OutputWriter();
169 279
        $this->migrationFinder = $finder ?? new RecursiveRegexFinder();
170 279
        $this->queryWriter     = $queryWriter;
171 279
    }
172
173
    /**
174
     * @return bool
175
     */
176 22
    public function areMigrationsOrganizedByYear()
177
    {
178 22
        return $this->migrationsAreOrganizedByYear;
179
    }
180
181
    /**
182
     * @return bool
183
     */
184 22
    public function areMigrationsOrganizedByYearAndMonth()
185
    {
186 22
        return $this->migrationsAreOrganizedByYearAndMonth;
187
    }
188
189
    /**
190
     * Validation that this instance has all the required properties configured
191
     *
192
     * @throws MigrationException
193
     */
194 147
    public function validate()
195
    {
196 147
        if ( ! $this->migrationsNamespace) {
197 10
            throw MigrationException::migrationsNamespaceRequired();
198
        }
199 137
        if ( ! $this->migrationsDirectory) {
200 10
            throw MigrationException::migrationsDirectoryRequired();
201
        }
202 127
    }
203
204
    /**
205
     * Set the name of this set of migrations
206
     *
207
     * @param string $name The name of this set of migrations
208
     */
209 63
    public function setName($name)
210
    {
211 63
        $this->name = $name;
212 63
    }
213
214
    /**
215
     * Returns the name of this set of migrations
216
     *
217
     * @return string $name The name of this set of migrations
218
     */
219 16
    public function getName()
220
    {
221 16
        return $this->name;
222
    }
223
224
    /**
225
     * Sets the output writer.
226
     *
227
     * @param OutputWriter $outputWriter
228
     */
229 3
    public function setOutputWriter(OutputWriter $outputWriter)
230
    {
231 3
        $this->outputWriter = $outputWriter;
232 3
    }
233
234
    /**
235
     * Returns the OutputWriter instance
236
     *
237
     * @return OutputWriter $outputWriter  The OutputWriter instance
238
     */
239 119
    public function getOutputWriter()
240
    {
241 119
        return $this->outputWriter;
242
    }
243
244
    /**
245
     * Returns a timestamp version as a formatted date
246
     *
247
     * @param string $version
248
     *
249
     * @return string The formatted version
250
     * @deprecated
251
     */
252 14
    public function formatVersion($version)
253
    {
254 14
        return $this->getDateTime($version);
255
    }
256
257
    /**
258
     * Returns the datetime of a migration
259
     *
260
     * @param string $version
261
     * @return string
262
     */
263 22
    public function getDateTime($version)
264
    {
265 22
        $datetime = str_replace('Version', '', $version);
266 22
        $datetime = \DateTime::createFromFormat('YmdHis', $datetime);
267
268 22
        if ($datetime === false) {
269 8
            return '';
270
        }
271
272 15
        return $datetime->format('Y-m-d H:i:s');
273
    }
274
275
    /**
276
     * Returns the Connection instance
277
     *
278
     * @return Connection $connection  The Connection instance
279
     */
280 122
    public function getConnection()
281
    {
282 122
        return $this->connection;
283
    }
284
285
    /**
286
     * Set the migration table name
287
     *
288
     * @param string $tableName The migration table name
289
     */
290 85
    public function setMigrationsTableName($tableName)
291
    {
292 85
        $this->migrationsTableName = $tableName;
293 85
    }
294
295
    /**
296
     * Returns the migration table name
297
     *
298
     * @return string $migrationsTableName The migration table name
299
     */
300 61
    public function getMigrationsTableName()
301
    {
302 61
        return $this->migrationsTableName;
303
    }
304
305
    /**
306
     * Set the migration column name
307
     *
308
     * @param string $columnName The migration column name
309
     */
310 56
    public function setMigrationsColumnName($columnName)
311
    {
312 56
        $this->migrationsColumnName = $columnName;
313 56
    }
314
315
    /**
316
     * Returns the migration column name
317
     *
318
     * @return string $migrationsColumnName The migration column name
319
     */
320 55
    public function getMigrationsColumnName()
321
    {
322 55
        return $this->migrationsColumnName;
323
    }
324
325
    /**
326
     * Set the new migrations directory where new migration classes are generated
327
     *
328
     * @param string $migrationsDirectory The new migrations directory
329
     */
330 184
    public function setMigrationsDirectory($migrationsDirectory)
331
    {
332 184
        $this->migrationsDirectory = $migrationsDirectory;
333 184
    }
334
335
    /**
336
     * Returns the new migrations directory where new migration classes are generated
337
     *
338
     * @return string $migrationsDirectory The new migrations directory
339
     */
340 66
    public function getMigrationsDirectory()
341
    {
342 66
        return $this->migrationsDirectory;
343
    }
344
345
    /**
346
     * Set the migrations namespace
347
     *
348
     * @param string $migrationsNamespace The migrations namespace
349
     */
350 196
    public function setMigrationsNamespace($migrationsNamespace)
351
    {
352 196
        $this->migrationsNamespace = $migrationsNamespace;
353 196
    }
354
355
    /**
356
     * Returns the migrations namespace
357
     *
358
     * @return string $migrationsNamespace The migrations namespace
359
     */
360 93
    public function getMigrationsNamespace()
361
    {
362 93
        return $this->migrationsNamespace;
363
    }
364
365
    /**
366
     * Returns the custom template path
367
     *
368
     * @return string $customTemplate The custom template path
369
     */
370 9
    public function getCustomTemplate() : ?string
371
    {
372 9
        return $this->customTemplate;
373
    }
374
375
    /**
376
     * Set the custom template path
377
     *
378
     * @param string $customTemplate The custom template path
379
     */
380 5
    public function setCustomTemplate(?string $customTemplate) : void
381
    {
382 5
        $this->customTemplate = $customTemplate;
383 5
    }
384
385
    /**
386
     * Set the implementation of the migration finder.
387
     *
388
     * @param MigrationFinderInterface $finder The new migration finder
389
     * @throws MigrationException
390
     */
391 8
    public function setMigrationsFinder(MigrationFinderInterface $finder)
392
    {
393 8
        if (($this->migrationsAreOrganizedByYear || $this->migrationsAreOrganizedByYearAndMonth)
394 8
            && ! ($finder instanceof MigrationDeepFinderInterface)) {
395 4
            throw MigrationException::configurationIncompatibleWithFinder(
396 4
                'organize-migrations',
397 4
                $finder
398
            );
399
        }
400
401 4
        $this->migrationFinder = $finder;
402 4
    }
403
404
    /**
405
     * Register migrations from a given directory. Recursively finds all files
406
     * with the pattern VersionYYYYMMDDHHMMSS.php as the filename and registers
407
     * them as migrations.
408
     *
409
     * @param string $path The root directory to where some migration classes live.
410
     *
411
     * @return Version[] The array of migrations registered.
412
     */
413 104
    public function registerMigrationsFromDirectory($path)
414
    {
415 104
        $this->validate();
416
417 88
        return $this->registerMigrations($this->findMigrations($path));
418
    }
419
420
    /**
421
     * Register a single migration version to be executed by a AbstractMigration
422
     * class.
423
     *
424
     * @param string $version The version of the migration in the format YYYYMMDDHHMMSS.
425
     * @param string $class   The migration class to execute for the version.
426
     *
427
     * @return Version
428
     *
429
     * @throws MigrationException
430
     */
431 70
    public function registerMigration($version, $class)
432
    {
433 70
        $this->ensureMigrationClassExists($class);
434
435 69
        $version = (string) $version;
436 69
        $class   = (string) $class;
437 69
        if (isset($this->migrations[$version])) {
438 1
            throw MigrationException::duplicateMigrationVersion($version, get_class($this->migrations[$version]));
439
        }
440 69
        $version                                  = new Version($this, $version, $class);
441 69
        $this->migrations[$version->getVersion()] = $version;
442 69
        ksort($this->migrations, SORT_STRING);
443
444 69
        return $version;
445
    }
446
447
    /**
448
     * Register an array of migrations. Each key of the array is the version and
449
     * the value is the migration class name.
450
     *
451
     *
452
     * @param array $migrations
453
     *
454
     * @return Version[]
455
     */
456 92
    public function registerMigrations(array $migrations)
457
    {
458 92
        $versions = [];
459 92
        foreach ($migrations as $version => $class) {
460 35
            $versions[] = $this->registerMigration($version, $class);
461
        }
462
463 91
        return $versions;
464
    }
465
466
    /**
467
     * Get the array of registered migration versions.
468
     *
469
     * @return Version[] $migrations
470
     */
471 35
    public function getMigrations()
472
    {
473 35
        return $this->migrations;
474
    }
475
476
    /**
477
     * Returns the Version instance for a given version in the format YYYYMMDDHHMMSS.
478
     *
479
     * @param string $version The version string in the format YYYYMMDDHHMMSS.
480
     *
481
     * @return Version
482
     *
483
     * @throws MigrationException Throws exception if migration version does not exist.
484
     */
485 21
    public function getVersion($version)
486
    {
487 21
        if (empty($this->migrations)) {
488 7
            $this->registerMigrationsFromDirectory($this->getMigrationsDirectory());
489
        }
490
491 21
        if ( ! isset($this->migrations[$version])) {
492 2
            throw MigrationException::unknownMigrationVersion($version);
493
        }
494
495 19
        return $this->migrations[$version];
496
    }
497
498
    /**
499
     * Check if a version exists.
500
     *
501
     * @param string $version
502
     *
503
     * @return boolean
504
     */
505 22
    public function hasVersion($version)
506
    {
507 22
        if (empty($this->migrations)) {
508 4
            $this->registerMigrationsFromDirectory($this->getMigrationsDirectory());
509
        }
510
511 20
        return isset($this->migrations[$version]);
512
    }
513
514
    /**
515
     * Check if a version has been migrated or not yet
516
     *
517
     * @param Version $version
518
     *
519
     * @return boolean
520
     */
521 21
    public function hasVersionMigrated(Version $version)
522
    {
523 21
        $this->connect();
524 21
        $this->createMigrationTable();
525
526 21
        $version = $this->connection->fetchColumn(
527 21
            "SELECT " . $this->migrationsColumnName . " FROM " . $this->migrationsTableName . " WHERE " . $this->migrationsColumnName . " = ?",
528 21
            [$version->getVersion()]
529
        );
530
531 21
        return $version !== false;
532
    }
533
534
    /**
535
     * Returns all migrated versions from the versions table, in an array.
536
     *
537
     * @return Version[]
538
     */
539 42
    public function getMigratedVersions()
540
    {
541 42
        $this->createMigrationTable();
542
543 42
        if ( ! $this->migrationTableCreated && $this->isDryRun) {
544 1
            return [];
545
        }
546
547 42
        $this->connect();
548
549 42
        $ret = $this->connection->fetchAll("SELECT " . $this->migrationsColumnName . " FROM " . $this->migrationsTableName);
550
551 42
        return array_map('current', $ret);
552
    }
553
554
    /**
555
     * Returns an array of available migration version numbers.
556
     *
557
     * @return array
558
     */
559 16
    public function getAvailableVersions()
560
    {
561 16
        $availableVersions = [];
562
563 16
        if (empty($this->migrations)) {
564 3
            $this->registerMigrationsFromDirectory($this->getMigrationsDirectory());
565
        }
566
567 14
        foreach ($this->migrations as $migration) {
568 14
            $availableVersions[] = $migration->getVersion();
569
        }
570
571 14
        return $availableVersions;
572
    }
573
574
    /**
575
     * Returns the current migrated version from the versions table.
576
     *
577
     * @return string
578
     */
579 44
    public function getCurrentVersion()
580
    {
581 44
        $this->createMigrationTable();
582
583 42
        if ( ! $this->migrationTableCreated && $this->isDryRun) {
584 1
            return '0';
585
        }
586
587 42
        $this->connect();
588
589 42
        if (empty($this->migrations)) {
590 16
            $this->registerMigrationsFromDirectory($this->getMigrationsDirectory());
591
        }
592
593 42
        $where = null;
594 42
        if ( ! empty($this->migrations)) {
595 38
            $migratedVersions = [];
596 38
            foreach ($this->migrations as $migration) {
597 38
                $migratedVersions[] = sprintf("'%s'", $migration->getVersion());
598
            }
599 38
            $where = " WHERE " . $this->migrationsColumnName . " IN (" . implode(', ', $migratedVersions) . ")";
600
        }
601
602 42
        $sql = sprintf(
603 42
            "SELECT %s FROM %s%s ORDER BY %s DESC",
604 42
            $this->migrationsColumnName,
605 42
            $this->migrationsTableName,
606 42
            $where,
607 42
            $this->migrationsColumnName
608
        );
609
610 42
        $sql    = $this->connection->getDatabasePlatform()->modifyLimitQuery($sql, 1);
611 42
        $result = $this->connection->fetchColumn($sql);
612
613 42
        return $result !== false ? (string) $result : '0';
614
    }
615
616
    /**
617
     * Returns the version prior to the current version.
618
     *
619
     * @return string|null A version string, or null if the current version is
620
     *                     the first.
621
     */
622 10
    public function getPrevVersion()
623
    {
624 10
        return $this->getRelativeVersion($this->getCurrentVersion(), -1);
625
    }
626
627
    /**
628
     * Returns the version following the current version.
629
     *
630
     * @return string|null A version string, or null if the current version is
631
     *                     the latest.
632
     */
633 11
    public function getNextVersion()
634
    {
635 11
        return $this->getRelativeVersion($this->getCurrentVersion(), 1);
636
    }
637
638
    /**
639
     * Returns the version with the specified offset to the specified version.
640
     *
641
     * @param string $version
642
     * @param string $delta
643
     * @return null|string A version string, or null if the specified version
644
     *                     is unknown or the specified delta is not within the
645
     *                     list of available versions.
646
     */
647 17
    public function getRelativeVersion($version, $delta)
648
    {
649 17
        if (empty($this->migrations)) {
650 5
            $this->registerMigrationsFromDirectory($this->getMigrationsDirectory());
651
        }
652
653 15
        $versions = array_map('strval', array_keys($this->migrations));
654 15
        array_unshift($versions, '0');
655 15
        $offset = array_search((string) $version, $versions);
656 15
        if ($offset === false || ! isset($versions[$offset + $delta])) {
657
            // Unknown version or delta out of bounds.
658 11
            return null;
659
        }
660
661 13
        return $versions[$offset + $delta];
662
    }
663
664
    /**
665
     * Returns the version with the specified to the current version.
666
     *
667
     * @param string $delta
668
     * @return null|string A version string, or null if the specified delta is
669
     *                     not within the list of available versions.
670
     */
671 1
    public function getDeltaVersion($delta)
672
    {
673 1
        $symbol = substr($delta, 0, 1);
674 1
        $number = (int) substr($delta, 1);
675
676 1
        if ($number <= 0) {
677
            return null;
678
        }
679
680 1
        if ($symbol == "+" || $symbol == "-") {
681 1
            return $this->getRelativeVersion($this->getCurrentVersion(), (int) $delta);
682
        }
683
684
        return null;
685
    }
686
687
    /**
688
     * Returns the version number from an alias.
689
     *
690
     * Supported aliases are:
691
     * - first: The very first version before any migrations have been run.
692
     * - current: The current version.
693
     * - prev: The version prior to the current version.
694
     * - next: The version following the current version.
695
     * - latest: The latest available version.
696
     *
697
     * If an existing version number is specified, it is returned verbatimly.
698
     *
699
     * @param string $alias
700
     * @return null|string A version number, or null if the specified alias
701
     *                     does not map to an existing version, e.g. if "next"
702
     *                     is passed but the current version is already the
703
     *                     latest.
704
     */
705 9
    public function resolveVersionAlias($alias)
706
    {
707 9
        if ($this->hasVersion($alias)) {
708 1
            return $alias;
709
        }
710
        switch ($alias) {
711 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...
712 1
                return '0';
713 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...
714 9
                return $this->getCurrentVersion();
715 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...
716 9
                return $this->getPrevVersion();
717 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...
718 9
                return $this->getNextVersion();
719 9
            case 'latest':
720 9
                return $this->getLatestVersion();
721
            default:
722 1
                if (substr($alias, 0, 7) == 'current') {
723
                    return $this->getDeltaVersion(substr($alias, 7));
724
                }
725 1
                return null;
726
        }
727
    }
728
729
    /**
730
     * Returns the total number of executed migration versions
731
     *
732
     * @return integer
733
     */
734 1
    public function getNumberOfExecutedMigrations()
735
    {
736 1
        $this->connect();
737 1
        $this->createMigrationTable();
738
739 1
        $result = $this->connection->fetchColumn("SELECT COUNT(" . $this->migrationsColumnName . ") FROM " . $this->migrationsTableName);
740
741 1
        return $result !== false ? $result : 0;
742
    }
743
744
    /**
745
     * Returns the total number of available migration versions
746
     *
747
     * @return integer
748
     */
749 5
    public function getNumberOfAvailableMigrations()
750
    {
751 5
        if (empty($this->migrations)) {
752 4
            $this->registerMigrationsFromDirectory($this->getMigrationsDirectory());
753
        }
754
755 3
        return count($this->migrations);
756
    }
757
758
    /**
759
     * Returns the latest available migration version.
760
     *
761
     * @return string The version string in the format YYYYMMDDHHMMSS.
762
     */
763 28
    public function getLatestVersion()
764
    {
765 28
        if (empty($this->migrations)) {
766 7
            $this->registerMigrationsFromDirectory($this->getMigrationsDirectory());
767
        }
768
769 26
        $versions = array_keys($this->migrations);
770 26
        $latest   = end($versions);
771
772 26
        return $latest !== false ? (string) $latest : '0';
773
    }
774
775
    /**
776
     * Create the migration table to track migrations with.
777
     *
778
     * @return boolean Whether or not the table was created.
779
     */
780 65
    public function createMigrationTable()
781
    {
782 65
        $this->validate();
783
784 63
        if ($this->migrationTableCreated) {
785 52
            return false;
786
        }
787
788 63
        $this->connect();
789 63
        if ($this->connection->getSchemaManager()->tablesExist([$this->migrationsTableName])) {
790 4
            $this->migrationTableCreated = true;
791
792 4
            return false;
793
        }
794
795 62
        if ($this->isDryRun) {
796 1
            return false;
797
        }
798
799
        $columns = [
800 62
            $this->migrationsColumnName => new Column($this->migrationsColumnName, Type::getType('string'), ['length' => 255]),
801
        ];
802 62
        $table   = new Table($this->migrationsTableName, $columns);
803 62
        $table->setPrimaryKey([$this->migrationsColumnName]);
804 62
        $this->connection->getSchemaManager()->createTable($table);
805
806 62
        $this->migrationTableCreated = true;
807
808 62
        return true;
809
    }
810
811
    /**
812
     * Returns the array of migrations to executed based on the given direction
813
     * and target version number.
814
     *
815
     * @param string $direction The direction we are migrating.
816
     * @param string $to        The version to migrate to.
817
     *
818
     * @return Version[] $migrations   The array of migrations we can execute.
819
     */
820 39
    public function getMigrationsToExecute($direction, $to)
821
    {
822 39
        if (empty($this->migrations)) {
823 11
            $this->registerMigrationsFromDirectory($this->getMigrationsDirectory());
824
        }
825
826 33
        if ($direction === Version::DIRECTION_DOWN) {
827 7
            if (count($this->migrations)) {
828 7
                $allVersions = array_reverse(array_keys($this->migrations));
829 7
                $classes     = array_reverse(array_values($this->migrations));
830 7
                $allVersions = array_combine($allVersions, $classes);
831
            } else {
832 7
                $allVersions = [];
833
            }
834
        } else {
835 31
            $allVersions = $this->migrations;
836
        }
837 33
        $versions = [];
838 33
        $migrated = $this->getMigratedVersions();
839 33
        foreach ($allVersions as $version) {
840 31
            if ($this->shouldExecuteMigration($direction, $version, $to, $migrated)) {
841 31
                $versions[$version->getVersion()] = $version;
842
            }
843
        }
844
845 33
        return $versions;
846
    }
847
848
    /**
849
     * Use the connection's event manager to emit an event.
850
     *
851
     * @param string $eventName The event to emit.
852
     * @param EventArgs $args The event args instance to emit.
853
     */
854 46
    public function dispatchEvent($eventName, EventArgs $args = null)
855
    {
856 46
        $this->connection->getEventManager()->dispatchEvent($eventName, $args);
857 46
    }
858
859
    /**
860
     * Find all the migrations in a given directory.
861
     *
862
     * @param   string $path the directory to search.
863
     * @return  array
864
     */
865 88
    protected function findMigrations($path)
866
    {
867 88
        return $this->migrationFinder->findMigrations($path, $this->getMigrationsNamespace());
868
    }
869
870
    /**
871
     * @param bool $migrationsAreOrganizedByYear
872
     * @throws MigrationException
873
     */
874 9
    public function setMigrationsAreOrganizedByYear($migrationsAreOrganizedByYear = true)
875
    {
876 9
        $this->ensureOrganizeMigrationsIsCompatibleWithFinder();
877
878 5
        $this->migrationsAreOrganizedByYear = $migrationsAreOrganizedByYear;
879 5
    }
880
881
    /**
882
     * @param bool $migrationsAreOrganizedByYearAndMonth
883
     * @throws MigrationException
884
     */
885 10
    public function setMigrationsAreOrganizedByYearAndMonth($migrationsAreOrganizedByYearAndMonth = true)
886
    {
887 10
        $this->ensureOrganizeMigrationsIsCompatibleWithFinder();
888
889 10
        $this->migrationsAreOrganizedByYear         = $migrationsAreOrganizedByYearAndMonth;
890 10
        $this->migrationsAreOrganizedByYearAndMonth = $migrationsAreOrganizedByYearAndMonth;
891 10
    }
892
893
    /**
894
     * Generate a new migration version. A version is (usually) a datetime string.
895
     *
896
     * @param \DateTimeInterface|null $now Defaults to the current time in UTC
897
     * @return string The newly generated version
898
     */
899 9
    public function generateVersionNumber(\DateTimeInterface $now = null)
900
    {
901 9
        $now = $now ?: new \DateTime('now', new \DateTimeZone('UTC'));
902
903 9
        return $now->format(self::VERSION_FORMAT);
904
    }
905
906
    /**
907
     * Explicitely opens the database connection. This is done to play nice
908
     * with DBAL's MasterSlaveConnection. Which, in some cases, connects to a
909
     * follower when fetching the executed migrations. If a follower is lagging
910
     * significantly behind that means the migrations system may see unexecuted
911
     * migrations that were actually executed earlier.
912
     *
913
     * @return bool The same value returned from the `connect` method
914
     */
915 63
    protected function connect()
916
    {
917 63
        if ($this->connection instanceof MasterSlaveConnection) {
918 1
            return $this->connection->connect('master');
919
        }
920
921 62
        return $this->connection->connect();
922
    }
923
924
    /**
925
     * @throws MigrationException
926
     */
927 19
    private function ensureOrganizeMigrationsIsCompatibleWithFinder()
928
    {
929 19
        if ( ! ($this->migrationFinder instanceof MigrationDeepFinderInterface)) {
930 4
            throw MigrationException::configurationIncompatibleWithFinder(
931 4
                'organize-migrations',
932 4
                $this->migrationFinder
933
            );
934
        }
935 15
    }
936
937
    /**
938
     * Check if we should execute a migration for a given direction and target
939
     * migration version.
940
     *
941
     * @param string  $direction The direction we are migrating.
942
     * @param Version $version   The Version instance to check.
943
     * @param string  $to        The version we are migrating to.
944
     * @param array   $migrated  Migrated versions array.
945
     *
946
     * @return boolean
947
     */
948 31
    private function shouldExecuteMigration($direction, Version $version, $to, $migrated)
949
    {
950 31
        if ($direction === Version::DIRECTION_DOWN) {
951 7
            if ( ! in_array($version->getVersion(), $migrated)) {
952 4
                return false;
953
            }
954
955 5
            return $version->getVersion() > $to;
956
        }
957
958 29
        if ($direction === Version::DIRECTION_UP) {
959 29
            if (in_array($version->getVersion(), $migrated)) {
960 8
                return false;
961
            }
962
963 28
            return $version->getVersion() <= $to;
964
        }
965
    }
966
967
    /**
968
     * @param string $class
969
     * @throws MigrationException
970
     */
971 70
    private function ensureMigrationClassExists($class)
972
    {
973 70
        if ( ! class_exists($class)) {
974 1
            throw MigrationException::migrationClassNotFound($class, $this->getMigrationsNamespace());
975
        }
976 69
    }
977
978 8
    public function getQueryWriter() : QueryWriter
979
    {
980 8
        if ($this->queryWriter === null) {
981 7
            $this->queryWriter = new FileQueryWriter(
982 7
                $this->migrationsColumnName,
983 7
                $this->migrationsTableName,
984 7
                $this->outputWriter
985
            );
986
        }
987
988 8
        return $this->queryWriter;
989
    }
990
991
    /**
992
     * @param bool $isDryRun
993
     */
994 2
    public function setIsDryRun($isDryRun)
995
    {
996 2
        $this->isDryRun = $isDryRun;
997 2
    }
998
}
999