Failed Conditions
Push — master ( 218fce...20dc1f )
by Luís
12:56 queued 05:38
created

Configuration::getCustomTemplate()   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
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the LGPL. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\DBAL\Migrations\Configuration;
21
22
use Doctrine\Common\EventArgs;
23
use Doctrine\DBAL\Connection;
24
use Doctrine\DBAL\Connections\MasterSlaveConnection;
25
use Doctrine\DBAL\Migrations\FileQueryWriter;
26
use Doctrine\DBAL\Migrations\Finder\MigrationDeepFinderInterface;
27
use Doctrine\DBAL\Migrations\MigrationException;
28
use Doctrine\DBAL\Migrations\OutputWriter;
29
use Doctrine\DBAL\Migrations\QueryWriter;
30
use Doctrine\DBAL\Migrations\Version;
31
use Doctrine\DBAL\Migrations\Finder\MigrationFinderInterface;
32
use Doctrine\DBAL\Migrations\Finder\RecursiveRegexFinder;
33
use Doctrine\DBAL\Schema\Column;
34
use Doctrine\DBAL\Schema\Table;
35
use Doctrine\DBAL\Types\Type;
36
37
/**
38
 * Default Migration Configuration object used for configuring an instance of
39
 * the Migration class. Set the connection, version table name, register migration
40
 * classes/versions, etc.
41
 *
42
 * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
43
 * @link        www.doctrine-project.org
44
 * @since       2.0
45
 * @author      Jonathan H. Wage <[email protected]>
46
 */
47
class Configuration
48
{
49
    /**
50
     * Configure versions to be organized by year.
51
     */
52
    const VERSIONS_ORGANIZATION_BY_YEAR = 'year';
53
54
    /**
55
     * Configure versions to be organized by year and month.
56
     *
57
     * @var string
58
     */
59
    const VERSIONS_ORGANIZATION_BY_YEAR_AND_MONTH = 'year_and_month';
60
61
    /**
62
     * The date format for new version numbers
63
     */
64
    const VERSION_FORMAT = 'YmdHis';
65
66
    /**
67
     * Name of this set of migrations
68
     *
69
     * @var string
70
     */
71
    private $name;
72
73
    /**
74
     * Flag for whether or not the migration table has been created
75
     *
76
     * @var boolean
77
     */
78
    private $migrationTableCreated = false;
79
80
    /**
81
     * Connection instance to use for migrations
82
     *
83
     * @var Connection
84
     */
85
    private $connection;
86
87
    /**
88
     * OutputWriter instance for writing output during migrations
89
     *
90
     * @var OutputWriter
91
     */
92
    private $outputWriter;
93
94
    /**
95
     * The migration finder implementation -- used to load migrations from a
96
     * directory.
97
     *
98
     * @var MigrationFinderInterface
99
     */
100
    private $migrationFinder;
101
102
    /**
103
     * @var QueryWriter
104
     */
105
    private $queryWriter;
106
107
    /**
108
     * The migration table name to track versions in
109
     *
110
     * @var string
111
     */
112
    private $migrationsTableName = 'doctrine_migration_versions';
113
114
    /**
115
     * The migration column name to track versions in
116
     *
117
     * @var string
118
     */
119
    private $migrationsColumnName = 'version';
120
121
    /**
122
     * The path to a directory where new migration classes will be written
123
     *
124
     * @var string
125
     */
126
    private $migrationsDirectory;
127
128
    /**
129
     * Namespace the migration classes live in
130
     *
131
     * @var string
132
     */
133
    private $migrationsNamespace;
134
135
    /**
136
     * Array of the registered migrations
137
     *
138
     * @var Version[]
139
     */
140
    private $migrations = [];
141
142
    /**
143
     * Versions are organized by year.
144
     *
145
     * @var boolean
146
     */
147
    private $migrationsAreOrganizedByYear = false;
148
149
    /**
150
     * Versions are organized by year and month.
151
     *
152
     * @var boolean
153
     */
154
    private $migrationsAreOrganizedByYearAndMonth = false;
155
156
    /**
157
     * The custom template path to be used in generate command
158
     *
159
     * @var string
160
     */
161
    private $customTemplate;
162
163
    /**
164
     * Construct a migration configuration object.
165
     *
166
     * @param Connection               $connection   A Connection instance
167
     * @param OutputWriter             $outputWriter A OutputWriter instance
168
     * @param MigrationFinderInterface $finder       Migration files finder
169
     * @param QueryWriter|null         $queryWriter
170
     */
171 275
    public function __construct(
172
        Connection $connection,
173
        OutputWriter $outputWriter = null,
174
        MigrationFinderInterface $finder = null,
175
        QueryWriter $queryWriter = null
176
    ) {
177 275
        $this->connection      = $connection;
178 275
        $this->outputWriter    = $outputWriter ?? new OutputWriter();
179 275
        $this->migrationFinder = $finder ?? new RecursiveRegexFinder();
180 275
        $this->queryWriter     = $queryWriter;
181 275
    }
182
183
    /**
184
     * @return bool
185
     */
186 22
    public function areMigrationsOrganizedByYear()
187
    {
188 22
        return $this->migrationsAreOrganizedByYear;
189
    }
190
191
    /**
192
     * @return bool
193
     */
194 22
    public function areMigrationsOrganizedByYearAndMonth()
195
    {
196 22
        return $this->migrationsAreOrganizedByYearAndMonth;
197
    }
198
199
    /**
200
     * Validation that this instance has all the required properties configured
201
     *
202
     * @throws MigrationException
203
     */
204 146
    public function validate()
205
    {
206 146
        if ( ! $this->migrationsNamespace) {
207 10
            throw MigrationException::migrationsNamespaceRequired();
208
        }
209 136
        if ( ! $this->migrationsDirectory) {
210 10
            throw MigrationException::migrationsDirectoryRequired();
211
        }
212 126
    }
213
214
    /**
215
     * Set the name of this set of migrations
216
     *
217
     * @param string $name The name of this set of migrations
218
     */
219 63
    public function setName($name)
220
    {
221 63
        $this->name = $name;
222 63
    }
223
224
    /**
225
     * Returns the name of this set of migrations
226
     *
227
     * @return string $name The name of this set of migrations
228
     */
229 16
    public function getName()
230
    {
231 16
        return $this->name;
232
    }
233
234
    /**
235
     * Sets the output writer.
236
     *
237
     * @param OutputWriter $outputWriter
238
     */
239 3
    public function setOutputWriter(OutputWriter $outputWriter)
240
    {
241 3
        $this->outputWriter = $outputWriter;
242 3
    }
243
244
    /**
245
     * Returns the OutputWriter instance
246
     *
247
     * @return OutputWriter $outputWriter  The OutputWriter instance
248
     */
249 117
    public function getOutputWriter()
250
    {
251 117
        return $this->outputWriter;
252
    }
253
254
    /**
255
     * Returns a timestamp version as a formatted date
256
     *
257
     * @param string $version
258
     *
259
     * @return string The formatted version
260
     * @deprecated
261
     */
262 14
    public function formatVersion($version)
263
    {
264 14
        return $this->getDateTime($version);
265
    }
266
267
    /**
268
     * Returns the datetime of a migration
269
     *
270
     * @param $version
271
     * @return string
272
     */
273 22
    public function getDateTime($version)
274
    {
275 22
        $datetime = str_replace('Version', '', $version);
276 22
        $datetime = \DateTime::createFromFormat('YmdHis', $datetime);
277
278 22
        if ($datetime === false) {
279 8
            return '';
280
        }
281
282 15
        return $datetime->format('Y-m-d H:i:s');
283
    }
284
285
    /**
286
     * Returns the Connection instance
287
     *
288
     * @return Connection $connection  The Connection instance
289
     */
290 120
    public function getConnection()
291
    {
292 120
        return $this->connection;
293
    }
294
295
    /**
296
     * Set the migration table name
297
     *
298
     * @param string $tableName The migration table name
299
     */
300 85
    public function setMigrationsTableName($tableName)
301
    {
302 85
        $this->migrationsTableName = $tableName;
303 85
    }
304
305
    /**
306
     * Returns the migration table name
307
     *
308
     * @return string $migrationsTableName The migration table name
309
     */
310 60
    public function getMigrationsTableName()
311
    {
312 60
        return $this->migrationsTableName;
313
    }
314
315
    /**
316
     * Set the migration column name
317
     *
318
     * @param string $columnName The migration column name
319
     */
320 56
    public function setMigrationsColumnName($columnName)
321
    {
322 56
        $this->migrationsColumnName = $columnName;
323 56
    }
324
325
    /**
326
     * Returns the migration column name
327
     *
328
     * @return string $migrationsColumnName The migration column name
329
     */
330 54
    public function getMigrationsColumnName()
331
    {
332 54
        return $this->migrationsColumnName;
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 183
    public function setMigrationsDirectory($migrationsDirectory)
341
    {
342 183
        $this->migrationsDirectory = $migrationsDirectory;
343 183
    }
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 65
    public function getMigrationsDirectory()
351
    {
352 65
        return $this->migrationsDirectory;
353
    }
354
355
    /**
356
     * Set the migrations namespace
357
     *
358
     * @param string $migrationsNamespace The migrations namespace
359
     */
360 195
    public function setMigrationsNamespace($migrationsNamespace)
361
    {
362 195
        $this->migrationsNamespace = $migrationsNamespace;
363 195
    }
364
365
    /**
366
     * Returns the migrations namespace
367
     *
368
     * @return string $migrationsNamespace The migrations namespace
369
     */
370 92
    public function getMigrationsNamespace()
371
    {
372 92
        return $this->migrationsNamespace;
373
    }
374
375
    /**
376
     * Returns the custom template path
377
     *
378
     * @return string $customTemplate The custom template path
379
     */
380 2
    public function getCustomTemplate() : ?string
381
    {
382 2
        return $this->customTemplate;
383
    }
384
385
    /**
386
     * Set the custom template path
387
     *
388
     * @param string $customTemplate The custom template path
389
     */
390 3
    public function setCustomTemplate(string $customTemplate) : void
391
    {
392 3
        $this->customTemplate = $customTemplate;
393 3
    }
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 103
    public function registerMigrationsFromDirectory($path)
424
    {
425 103
        $this->validate();
426
427 87
        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 69
    public function registerMigration($version, $class)
442
    {
443 69
        $this->ensureMigrationClassExists($class);
444
445 68
        $version = (string) $version;
446 68
        $class   = (string) $class;
447 68
        if (isset($this->migrations[$version])) {
448 1
            throw MigrationException::duplicateMigrationVersion($version, get_class($this->migrations[$version]));
449
        }
450 68
        $version                                  = new Version($this, $version, $class);
451 68
        $this->migrations[$version->getVersion()] = $version;
452 68
        ksort($this->migrations, SORT_STRING);
453
454 68
        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 91
    public function registerMigrations(array $migrations)
467
    {
468 91
        $versions = [];
469 91
        foreach ($migrations as $version => $class) {
470 34
            $versions[] = $this->registerMigration($version, $class);
471
        }
472
473 90
        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 20
    public function getVersion($version)
496
    {
497 20
        if (empty($this->migrations)) {
498 7
            $this->registerMigrationsFromDirectory($this->getMigrationsDirectory());
499
        }
500
501 20
        if ( ! isset($this->migrations[$version])) {
502 2
            throw MigrationException::unknownMigrationVersion($version);
503
        }
504
505 18
        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->migrationsColumnName . " FROM " . $this->migrationsTableName . " WHERE " . $this->migrationsColumnName . " = ?",
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 41
    public function getMigratedVersions()
550
    {
551 41
        $this->connect();
552 41
        $this->createMigrationTable();
553
554 41
        $ret = $this->connection->fetchAll("SELECT " . $this->migrationsColumnName . " FROM " . $this->migrationsTableName);
555
556 41
        return array_map('current', $ret);
557
    }
558
559
    /**
560
     * Returns an array of available migration version numbers.
561
     *
562
     * @return array
563
     */
564 16
    public function getAvailableVersions()
565
    {
566 16
        $availableVersions = [];
567
568 16
        if (empty($this->migrations)) {
569 3
            $this->registerMigrationsFromDirectory($this->getMigrationsDirectory());
570
        }
571
572 14
        foreach ($this->migrations as $migration) {
573 14
            $availableVersions[] = $migration->getVersion();
574
        }
575
576 14
        return $availableVersions;
577
    }
578
579
    /**
580
     * Returns the current migrated version from the versions table.
581
     *
582
     * @return string
583
     */
584 43
    public function getCurrentVersion()
585
    {
586 43
        $this->connect();
587 43
        $this->createMigrationTable();
588
589 41
        if (empty($this->migrations)) {
590 15
            $this->registerMigrationsFromDirectory($this->getMigrationsDirectory());
591
        }
592
593 41
        $where = null;
594 41
        if ( ! empty($this->migrations)) {
595 37
            $migratedVersions = [];
596 37
            foreach ($this->migrations as $migration) {
597 37
                $migratedVersions[] = sprintf("'%s'", $migration->getVersion());
598
            }
599 37
            $where = " WHERE " . $this->migrationsColumnName . " IN (" . implode(', ', $migratedVersions) . ")";
600
        }
601
602 41
        $sql = sprintf(
603 41
            "SELECT %s FROM %s%s ORDER BY %s DESC",
604 41
            $this->migrationsColumnName,
605 41
            $this->migrationsTableName,
606 41
            $where,
607 41
            $this->migrationsColumnName
608
        );
609
610 41
        $sql    = $this->connection->getDatabasePlatform()->modifyLimitQuery($sql, 1);
611 41
        $result = $this->connection->fetchColumn($sql);
612
613 41
        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 10
    public function getNextVersion()
634
    {
635 10
        return $this->getRelativeVersion($this->getCurrentVersion(), 1);
636
    }
637
638
    /**
639
     * Returns the version with the specified offset to the specified version.
640
     *
641
     * @return string|null A version string, or null if the specified version
642
     *                     is unknown or the specified delta is not within the
643
     *                     list of available versions.
644
     */
645 16
    public function getRelativeVersion($version, $delta)
646
    {
647 16
        if (empty($this->migrations)) {
648 5
            $this->registerMigrationsFromDirectory($this->getMigrationsDirectory());
649
        }
650
651 14
        $versions = array_map('strval', array_keys($this->migrations));
652 14
        array_unshift($versions, '0');
653 14
        $offset = array_search((string) $version, $versions);
654 14
        if ($offset === false || ! isset($versions[$offset + $delta])) {
655
            // Unknown version or delta out of bounds.
656 11
            return null;
657
        }
658
659 12
        return $versions[$offset + $delta];
660
    }
661
662
    /**
663
     * Returns the version with the specified to the current version.
664
     *
665
     * @return string|null A version string, or null if the specified delta is
666
     *                     not within the list of available versions.
667
     */
668 1
    public function getDeltaVersion($delta)
669
    {
670 1
        $symbol = substr($delta, 0, 1);
671 1
        $number = (int) substr($delta, 1);
672
673 1
        if ($number <= 0) {
674
            return null;
675
        }
676
677 1
        if ($symbol == "+" || $symbol == "-") {
678 1
            return $this->getRelativeVersion($this->getCurrentVersion(), (int) $delta);
679
        }
680
681
        return null;
682
    }
683
684
    /**
685
     * Returns the version number from an alias.
686
     *
687
     * Supported aliases are:
688
     * - first: The very first version before any migrations have been run.
689
     * - current: The current version.
690
     * - prev: The version prior to the current version.
691
     * - next: The version following the current version.
692
     * - latest: The latest available version.
693
     *
694
     * If an existing version number is specified, it is returned verbatimly.
695
     *
696
     * @return string|null A version number, or null if the specified alias
697
     *                     does not map to an existing version, e.g. if "next"
698
     *                     is passed but the current version is already the
699
     *                     latest.
700
     */
701 9
    public function resolveVersionAlias($alias)
702
    {
703 9
        if ($this->hasVersion($alias)) {
704 1
            return $alias;
705
        }
706
        switch ($alias) {
707 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...
708 1
                return '0';
709 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...
710 9
                return $this->getCurrentVersion();
711 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...
712 9
                return $this->getPrevVersion();
713 9
            case 'next':
714 9
                return $this->getNextVersion();
715 9
            case 'latest':
716 9
                return $this->getLatestVersion();
717
            default:
718 1
                if (substr($alias, 0, 7) == 'current') {
719
                    return $this->getDeltaVersion(substr($alias, 7));
720
                }
721 1
                return null;
722
        }
723
    }
724
725
    /**
726
     * Returns the total number of executed migration versions
727
     *
728
     * @return integer
729
     */
730 1
    public function getNumberOfExecutedMigrations()
731
    {
732 1
        $this->connect();
733 1
        $this->createMigrationTable();
734
735 1
        $result = $this->connection->fetchColumn("SELECT COUNT(" . $this->migrationsColumnName . ") FROM " . $this->migrationsTableName);
736
737 1
        return $result !== false ? $result : 0;
738
    }
739
740
    /**
741
     * Returns the total number of available migration versions
742
     *
743
     * @return integer
744
     */
745 5
    public function getNumberOfAvailableMigrations()
746
    {
747 5
        if (empty($this->migrations)) {
748 4
            $this->registerMigrationsFromDirectory($this->getMigrationsDirectory());
749
        }
750
751 3
        return count($this->migrations);
752
    }
753
754
    /**
755
     * Returns the latest available migration version.
756
     *
757
     * @return string The version string in the format YYYYMMDDHHMMSS.
758
     */
759 28
    public function getLatestVersion()
760
    {
761 28
        if (empty($this->migrations)) {
762 7
            $this->registerMigrationsFromDirectory($this->getMigrationsDirectory());
763
        }
764
765 26
        $versions = array_keys($this->migrations);
766 26
        $latest   = end($versions);
767
768 26
        return $latest !== false ? (string) $latest : '0';
769
    }
770
771
    /**
772
     * Create the migration table to track migrations with.
773
     *
774
     * @return boolean Whether or not the table was created.
775
     */
776 64
    public function createMigrationTable()
777
    {
778 64
        $this->validate();
779
780 62
        if ($this->migrationTableCreated) {
781 51
            return false;
782
        }
783
784 62
        $this->connect();
785 62
        if ($this->connection->getSchemaManager()->tablesExist([$this->migrationsTableName])) {
786 4
            $this->migrationTableCreated = true;
787
788 4
            return false;
789
        }
790
791
        $columns = [
792 61
            $this->migrationsColumnName => new Column($this->migrationsColumnName, Type::getType('string'), ['length' => 255]),
793
        ];
794 61
        $table   = new Table($this->migrationsTableName, $columns);
795 61
        $table->setPrimaryKey([$this->migrationsColumnName]);
796 61
        $this->connection->getSchemaManager()->createTable($table);
797
798 61
        $this->migrationTableCreated = true;
799
800 61
        return true;
801
    }
802
803
    /**
804
     * Returns the array of migrations to executed based on the given direction
805
     * and target version number.
806
     *
807
     * @param string $direction The direction we are migrating.
808
     * @param string $to        The version to migrate to.
809
     *
810
     * @return Version[] $migrations   The array of migrations we can execute.
811
     */
812 39
    public function getMigrationsToExecute($direction, $to)
813
    {
814 39
        if (empty($this->migrations)) {
815 11
            $this->registerMigrationsFromDirectory($this->getMigrationsDirectory());
816
        }
817
818 33
        if ($direction === Version::DIRECTION_DOWN) {
819 7
            if (count($this->migrations)) {
820 7
                $allVersions = array_reverse(array_keys($this->migrations));
821 7
                $classes     = array_reverse(array_values($this->migrations));
822 7
                $allVersions = array_combine($allVersions, $classes);
823
            } else {
824 7
                $allVersions = [];
825
            }
826
        } else {
827 31
            $allVersions = $this->migrations;
828
        }
829 33
        $versions = [];
830 33
        $migrated = $this->getMigratedVersions();
831 33
        foreach ($allVersions as $version) {
832 31
            if ($this->shouldExecuteMigration($direction, $version, $to, $migrated)) {
833 31
                $versions[$version->getVersion()] = $version;
834
            }
835
        }
836
837 33
        return $versions;
838
    }
839
840
    /**
841
     * Use the connection's event manager to emit an event.
842
     *
843
     * @param string $eventName The event to emit.
844
     * @param $args The event args instance to emit.
845
     */
846 45
    public function dispatchEvent($eventName, EventArgs $args = null)
847
    {
848 45
        $this->connection->getEventManager()->dispatchEvent($eventName, $args);
849 45
    }
850
851
    /**
852
     * Find all the migrations in a given directory.
853
     *
854
     * @param   string $path the directory to search.
855
     * @return  array
856
     */
857 87
    protected function findMigrations($path)
858
    {
859 87
        return $this->migrationFinder->findMigrations($path, $this->getMigrationsNamespace());
860
    }
861
862
    /**
863
     * @param bool $migrationsAreOrganizedByYear
864
     * @throws MigrationException
865
     */
866 9
    public function setMigrationsAreOrganizedByYear($migrationsAreOrganizedByYear = true)
867
    {
868 9
        $this->ensureOrganizeMigrationsIsCompatibleWithFinder();
869
870 5
        $this->migrationsAreOrganizedByYear = $migrationsAreOrganizedByYear;
871 5
    }
872
873
    /**
874
     * @param bool $migrationsAreOrganizedByYearAndMonth
875
     * @throws MigrationException
876
     */
877 10
    public function setMigrationsAreOrganizedByYearAndMonth($migrationsAreOrganizedByYearAndMonth = true)
878
    {
879 10
        $this->ensureOrganizeMigrationsIsCompatibleWithFinder();
880
881 10
        $this->migrationsAreOrganizedByYear         = $migrationsAreOrganizedByYearAndMonth;
882 10
        $this->migrationsAreOrganizedByYearAndMonth = $migrationsAreOrganizedByYearAndMonth;
883 10
    }
884
885
    /**
886
     * Generate a new migration version. A version is (usually) a datetime string.
887
     *
888
     * @param \DateTimeInterface|null $now Defaults to the current time in UTC
889
     * @return string The newly generated version
890
     */
891 9
    public function generateVersionNumber(\DateTimeInterface $now = null)
892
    {
893 9
        $now = $now ?: new \DateTime('now', new \DateTimeZone('UTC'));
894
895 9
        return $now->format(self::VERSION_FORMAT);
896
    }
897
898
    /**
899
     * Explicitely opens the database connection. This is done to play nice
900
     * with DBAL's MasterSlaveConnection. Which, in some cases, connects to a
901
     * follower when fetching the executed migrations. If a follower is lagging
902
     * significantly behind that means the migrations system may see unexecuted
903
     * migrations that were actually executed earlier.
904
     *
905
     * @return bool The same value returned from the `connect` method
906
     */
907 64
    protected function connect()
908
    {
909 64
        if ($this->connection instanceof MasterSlaveConnection) {
910 1
            return $this->connection->connect('master');
911
        }
912
913 63
        return $this->connection->connect();
914
    }
915
916
    /**
917
     * @throws MigrationException
918
     */
919 19
    private function ensureOrganizeMigrationsIsCompatibleWithFinder()
920
    {
921 19
        if ( ! ($this->migrationFinder instanceof MigrationDeepFinderInterface)) {
922 4
            throw MigrationException::configurationIncompatibleWithFinder(
923 4
                'organize-migrations',
924 4
                $this->migrationFinder
925
            );
926
        }
927 15
    }
928
929
    /**
930
     * Check if we should execute a migration for a given direction and target
931
     * migration version.
932
     *
933
     * @param string  $direction The direction we are migrating.
934
     * @param Version $version   The Version instance to check.
935
     * @param string  $to        The version we are migrating to.
936
     * @param array   $migrated  Migrated versions array.
937
     *
938
     * @return boolean
939
     */
940 31
    private function shouldExecuteMigration($direction, Version $version, $to, $migrated)
941
    {
942 31
        if ($direction === Version::DIRECTION_DOWN) {
943 7
            if ( ! in_array($version->getVersion(), $migrated)) {
944 4
                return false;
945
            }
946
947 5
            return $version->getVersion() > $to;
948
        }
949
950 29
        if ($direction === Version::DIRECTION_UP) {
951 29
            if (in_array($version->getVersion(), $migrated)) {
952 8
                return false;
953
            }
954
955 28
            return $version->getVersion() <= $to;
956
        }
957
    }
958
959
    /**
960
     * @param string $class
961
     */
962 69
    private function ensureMigrationClassExists($class)
963
    {
964 69
        if ( ! class_exists($class)) {
965 1
            throw MigrationException::migrationClassNotFound($class, $this->getMigrationsNamespace());
966
        }
967 68
    }
968
969 8
    public function getQueryWriter() : QueryWriter
970
    {
971 8
        if ($this->queryWriter === null) {
972 7
            $this->queryWriter = new FileQueryWriter(
973 7
                $this->migrationsColumnName,
974 7
                $this->migrationsTableName,
975 7
                $this->outputWriter
976
            );
977
        }
978
979 8
        return $this->queryWriter;
980
    }
981
}
982