Completed
Push — master ( 178c75...b82d68 )
by Luís
11s
created

Configuration   F

Complexity

Total Complexity 100

Size/Duplication

Total Lines 886
Duplicated Lines 3.61 %

Coupling/Cohesion

Components 1
Dependencies 13

Test Coverage

Coverage 98.42%

Importance

Changes 0
Metric Value
wmc 100
lcom 1
cbo 13
dl 32
loc 886
ccs 249
cts 253
cp 0.9842
rs 2.6666
c 0
b 0
f 0

49 Methods

Rating   Name   Duplication   Size   Complexity  
A areMigrationsOrganizedByYear() 0 4 1
A areMigrationsOrganizedByYearAndMonth() 0 4 1
A validate() 0 9 3
A setName() 0 4 1
A getName() 0 4 1
A setOutputWriter() 0 4 1
A getOutputWriter() 0 4 1
A formatVersion() 0 4 1
A getDateTime() 0 11 2
A getConnection() 0 4 1
A setMigrationsTableName() 0 4 1
A getMigrationsTableName() 0 4 1
A setMigrationsColumnName() 0 4 1
A getMigrationsColumnName() 0 4 1
A setMigrationsDirectory() 0 4 1
A getMigrationsDirectory() 0 4 1
A setMigrationsNamespace() 0 4 1
A getMigrationsNamespace() 0 4 1
A setMigrationsFinder() 0 13 4
A registerMigrationsFromDirectory() 0 6 1
A registerMigration() 0 15 2
A getMigrations() 0 4 1
A getMigratedVersions() 9 9 1
A getPrevVersion() 0 4 1
A getNextVersion() 0 4 1
A getDeltaVersion() 0 14 4
A getNumberOfExecutedMigrations() 9 9 2
A dispatchEvent() 0 4 1
A findMigrations() 0 4 1
A setMigrationsAreOrganizedByYear() 0 6 1
A setMigrationsAreOrganizedByYearAndMonth() 0 7 1
A generateVersionNumber() 0 6 2
A connect() 0 8 2
B shouldExecuteMigration() 14 18 5
A ensureMigrationClassExists() 0 6 2
A __construct() 0 12 3
A registerMigrations() 0 9 2
A getVersion() 0 12 3
A hasVersion() 0 8 2
A hasVersionMigrated() 0 12 1
A getAvailableVersions() 0 14 3
B getCurrentVersion() 0 27 5
A getRelativeVersion() 0 16 4
C resolveVersionAlias() 0 23 8
A getNumberOfAvailableMigrations() 0 8 2
A getLatestVersion() 0 11 3
B createMigrationTable() 0 26 3
B getMigrationsToExecute() 0 27 6
A ensureOrganizeMigrationsIsCompatibleWithFinder() 0 9 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Configuration often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Configuration, and based on these observations, apply Extract Interface, too.

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