Configuration   F
last analyzed

Complexity

Total Complexity 69

Size/Duplication

Total Lines 680
Duplicated Lines 0 %

Test Coverage

Coverage 69.59%

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 191
c 3
b 0
f 0
dl 0
loc 680
ccs 151
cts 217
cp 0.6959
rs 2.88
wmc 69

40 Methods

Rating   Name   Duplication   Size   Complexity  
A shouldExecuteMigration() 0 16 5
A validate() 0 13 4
A registerMigrations() 0 8 2
A setMigrationsScriptDirectory() 0 5 1
A getDetailsMap() 0 29 1
A getMigrations() 0 3 1
A setMigrationsNamespace() 0 5 1
A getMigratedVersions() 0 11 2
A getConnection() 0 3 1
A formatVersion() 0 9 1
A getNumberOfExecutedMigrations() 0 5 1
A getName() 0 3 2
A getMigrationsNamespace() 0 3 1
A getMigratedTimestamp() 0 23 3
A getUnavailableMigratedVersions() 0 3 1
A registerMigrationsFromDirectory() 0 17 3
A getFile() 0 3 1
A getAvailableVersions() 0 8 2
A hasVersion() 0 3 1
A getNumberOfAvailableMigrations() 0 3 1
A setMigrationsDirectory() 0 5 1
A getCurrentVersion() 0 25 4
A getMigrationsDatabaseName() 0 3 1
A getMigrationsDirectory() 0 3 1
A createMigrationCollection() 0 11 2
A getCollection() 0 9 2
A getLatestVersion() 0 6 2
A getMigrationsCollectionName() 0 3 1
A getMigrationsScriptDirectory() 0 3 1
A setMigrationsDatabaseName() 0 5 1
A __construct() 0 7 2
A registerMigration() 0 17 2
A getMigrationsToExecute() 0 22 5
A getDatabase() 0 9 2
A getOutputWriter() 0 3 1
A setMigrationsCollectionName() 0 5 1
A setFile() 0 5 1
A hasVersionMigrated() 0 7 1
A setName() 0 5 1
A getVersion() 0 7 2

How to fix   Complexity   

Complex Class

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.

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
/*
4
 * This file is part of the AntiMattr MongoDB Migrations Library, a library by Matthew Fitzgerald.
5
 *
6
 * (c) 2014 Matthew Fitzgerald
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace AntiMattr\MongoDB\Migrations\Configuration;
13
14
use AntiMattr\MongoDB\Migrations\Exception\ConfigurationValidationException;
15
use AntiMattr\MongoDB\Migrations\Exception\DuplicateVersionException;
16
use AntiMattr\MongoDB\Migrations\Exception\UnknownVersionException;
17
use AntiMattr\MongoDB\Migrations\OutputWriter;
18
use AntiMattr\MongoDB\Migrations\Version;
19
use MongoDB\Client;
20
21
/**
22
 * @author Matthew Fitzgerald <[email protected]>
23
 */
24
class Configuration
25
{
26
    /**
27
     * @var \MongoDB\Collection
28
     */
29
    private $collection;
30
31
    /**
32
     * @var \MongoDB\Client
33
     */
34
    private $connection;
35
36
    /**
37
     * @var \MongoDB\Database
38
     */
39
    private $database;
40
41
    /**
42
     * The migration database name to track versions in.
43
     *
44
     * @var string
45
     */
46
    private $migrationsDatabaseName;
47
48
    /**
49
     * Flag for whether or not the migration collection has been created.
50
     *
51
     * @var bool
52
     */
53
    private $migrationCollectionCreated = false;
54
55
    /**
56
     * The migration collection name to track versions in.
57
     *
58
     * @var string
59
     */
60
    private $migrationsCollectionName = 'antimattr_migration_versions';
61
62
    /**
63
     * The path to a directory where new migration classes will be written.
64
     *
65
     * @var string
66
     */
67
    private $migrationsDirectory;
68
69
    /**
70
     * Namespace the migration classes live in.
71
     *
72
     * @var string
73
     */
74
    private $migrationsNamespace;
75
76
    /**
77
     * The path to a directory where mongo console scripts are.
78
     *
79
     * @var string
80
     */
81
    private $migrationsScriptDirectory;
82
83
    /**
84
     * Used by Console Commands and Output Writer.
85
     *
86
     * @var string
87
     */
88
    private $name;
89
90
    /**
91
     * @var \AntiMattr\MongoDB\Migrations\Version[]
92
     */
93
    protected $migrations = [];
94
95
    /**
96
     * @var \AntiMattr\MongoDB\Migrations\OutputWriter
97
     */
98
    private $outputWriter;
99
100
    /**
101
     * @var string
102
     */
103
    private $file;
104
105
    /**
106
     * @param \AntiMattr\MongoDB\Migrations\OutputWriter $outputWriter
107
     */
108 17
    public function __construct(Client $connection, OutputWriter $outputWriter = null)
109
    {
110 17
        $this->connection = $connection;
111 17
        if (null === $outputWriter) {
112 14
            $outputWriter = new OutputWriter();
113
        }
114 17
        $this->outputWriter = $outputWriter;
115 17
    }
116
117
    /**
118
     * Returns a timestamp version as a formatted date.
119
     *
120
     * @param string $version
121
     *
122
     * @return string The formatted version
123
     */
124 5
    public static function formatVersion($version)
125
    {
126 5
        return sprintf('%s-%s-%s %s:%s:%s',
127 5
            substr($version, 0, 4),
128 5
            substr($version, 4, 2),
129 5
            substr($version, 6, 2),
130 5
            substr($version, 8, 2),
131 5
            substr($version, 10, 2),
132 5
            substr($version, 12, 2)
133
        );
134
    }
135
136
    /**
137
     * Returns an array of available migration version numbers.
138
     *
139
     * @return array
140
     */
141 2
    public function getAvailableVersions()
142
    {
143 2
        $availableVersions = [];
144 2
        foreach ($this->migrations as $migration) {
145 1
            $availableVersions[] = $migration->getVersion();
146
        }
147
148 2
        return $availableVersions;
149
    }
150
151
    /**
152
     * @return \MongoDB\Collection
153
     */
154 7
    public function getCollection()
155
    {
156 7
        if (isset($this->collection)) {
157 6
            return $this->collection;
158
        }
159
160 7
        $this->collection = $this->getDatabase()->selectCollection($this->migrationsCollectionName);
161
162 7
        return $this->collection;
163
    }
164
165
    /**
166
     * @return \MongoDB\Client
167
     */
168 4
    public function getConnection()
169
    {
170 4
        return $this->connection;
171
    }
172
173
    /**
174
     * @return \MongoDB\Database
175
     */
176 9
    public function getDatabase(): ?\MongoDB\Database
177
    {
178 9
        if (isset($this->database)) {
179
            return $this->database;
180
        }
181
182 9
        $this->database = $this->connection->selectDatabase($this->migrationsDatabaseName);
183
184 9
        return $this->database;
185
    }
186
187
    /**
188
     * Get the array of registered migration versions.
189
     *
190
     * @return Version[] $migrations
191
     */
192 2
    public function getMigrations()
193
    {
194 2
        return $this->migrations;
195
    }
196
197
    /**
198
     * @param string $databaseName
199
     */
200 12
    public function setMigrationsDatabaseName($databaseName)
201
    {
202 12
        $this->migrationsDatabaseName = $databaseName;
203
204 12
        return $this;
205
    }
206
207
    /**
208
     * @return string
209
     */
210 2
    public function getMigrationsDatabaseName()
211
    {
212 2
        return $this->migrationsDatabaseName;
213
    }
214
215
    /**
216
     * @param string $collectionName
217
     */
218 11
    public function setMigrationsCollectionName($collectionName)
219
    {
220 11
        $this->migrationsCollectionName = $collectionName;
221
222 11
        return $this;
223
    }
224
225
    /**
226
     * @return string
227
     */
228 2
    public function getMigrationsCollectionName()
229
    {
230 2
        return $this->migrationsCollectionName;
231
    }
232
233
    /**
234
     * @param string $migrationsDirectory
235
     */
236 9
    public function setMigrationsDirectory($migrationsDirectory)
237
    {
238 9
        $this->migrationsDirectory = $migrationsDirectory;
239
240 9
        return $this;
241
    }
242
243
    /**
244
     * @return string
245
     */
246 2
    public function getMigrationsDirectory()
247
    {
248 2
        return $this->migrationsDirectory;
249
    }
250
251
    /**
252
     * Set the migrations namespace.
253
     *
254
     * @param string $migrationsNamespace The migrations namespace
255
     */
256 11
    public function setMigrationsNamespace($migrationsNamespace)
257
    {
258 11
        $this->migrationsNamespace = $migrationsNamespace;
259
260 11
        return $this;
261
    }
262
263
    /**
264
     * @return string $migrationsNamespace
265
     */
266 2
    public function getMigrationsNamespace()
267
    {
268 2
        return $this->migrationsNamespace;
269
    }
270
271
    /**
272
     * @param string $scriptsDirectory
273
     */
274 2
    public function setMigrationsScriptDirectory($scriptsDirectory)
275
    {
276 2
        $this->migrationsScriptDirectory = $scriptsDirectory;
277
278 2
        return $this;
279
    }
280
281
    /**
282
     * @return string
283
     */
284 2
    public function getMigrationsScriptDirectory()
285
    {
286 2
        return $this->migrationsScriptDirectory;
287
    }
288
289
    /**
290
     * @param string $file
291
     */
292 3
    public function setFile($file)
293
    {
294 3
        $this->file = $file;
295
296 3
        return $this;
297
    }
298
299
    public function getFile(): ?string
300
    {
301
        return $this->file;
302
    }
303
304
    /**
305
     * Returns all migrated versions from the versions collection, in an array.
306
     *
307
     * @return \AntiMattr\MongoDB\Migrations\Version[]
308
     */
309 1
    public function getMigratedVersions()
310
    {
311 1
        $this->createMigrationCollection();
312
313 1
        $cursor = $this->getCollection()->find();
314 1
        $versions = [];
315 1
        foreach ($cursor as $record) {
316 1
            $versions[] = $record['v'];
317
        }
318
319 1
        return $versions;
320
    }
321
322
    /**
323
     * Returns the time a migration occurred.
324
     *
325
     * @param string $version
326
     *
327
     * @throws AntiMattr\MongoDB\Migrations\Exception\UnknownVersionException Throws exception if migration version does not exist
328
     * @throws DomainException                                                If more than one version exists
329
     */
330 2
    public function getMigratedTimestamp($version): int
331
    {
332 2
        $this->createMigrationCollection();
333
334 2
        $cursor = $this->getCollection()->find(
335 2
            ['v' => $version]
336
        );
337
338 2
        $result = $cursor->toArray();
339 2
        if (!count($result)) {
340
            throw new UnknownVersionException($version);
341
        }
342
343 2
        if (count($result) > 1) {
344 1
            throw new \DomainException('Unexpected duplicate version records in the database');
345
        }
346
347 1
        $returnVersion = $result[0];
348
349
        // Convert to normalised timestamp
350 1
        $ts = new Timestamp($returnVersion['t']);
351
352 1
        return $ts->getTimestamp();
353
    }
354
355
    /**
356
     * Return all migrated versions from versions collection that have migration files deleted.
357
     *
358
     * @return array
359
     */
360 1
    public function getUnavailableMigratedVersions()
361
    {
362 1
        return array_diff($this->getMigratedVersions(), $this->getAvailableVersions());
363
    }
364
365
    /**
366
     * @param string $name
367
     */
368 3
    public function setName($name)
369
    {
370 3
        $this->name = $name;
371
372 3
        return $this;
373
    }
374
375
    /**
376
     * @return string $name
377
     */
378 2
    public function getName()
379
    {
380 2
        return ($this->name) ?: 'Database Migrations';
381
    }
382
383
    /**
384
     * @return int
385
     */
386 1
    public function getNumberOfAvailableMigrations()
387
    {
388 1
        return count($this->migrations);
389
    }
390
391
    /**
392
     * @return int
393
     */
394 1
    public function getNumberOfExecutedMigrations()
395
    {
396 1
        $this->createMigrationCollection();
397
398 1
        return $this->getCollection()->countDocuments();
399
    }
400
401
    /**
402
     * @return \AntiMattr\MongoDB\Migrations\OutputWriter
403
     */
404 5
    public function getOutputWriter()
405
    {
406 5
        return $this->outputWriter;
407
    }
408
409
    /**
410
     * Register a single migration version to be executed by a AbstractMigration
411
     * class.
412
     *
413
     * @param string $version The version of the migration in the format YYYYMMDDHHMMSS
414
     * @param string $class   The migration class to execute for the version
415
     *
416
     * @return Version
417
     *
418
     * @throws AntiMattr\MongoDB\Migrations\Exception\DuplicateVersionException
419
     */
420 2
    public function registerMigration($version, $class)
421
    {
422 2
        $version = (string) $version;
423 2
        $class = (string) $class;
424 2
        if (isset($this->migrations[$version])) {
425
            $message = sprintf(
426
                'Migration version %s already registered with class %s',
427
                $version,
428
                get_class($this->migrations[$version])
429
            );
430
            throw new DuplicateVersionException($message);
431
        }
432 2
        $version = new Version($this, $version, $class);
433 2
        $this->migrations[$version->getVersion()] = $version;
434 2
        ksort($this->migrations);
435
436 2
        return $version;
437
    }
438
439
    /**
440
     * Register an array of migrations. Each key of the array is the version and
441
     * the value is the migration class name.
442
     *
443
     * @return Version[]
444
     */
445
    public function registerMigrations(array $migrations)
446
    {
447
        $versions = [];
448
        foreach ($migrations as $version => $class) {
449
            $versions[] = $this->registerMigration($version, $class);
450
        }
451
452
        return $versions;
453
    }
454
455
    /**
456
     * Register migrations from a given directory. Recursively finds all files
457
     * with the pattern VersionYYYYMMDDHHMMSS.php as the filename and registers
458
     * them as migrations.
459
     *
460
     * @param string $path The root directory to where some migration classes live
461
     *
462
     * @return Version[] The array of migrations registered
463
     */
464 4
    public function registerMigrationsFromDirectory($path)
465
    {
466 4
        $path = realpath($path);
467 4
        $path = rtrim($path, '/');
468 4
        $files = glob($path . '/Version*.php');
469 4
        $versions = [];
470 4
        if ($files) {
471 2
            foreach ($files as $file) {
472 2
                require_once $file;
473 2
                $info = pathinfo($file);
474 2
                $version = substr($info['filename'], 7);
475 2
                $class = $this->migrationsNamespace . '\\' . $info['filename'];
476 2
                $versions[] = $this->registerMigration($version, $class);
477
            }
478
        }
479
480 4
        return $versions;
481
    }
482
483
    /**
484
     * Returns the Version instance for a given version in the format YYYYMMDDHHMMSS.
485
     *
486
     * @param string $version The version string in the format YYYYMMDDHHMMSS
487
     *
488
     * @return \AntiMattr\MongoDB\Migrations\Version
489
     *
490
     * @throws AntiMattr\MongoDB\Migrations\Exception\UnknownVersionException Throws exception if migration version does not exist
491
     */
492 2
    public function getVersion($version)
493
    {
494 2
        if (!isset($this->migrations[$version])) {
495 1
            throw new UnknownVersionException($version);
496
        }
497
498 1
        return $this->migrations[$version];
499
    }
500
501
    /**
502
     * Check if a version exists.
503
     *
504
     * @param string $version
505
     *
506
     * @return bool
507
     */
508 1
    public function hasVersion($version)
509
    {
510 1
        return isset($this->migrations[$version]);
511
    }
512
513
    /**
514
     * Check if a version has been migrated or not yet.
515
     *
516
     * @return bool
517
     */
518 1
    public function hasVersionMigrated(Version $version)
519
    {
520 1
        $this->createMigrationCollection();
521
522 1
        $record = $this->getCollection()->findOne(['v' => $version->getVersion()]);
523
524 1
        return null !== $record;
525
    }
526
527
    /**
528
     * @return string
529
     */
530 1
    public function getCurrentVersion()
531
    {
532 1
        $this->createMigrationCollection();
533
534 1
        $migratedVersions = [];
535 1
        if (!empty($this->migrations)) {
536 1
            foreach ($this->migrations as $migration) {
537 1
                $migratedVersions[] = $migration->getVersion();
538
            }
539
        }
540
541 1
        $cursor = $this->getCollection()
542 1
            ->find(
543 1
                ['v' => ['$in' => $migratedVersions]],
544 1
                ['sort' => ['v' => -1], 'limit' => 1]
545
            );
546
547 1
        $versions = $cursor->toArray();
548 1
        if (0 === \count($versions)) {
549
            return '0';
550
        }
551
552 1
        $version = $versions[0];
553
554 1
        return $version['v'];
555
    }
556
557
    /**
558
     * Returns the latest available migration version.
559
     *
560
     * @return string The version string in the format YYYYMMDDHHMMSS
561
     */
562
    public function getLatestVersion()
563
    {
564
        $versions = array_keys($this->migrations);
565
        $latest = end($versions);
566
567
        return false !== $latest ? (string) $latest : '0';
568
    }
569
570
    /**
571
     * Create the migration collection to track migrations with.
572
     *
573
     * @return bool Whether or not the collection was created
574
     */
575 6
    public function createMigrationCollection()
576
    {
577 6
        $this->validate();
578
579 6
        if (true !== $this->migrationCollectionCreated) {
580 6
            $collection = $this->getCollection();
581 6
            $collection->createIndex(['v' => -1], ['name' => 'version', 'unique' => true]);
582 6
            $this->migrationCollectionCreated = true;
583
        }
584
585 6
        return true;
586
    }
587
588
    /**
589
     * Returns the array of migrations to executed based on the given direction
590
     * and target version number.
591
     *
592
     * @param string $direction The direction we are migrating
593
     * @param string $to        The version to migrate to
594
     *
595
     * @return Version[] $migrations   The array of migrations we can execute
596
     */
597
    public function getMigrationsToExecute($direction, $to)
598
    {
599
        if ('down' === $direction) {
600
            if (count($this->migrations)) {
601
                $allVersions = array_reverse(array_keys($this->migrations));
602
                $classes = array_reverse(array_values($this->migrations));
603
                $allVersions = array_combine($allVersions, $classes);
604
            } else {
605
                $allVersions = [];
606
            }
607
        } else {
608
            $allVersions = $this->migrations;
609
        }
610
        $versions = [];
611
        $migrated = $this->getMigratedVersions();
612
        foreach ($allVersions as $version) {
613
            if ($this->shouldExecuteMigration($direction, $version, $to, $migrated)) {
614
                $versions[$version->getVersion()] = $version;
615
            }
616
        }
617
618
        return $versions;
619
    }
620
621
    /**
622
     * Check if we should execute a migration for a given direction and target
623
     * migration version.
624
     *
625
     * @param string  $direction The direction we are migrating
626
     * @param Version $version   The Version instance to check
627
     * @param string  $to        The version we are migrating to
628
     * @param array   $migrated  Migrated versions array
629
     *
630
     * @return bool
631
     */
632
    private function shouldExecuteMigration($direction, Version $version, $to, $migrated)
633
    {
634
        if ('down' === $direction) {
635
            if (!in_array($version->getVersion(), $migrated)) {
636
                return false;
637
            }
638
639
            return $version->getVersion() > $to;
640
        }
641
642
        if ('up' === $direction) {
643
            if (in_array($version->getVersion(), $migrated)) {
644
                return false;
645
            }
646
647
            return $version->getVersion() <= $to;
648
        }
649
    }
650
651
    /**
652
     * Validation that this instance has all the required properties configured.
653
     *
654
     * @throws AntiMattr\MongoDB\Migrations\Exception\ConfigurationValidationException
655
     */
656 8
    public function validate()
657
    {
658 8
        if (!$this->migrationsDatabaseName) {
659 1
            $message = 'Migrations Database Name must be configured in order to use AntiMattr migrations.';
660 1
            throw new ConfigurationValidationException($message);
661
        }
662 7
        if (!$this->migrationsNamespace) {
663
            $message = 'Migrations namespace must be configured in order to use AntiMattr migrations.';
664
            throw new ConfigurationValidationException($message);
665
        }
666 7
        if (!$this->migrationsDirectory) {
667
            $message = 'Migrations directory must be configured in order to use AntiMattr migrations.';
668
            throw new ConfigurationValidationException($message);
669
        }
670 7
    }
671
672
    /**
673
     * @return array
674
     */
675
    public function getDetailsMap()
676
    {
677
        // Executed migration count
678
        $executedMigrations = $this->getMigratedVersions();
679
        $numExecutedMigrations = count($executedMigrations);
680
681
        // Available migration count
682
        $availableMigrations = $this->getAvailableVersions();
683
        $numAvailableMigrations = count($availableMigrations);
684
685
        // Executed Unavailable migration count
686
        $numExecutedUnavailableMigrations = count($this->getUnavailableMigratedVersions());
687
688
        // New migration count
689
        $numNewMigrations = $numAvailableMigrations - ($numExecutedMigrations - $numExecutedUnavailableMigrations);
690
691
        return [
692
            'name' => $this->getName(),
693
            'database_driver' => 'MongoDB',
694
            'migrations_database_name' => $this->getMigrationsDatabaseName(),
695
            'migrations_collection_name' => $this->getMigrationsCollectionName(),
696
            'migrations_namespace' => $this->getMigrationsNamespace(),
697
            'migrations_directory' => $this->getMigrationsDirectory(),
698
            'current_version' => $this->getCurrentVersion(),
699
            'latest_version' => $this->getLatestVersion(),
700
            'num_executed_migrations' => $numExecutedMigrations,
701
            'num_executed_unavailable_migrations' => $numExecutedUnavailableMigrations,
702
            'num_available_migrations' => $numAvailableMigrations,
703
            'num_new_migrations' => $numNewMigrations,
704
        ];
705
    }
706
}
707