Passed
Push — master ( 50e9c9...634f59 )
by Mike
03:32
created

Configuration::getQuotedMigrationsColumnName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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