Failed Conditions
Pull Request — master (#537)
by Luís
07:20
created

Configuration::getQueryWriter()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

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

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
912 7
            if (!in_array($version->getVersion(), $migrated)) {
913 4
                return false;
914
            }
915
916 5
            return $version->getVersion() > $to;
917
        }
918
919 29 View Code Duplication
        if ($direction === Version::DIRECTION_UP) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
920 29
            if (in_array($version->getVersion(), $migrated)) {
921 8
                return false;
922
            }
923
924 28
            return $version->getVersion() <= $to;
925
        }
926
    }
927
928
    /**
929
     * @param string $class
930
     */
931 69
    private function ensureMigrationClassExists($class)
932
    {
933 69
        if ( ! class_exists($class)) {
934 1
            throw MigrationException::migrationClassNotFound($class, $this->getMigrationsNamespace());
935
        }
936 68
    }
937
938 8
    public function getQueryWriter() : QueryWriter
939
    {
940 8
        if ($this->queryWriter === null) {
941 7
            $this->queryWriter = new FileQueryWriter(
942 7
                $this->migrationsColumnName,
943 7
                $this->migrationsTableName,
944 7
                $this->outputWriter
945
            );
946
        }
947
948 8
        return $this->queryWriter;
949
    }
950
}
951