Completed
Push — master ( e82b97...62734f )
by Richard
04:18
created

Manager::getSeeds()   B

Complexity

Conditions 8
Paths 10

Size

Total Lines 53
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 29
CRAP Score 8.0023

Importance

Changes 0
Metric Value
dl 0
loc 53
ccs 29
cts 30
cp 0.9667
rs 7.1199
c 0
b 0
f 0
cc 8
eloc 28
nc 10
nop 0
crap 8.0023

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Phinx
4
 *
5
 * (The MIT license)
6
 * Copyright (c) 2015 Rob Morgan
7
 *
8
 * Permission is hereby granted, free of charge, to any person obtaining a copy
9
 * of this software and associated * documentation files (the "Software"), to
10
 * deal in the Software without restriction, including without limitation the
11
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
12
 * sell copies of the Software, and to permit persons to whom the Software is
13
 * furnished to do so, subject to the following conditions:
14
 *
15
 * The above copyright notice and this permission notice shall be included in
16
 * all copies or substantial portions of the Software.
17
 *
18
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
24
 * IN THE SOFTWARE.
25
 *
26
 * @package    Phinx
27
 * @subpackage Phinx\Migration
28
 */
29
namespace Phinx\Migration;
30
31
use Phinx\Config\ConfigInterface;
32
use Phinx\Config\NamespaceAwareInterface;
33
use Phinx\Migration\Manager\Environment;
34
use Phinx\Seed\AbstractSeed;
35
use Phinx\Seed\SeedInterface;
36
use Phinx\Util\Util;
37
use Symfony\Component\Console\Input\InputInterface;
38
use Symfony\Component\Console\Output\OutputInterface;
39
40
class Manager
41
{
42
    /**
43
     * @var \Phinx\Config\ConfigInterface
44
     */
45
    protected $config;
46
47
    /**
48
     * @var \Symfony\Component\Console\Input\InputInterface
49
     */
50
    protected $input;
51
52
    /**
53
     * @var \Symfony\Component\Console\Output\OutputInterface
54
     */
55
    protected $output;
56
57
    /**
58
     * @var array
59
     */
60
    protected $environments;
61
62
    /**
63
     * @var array
64
     */
65
    protected $migrations;
66
67
    /**
68
     * @var array
69
     */
70
    protected $seeds;
71
72
    /**
73
     * @var integer
74
     */
75
    const EXIT_STATUS_DOWN = 3;
76
77
    /**
78
     * @var integer
79
     */
80
    const EXIT_STATUS_MISSING = 2;
81
82
    /**
83
     * Class Constructor.
84
     *
85
     * @param \Phinx\Config\ConfigInterface $config Configuration Object
86
     * @param \Symfony\Component\Console\Input\InputInterface $input Console Input
87
     * @param \Symfony\Component\Console\Output\OutputInterface $output Console Output
88
     */
89 432
    public function __construct(ConfigInterface $config, InputInterface $input, OutputInterface $output)
90
    {
91 432
        $this->setConfig($config);
92 432
        $this->setInput($input);
93 432
        $this->setOutput($output);
94 432
    }
95
96
    /**
97
     * Prints the specified environment's migration status.
98
     *
99
     * @param string $environment
100
     * @param null $format
101
     * @return int 0 if all migrations are up, or an error code
102
     */
103 22
    public function printStatus($environment, $format = null)
104
    {
105 22
        $output = $this->getOutput();
106 22
        $hasDownMigration = false;
107 22
        $hasMissingMigration = false;
108 22
        $migrations = $this->getMigrations($environment);
109 22
        $migrationCount = 0;
110 22
        $missingCount = 0;
111
        $pendingMigrationCount = 0;
112
        if (count($migrations)) {
113 21
            // TODO - rewrite using Symfony Table Helper as we already have this library
114
            // included and it will fix formatting issues (e.g drawing the lines)
115 21
            $output->writeln('');
116 21
117 19
            switch ($this->getConfig()->getVersionOrder()) {
118 19
                case \Phinx\Config\Config::VERSION_ORDER_CREATION_TIME:
119 2
                    $migrationIdAndStartedHeader = "<info>[Migration ID]</info>  Started            ";
120 1
                    break;
121 1
                case \Phinx\Config\Config::VERSION_ORDER_EXECUTION_TIME:
122 1
                    $migrationIdAndStartedHeader = "Migration ID    <info>[Started          ]</info>";
123 1
                    break;
124 21
                default:
125
                    throw new \RuntimeException('Invalid version_order configuration option');
126 20
            }
127 20
128
            $output->writeln(" Status  $migrationIdAndStartedHeader  Finished             Migration Name ");
129 20
            $output->writeln('----------------------------------------------------------------------------------');
130 20
131
            $env = $this->getEnvironment($environment);
132
            $versions = $env->getVersionLog();
133 17
134 20
            $maxNameLength = $versions ? max(array_map(function ($version) {
135
                return strlen($version['migration_name']);
136 20
            }, $versions)) : 0;
137
138 20
            $missingVersions = array_diff_key($versions, $migrations);
139
            $missingCount = count($missingVersions);
140
141 20
            $hasMissingMigration = !empty($missingVersions);
142
143 20
            // get the migrations sorted in the same way as the versions
144 17
            $sortedMigrations = [];
145 13
146 13
            foreach ($versions as $versionCreationTime => $version) {
147 13
                if (isset($migrations[$versionCreationTime])) {
148 20
                    array_push($sortedMigrations, $migrations[$versionCreationTime]);
149
                    unset($migrations[$versionCreationTime]);
150 20
                }
151
            }
152
153 4
            if (empty($sortedMigrations) && !empty($missingVersions)) {
154 4
                // this means we have no up migrations, so we write all the missing versions already so they show up
155
                // before any possible down migration
156 4
                foreach ($missingVersions as $missingVersionCreationTime => $missingVersion) {
157 4
                    $this->printMissingVersion($missingVersion, $maxNameLength);
158 4
159
                    unset($missingVersions[$missingVersionCreationTime]);
160
                }
161
            }
162 20
163 13
            // any migration left in the migrations (ie. not unset when sorting the migrations by the version order) is
164 13
            // a migration that is down, so we add them to the end of the sorted migrations list
165
            if (!empty($migrations)) {
166 20
                $sortedMigrations = array_merge($sortedMigrations, $migrations);
167 20
            }
168 20
169
            $migrationCount = count($sortedMigrations);
170 13
            foreach ($sortedMigrations as $migration) {
171 6
                $version = array_key_exists($migration->getVersion(), $versions) ? $versions[$migration->getVersion()] : false;
172 6
                if ($version) {
173 4
                    // check if there are missing versions before this version
174
                    foreach ($missingVersions as $missingVersionCreationTime => $missingVersion) {
175 3
                        if ($this->getConfig()->isVersionOrderCreationTime()) {
176
                            if ($missingVersion['version'] > $version['version']) {
177
                                break;
178
                            }
179
                        } else {
180
                            if ($missingVersion['start_time'] > $version['start_time']) {
181
                                break;
182
                            } elseif ($missingVersion['start_time'] == $version['start_time'] &&
183
                                $missingVersion['version'] > $version['version']) {
184 3
                                break;
185
                            }
186 3
                        }
187 13
188
                        $this->printMissingVersion($missingVersion, $maxNameLength);
189 13
190 13
                        unset($missingVersions[$missingVersionCreationTime]);
191 13
                    }
192 13
193
                    $status = '     <info>up</info> ';
194 20
                } else {
195
                    $pendingMigrationCount++;
196 20
                    $hasDownMigration = true;
197 20
                    $status = '   <error>down</error> ';
198 20
                }
199 20
                $maxNameLength = max($maxNameLength, strlen($migration->getName()));
200 20
201 20
                $output->writeln(sprintf(
202 20
                    '%s %14.0f  %19s  %19s  <comment>%s</comment>',
203 20
                    $status,
204
                    $migration->getVersion(),
205 20
                    $version['start_time'],
206 1
                    $version['end_time'],
207 1
                    $migration->getName()
208
                ));
209 20
210 20
                if ($version && $version['breakpoint']) {
211 20
                    $output->writeln('         <error>BREAKPOINT SET</error>');
212
                }
213
214 20
                $migrations[] = ['migration_status' => trim(strip_tags($status)), 'migration_id' => sprintf('%14.0f', $migration->getVersion()), 'migration_name' => $migration->getName()];
215 4
                unset($versions[$migration->getVersion()]);
216
            }
217 4
218 20
            // and finally add any possibly-remaining missing migrations
219 20
            foreach ($missingVersions as $missingVersionCreationTime => $missingVersion) {
220
                $this->printMissingVersion($missingVersion, $maxNameLength);
221 1
222 1
                unset($missingVersions[$missingVersionCreationTime]);
223
            }
224
        } else {
225
            // there are no migrations
226 21
            $output->writeln('');
227 21
            $output->writeln('There are no available migrations. Try creating one using the <info>create</info> command.');
228
        }
229
230
        // write an empty line
231
        $output->writeln('');
232
        if ($format !== null) {
233
            switch ($format) {
234
                case 'json':
235
                    $output->writeln(json_encode(
236
                        [
237
                            'pending_count' => $pendingMigrationCount,
238
                            'missing_count' => $missingCount,
239
                            'total_count' => $migrationCount + $missingCount,
240
                            'migrations' => $migrations
241
                        ]
242 21
                    ));
243 10
                    break;
244 11
                default:
245 6
                    $output->writeln('<info>Unsupported format: ' . $format . '</info>');
246
            }
247 5
        }
248
249
        if ($hasMissingMigration) {
250
            return self::EXIT_STATUS_MISSING;
251
        } elseif ($hasDownMigration) {
252
            return self::EXIT_STATUS_DOWN;
253
        } else {
254
            return 0;
255
        }
256
    }
257 10
258
    /**
259 10
     * Print Missing Version
260 10
     *
261 10
     * @param array $version        The missing version to print (in the format returned by Environment.getVersionLog).
262 10
     * @param int   $maxNameLength  The maximum migration name length.
263 10
     */
264 10
    private function printMissingVersion($version, $maxNameLength)
265 10
    {
266
        $this->getOutput()->writeln(sprintf(
267 10
            '     <error>up</error>  %14.0f  %19s  %19s  <comment>%s</comment>  <error>** MISSING **</error>',
268 1
            $version['version'],
269 1
            $version['start_time'],
270 10
            $version['end_time'],
271
            str_pad($version['migration_name'], $maxNameLength, ' ')
272
        ));
273
274
        if ($version && $version['breakpoint']) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $version of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
275
            $this->getOutput()->writeln('         <error>BREAKPOINT SET</error>');
276
        }
277
    }
278
279
    /**
280 4
     * Migrate to the version of the database on a given date.
281
     *
282 4
     * @param string    $environment Environment
283 4
     * @param \DateTime $dateTime    Date to migrate to
284
     * @param bool      $fake        flag that if true, we just record running the migration, but not actually do the
285
     *                               migration
286 4
     *
287 4
     * @return void
288
     */
289 4
    public function migrateToDateTime($environment, \DateTime $dateTime, $fake = false)
290 3
    {
291 3
        $versions = array_keys($this->getMigrations($environment));
292 3
        $dateString = $dateTime->format('YmdHis');
293 3
294 4
        $outstandingMigrations = array_filter($versions, function ($version) use ($dateString) {
295
            return $version <= $dateString;
296
        });
297
298
        if (count($outstandingMigrations) > 0) {
299
            $migration = max($outstandingMigrations);
300
            $this->getOutput()->writeln('Migrating to version ' . $migration);
301
            $this->migrate($environment, $migration, $fake);
302
        }
303 8
    }
304
305 8
    /**
306 8
     * Migrate an environment to the specified version.
307 8
     *
308 8
     * @param string $environment Environment
309
     * @param int    $version     version to migrate to
310 8
     * @param bool   $fake        flag that if true, we just record running the migration, but not actually do the migration
311
     * @return void
312
     */
313
    public function migrate($environment, $version = null, $fake = false)
314 8
    {
315 5
        $migrations = $this->getMigrations($environment);
316 5
        $env = $this->getEnvironment($environment);
317 3
        $versions = $env->getVersions();
318
        $current = $env->getCurrentVersion();
319
320
        if (empty($versions) && empty($migrations)) {
321
            return;
322
        }
323
324
        if ($version === null) {
325
            $version = max(array_merge($versions, array_keys($migrations)));
326 View Code Duplication
        } else {
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...
327 8
            if (0 != $version && !isset($migrations[$version])) {
328
                $this->output->writeln(sprintf(
329 8
                    '<comment>warning</comment> %s is not a valid version',
330
                    $version
331
                ));
332
333
                return;
334
            }
335
        }
336
337
        // are we migrating up or down?
338
        $direction = $version > $current ? MigrationInterface::UP : MigrationInterface::DOWN;
339
340
        if ($direction === MigrationInterface::DOWN) {
341
            // run downs first
342
            krsort($migrations);
343 8 View Code Duplication
            foreach ($migrations as $migration) {
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...
344 8
                if ($migration->getVersion() <= $version) {
345 8
                    break;
346 2
                }
347
348
                if (in_array($migration->getVersion(), $versions)) {
349 8
                    $this->executeMigration($environment, $migration, MigrationInterface::DOWN, $fake);
350 5
                }
351 5
            }
352 8
        }
353 8
354
        ksort($migrations);
355 View Code Duplication
        foreach ($migrations as $migration) {
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...
356
            if ($migration->getVersion() > $version) {
357
                break;
358
            }
359
360
            if (!in_array($migration->getVersion(), $versions)) {
361
                $this->executeMigration($environment, $migration, MigrationInterface::UP, $fake);
362
            }
363 119
        }
364
    }
365 119
366 119
    /**
367
     * Execute a migration against the specified environment.
368 119
     *
369 119
     * @param string $name Environment Name
370 119
     * @param \Phinx\Migration\MigrationInterface $migration Migration
371
     * @param string $direction Direction
372
     * @param bool $fake flag that if true, we just record running the migration, but not actually do the migration
373 119
     * @return void
374 119
     */
375 119
    public function executeMigration($name, MigrationInterface $migration, $direction = MigrationInterface::UP, $fake = false)
376
    {
377 119
        $this->getOutput()->writeln('');
378
        $this->getOutput()->writeln(
379 119
            ' ==' .
380 119
            ' <info>' . $migration->getVersion() . ' ' . $migration->getName() . ':</info>' .
381 119
            ' <comment>' . ($direction === MigrationInterface::UP ? 'migrating' : 'reverting') . '</comment>'
382 119
        );
383 119
        $migration->preFlightCheck($direction);
384
385
        // Execute the migration and log the time elapsed.
386
        $start = microtime(true);
387
        $this->getEnvironment($name)->executeMigration($migration, $direction, $fake);
388
        $end = microtime(true);
389
390
        $this->getOutput()->writeln(
391
            ' ==' .
392 6
            ' <info>' . $migration->getVersion() . ' ' . $migration->getName() . ':</info>' .
393
            ' <comment>' . ($direction === MigrationInterface::UP ? 'migrated' : 'reverted') .
394 6
            ' ' . sprintf('%.4fs', $end - $start) . '</comment>'
395 6
        );
396
    }
397 6
398 6
    /**
399 6
     * Execute a seeder against the specified environment.
400
     *
401
     * @param string $name Environment Name
402 6
     * @param \Phinx\Seed\SeedInterface $seed Seed
403 6
     * @return void
404 6
     */
405
    public function executeSeed($name, SeedInterface $seed)
406 6
    {
407
        $this->getOutput()->writeln('');
408 6
        $this->getOutput()->writeln(
409 6
            ' ==' .
410 6
            ' <info>' . $seed->getName() . ':</info>' .
411 6
            ' <comment>seeding</comment>'
412 6
        );
413
414
        // Execute the seeder and log the time elapsed.
415
        $start = microtime(true);
416
        $this->getEnvironment($name)->executeSeed($seed);
417
        $end = microtime(true);
418
419
        $this->getOutput()->writeln(
420
            ' ==' .
421
            ' <info>' . $seed->getName() . ':</info>' .
422
            ' <comment>seeded' .
423 349
            ' ' . sprintf('%.4fs', $end - $start) . '</comment>'
424
        );
425
    }
426 349
427
    /**
428
     * Rollback an environment to the specified version.
429 349
     *
430
     * @param string $environment Environment
431
     * @param int|string $target
432 349
     * @param bool $force
433
     * @param bool $targetMustMatchVersion
434 349
     * @param bool $fake flag that if true, we just record running the migration, but not actually do the migration
435
     * @return void
436
     */
437 349
    public function rollback($environment, $target = null, $force = false, $targetMustMatchVersion = true, $fake = false)
438 48
    {
439 48
        // note that the migrations are indexed by name (aka creation time) in ascending order
440 48
        $migrations = $this->getMigrations($environment);
441
442 349
        // note that the version log are also indexed by name with the proper ascending order according to the version order
443 349
        $executedVersions = $this->getEnvironment($environment)->getVersionLog();
444 349
445
        // get a list of migrations sorted in the opposite way of the executed versions
446
        $sortedMigrations = [];
447 47
448
        foreach ($executedVersions as $versionCreationTime => &$executedVersion) {
449 349
            // if we have a date (ie. the target must not match a version) and we are sorting by execution time, we
450
            // convert the version start time so we can compare directly with the target date
451 349
            if (!$this->getConfig()->isVersionOrderCreationTime() && !$targetMustMatchVersion) {
452 23
                $dateTime = \DateTime::createFromFormat('Y-m-d H:i:s', $executedVersion['start_time']);
453 349
                $executedVersion['start_time'] = $dateTime->format('YmdHis');
454
            }
455 20
456 20
            if (isset($migrations[$versionCreationTime])) {
457 20
                array_unshift($sortedMigrations, $migrations[$versionCreationTime]);
458 20
            } else {
459
                // this means the version is missing so we unset it so that we don't consider it when rolling back
460
                // migrations (or choosing the last up version as target)
461 20
                unset($executedVersions[$versionCreationTime]);
462 20
            }
463 20
        }
464
465
        if ($target === 'all' || $target === '0') {
466
            $target = 0;
467 20
        } elseif (!is_numeric($target) && !is_null($target)) { // try to find a target version based on name
468
            // search through the migrations using the name
469
            $migrationNames = array_map(function ($item) {
470 349
                return $item['migration_name'];
471 349
            }, $executedVersions);
472 53
            $found = array_search($target, $migrationNames);
473 53
474
            // check on was found
475
            if ($found !== false) {
476
                $target = (string)$found;
477 296
            } else {
478
                $this->getOutput()->writeln("<error>No migration found with name ($target)</error>");
479 94
480 94
                return;
481 94
            }
482
        }
483
484 296
        // Check we have at least 1 migration to revert
485 46
        $executedVersionCreationTimes = array_keys($executedVersions);
486 46
        if (empty($executedVersionCreationTimes) || $target == end($executedVersionCreationTimes)) {
487
            $this->getOutput()->writeln('<error>No migrations to rollback</error>');
488
489
            return;
490 250
        }
491
492 250
        // If no target was supplied, revert the last migration
493 250
        if ($target === null) {
494 70
            // Get the migration before the last run migration
495
            $prev = count($executedVersionCreationTimes) - 2;
496
            $target = $prev >= 0 ? $executedVersionCreationTimes[$prev] : 0;
497 250
        }
498 250
499
        // If the target must match a version, check the target version exists
500 250
        if ($targetMustMatchVersion && 0 !== $target && !isset($migrations[$target])) {
501 96
            $this->getOutput()->writeln("<error>Target version ($target) not found</error>");
502 96
503 42
            return;
504
        }
505 68
506
        // Rollback all versions until we find the wanted rollback target
507 222
        $rollbacked = false;
508 121
509 121
        foreach ($sortedMigrations as $migration) {
510
            if ($targetMustMatchVersion && $migration->getVersion() == $target) {
511 117
                break;
512 117
            }
513 117
514 250
            if (in_array($migration->getVersion(), $executedVersionCreationTimes)) {
515
                $executedVersion = $executedVersions[$migration->getVersion()];
516 250
517 133
                if (!$targetMustMatchVersion) {
518 133
                    if (($this->getConfig()->isVersionOrderCreationTime() && $executedVersion['version'] <= $target) ||
519 250
                        (!$this->getConfig()->isVersionOrderCreationTime() && $executedVersion['start_time'] <= $target)) {
520
                        break;
521
                    }
522
                }
523
524
                if (0 != $executedVersion['breakpoint'] && !$force) {
525
                    $this->getOutput()->writeln('<error>Breakpoint reached. Further rollbacks inhibited.</error>');
526
                    break;
527
                }
528 9
                $this->executeMigration($environment, $migration, MigrationInterface::DOWN, $fake);
529
                $rollbacked = true;
530 9
            }
531
        }
532 9
533
        if (!$rollbacked) {
534 3
            $this->getOutput()->writeln('<error>No migrations to rollback</error>');
535 3
        }
536 3
    }
537 3
538 3
    /**
539 3
     * Run database seeders against an environment.
540
     *
541 6
     * @param string $environment Environment
542 3
     * @param string $seed Seeder
543 3
     * @return void
544 3
     */
545
    public function seed($environment, $seed = null)
546
    {
547 6
        $seeds = $this->getSeeds();
548
549
        if ($seed === null) {
550
            // run all seeders
551
            foreach ($seeds as $seeder) {
552
                if (array_key_exists($seeder->getName(), $seeds)) {
553
                    $this->executeSeed($environment, $seeder);
554
                }
555 381
            }
556
        } else {
557 381
            // run only one seeder
558 381
            if (array_key_exists($seed, $seeds)) {
559
                $this->executeSeed($environment, $seeds[$seed]);
560
            } else {
561
                throw new \InvalidArgumentException(sprintf('The seed class "%s" does not exist', $seed));
562
            }
563
        }
564
    }
565
566
    /**
567
     * Sets the environments.
568 382
     *
569
     * @param array $environments Environments
570 382
     * @return \Phinx\Migration\Manager
571 380
     */
572
    public function setEnvironments($environments = [])
573
    {
574
        $this->environments = $environments;
575 7
576 1
        return $this;
577 1
    }
578
579 1
    /**
580
     * Gets the manager class for the given environment.
581
     *
582
     * @param string $name Environment Name
583 6
     * @throws \InvalidArgumentException
584 6
     * @return \Phinx\Migration\Manager\Environment
585
     */
586 6
    public function getEnvironment($name)
587 6
    {
588 6
        if (isset($this->environments[$name])) {
589 6
            return $this->environments[$name];
590
        }
591 6
592
        // check the environment exists
593
        if (!$this->getConfig()->hasEnvironment($name)) {
594
            throw new \InvalidArgumentException(sprintf(
595
                'The environment "%s" does not exist',
596
                $name
597
            ));
598
        }
599
600 400
        // create an environment instance and cache it
601
        $envOptions = $this->getConfig()->getEnvironment($name);
602 400
        $envOptions['version_order'] = $this->getConfig()->getVersionOrder();
603 400
604
        $environment = new Environment($name, $envOptions);
605
        $this->environments[$name] = $environment;
606
        $environment->setInput($this->getInput());
607
        $environment->setOutput($this->getOutput());
608
609
        return $environment;
610
    }
611 393
612
    /**
613 393
     * Sets the console input.
614
     *
615
     * @param \Symfony\Component\Console\Input\InputInterface $input Input
616
     * @return \Phinx\Migration\Manager
617
     */
618
    public function setInput(InputInterface $input)
619
    {
620
        $this->input = $input;
621
622 400
        return $this;
623
    }
624 400
625 400
    /**
626
     * Gets the console input.
627
     *
628
     * @return \Symfony\Component\Console\Input\InputInterface
629
     */
630
    public function getInput()
631
    {
632
        return $this->input;
633 395
    }
634
635 395
    /**
636
     * Sets the console output.
637
     *
638
     * @param \Symfony\Component\Console\Output\OutputInterface $output Output
639
     * @return \Phinx\Migration\Manager
640
     */
641
    public function setOutput(OutputInterface $output)
642
    {
643
        $this->output = $output;
644 379
645
        return $this;
646 379
    }
647 379
648
    /**
649
     * Gets the console output.
650
     *
651
     * @return \Symfony\Component\Console\Output\OutputInterface
652
     */
653
    public function getOutput()
654
    {
655
        return $this->output;
656
    }
657 388
658
    /**
659 388
     * Sets the database migrations.
660 388
     *
661
     * @param array $migrations Migrations
662
     * @return \Phinx\Migration\Manager
663 388
     */
664
    public function setMigrations(array $migrations)
665 388
    {
666
        $this->migrations = $migrations;
667 388
668 387
        return $this;
669 387
    }
670
671 387
    /**
672 3
     * Gets an array of the database migrations, indexed by migration name (aka creation time) and sorted in ascending
673
     * order
674
     *
675 387
     * @param string $environment Environment
676 387
     * @throws \InvalidArgumentException
677
     * @return \Phinx\Migration\AbstractMigration[]
678
     */
679 387
    public function getMigrations($environment)
680
    {
681 387
        if ($this->migrations === null) {
682 2
            $phpFiles = $this->getMigrationFiles();
683 2
684 2 View Code Duplication
            if (OutputInterface::VERBOSITY_VERBOSE <= $this->getOutput()->getVerbosity()) {
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...
685 2
                $this->getOutput()->writeln("Migration file " . implode(", ", $phpFiles));
686 2
            }
687
688
            // filter the files to only get the ones that match our naming scheme
689 387
            $fileNames = [];
690
            /** @var \Phinx\Migration\AbstractMigration[] $versions */
691
            $versions = [];
692
693 387
            foreach ($phpFiles as $filePath) {
694 387
                if (Util::isValidMigrationFileName(basename($filePath))) {
695 2 View Code Duplication
                    if (OutputInterface::VERBOSITY_VERBOSE <= $this->getOutput()->getVerbosity()) {
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...
696 2
                        $this->getOutput()->writeln("Valid migration file " . implode(", ", $phpFiles));
697 2
                    }
698
699 2
                    $version = Util::getVersionFromFileName(basename($filePath));
700
701
                    if (isset($versions[$version])) {
702
                        throw new \InvalidArgumentException(sprintf('Duplicate migration - "%s" has the same version as "%s"', $filePath, $versions[$version]->getVersion()));
703 385
                    }
704
705 385
                    $config = $this->getConfig();
706 2
                    $namespace = $config instanceof NamespaceAwareInterface ? $config->getMigrationNamespaceByPath(dirname($filePath)) : null;
707 2
708 2
                    // convert the filename to a class name
709
                    $class = ($namespace === null ? '' : $namespace . '\\') . Util::mapFileNameToClassName(basename($filePath));
710 2
711
                    if (isset($fileNames[$class])) {
712
                        throw new \InvalidArgumentException(sprintf(
713 383
                            'Migration "%s" has the same name as "%s"',
714 383
                            basename($filePath),
715 384
                            $fileNames[$class]
716
                        ));
717 379
                    }
718 379
719 379
                    $fileNames[$class] = basename($filePath);
720
721 379
                    if (OutputInterface::VERBOSITY_VERBOSE <= $this->getOutput()->getVerbosity()) {
722
                        $this->getOutput()->writeln("Loading class $class from $filePath");
723
                    }
724
725
                    // load the migration file
726
                    /** @noinspection PhpIncludeInspection */
727
                    $orig_display_errors_setting = ini_get('display_errors');
728
                    ini_set('display_errors', 'On');
729 388
                    require_once $filePath;
730
                    ini_set('display_errors', $orig_display_errors_setting);
731 388
                    if (!class_exists($class)) {
732 388
                        throw new \InvalidArgumentException(sprintf(
733 388
                            'Could not find class "%s" in file "%s"',
734
                            $class,
735 388
                            $filePath
736 388
                        ));
737 388
                    }
738 388
739 388
                    if (OutputInterface::VERBOSITY_VERBOSE <= $this->getOutput()->getVerbosity()) {
740 388
                        $this->getOutput()->writeln("Running $class");
741
                    }
742 388
743
                    // instantiate it
744
                    $migration = new $class($environment, $version, $this->getInput(), $this->getOutput());
745
746
                    if (!($migration instanceof AbstractMigration)) {
747
                        throw new \InvalidArgumentException(sprintf(
748
                            'The class "%s" in file "%s" must extend \Phinx\Migration\AbstractMigration',
749
                            $class,
750
                            $filePath
751 11
                        ));
752
                    }
753 11
754 11
                    $versions[$version] = $migration;
755 View Code Duplication
                } else {
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...
756
                    if (OutputInterface::VERBOSITY_VERBOSE <= $this->getOutput()->getVerbosity()) {
757
                        $this->getOutput()->writeln("Invalid migration file " . implode(", ", $phpFiles));
758
                    }
759
                }
760
            }
761
762
            ksort($versions);
763 11
            $this->setMigrations($versions);
764
        }
765 11
766 11
        return $this->migrations;
767
    }
768
769 11
    /**
770
     * Returns a list of migration files found in the provided migration paths.
771 11
     *
772
     * @return string[]
773 11
     */
774 11 View Code Duplication
    protected function getMigrationFiles()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
775 11
    {
776 11
        $config = $this->getConfig();
777
        $paths = $config->getMigrationPaths();
778
        $files = [];
779 11
780 11
        foreach ($paths as $path) {
781
            $files = array_merge(
782
                $files,
783
                Util::glob($path . DIRECTORY_SEPARATOR . '*.php')
784 11
            );
785 11
        }
786
        // glob() can return the same file multiple times
787
        // This will cause the migration to fail with a
788
        // false assumption of duplicate migrations
789
        // http://php.net/manual/en/function.glob.php#110340
790
        $files = array_unique($files);
791
792
        return $files;
793
    }
794 11
795
    /**
796 11
     * Sets the database seeders.
797
     *
798
     * @param array $seeds Seeders
799
     * @return \Phinx\Migration\Manager
800
     */
801
    public function setSeeds(array $seeds)
802
    {
803
        $this->seeds = $seeds;
804 11
805 11
        return $this;
806 11
    }
807
808 11
    /**
809 11
     * Get seed dependencies instances from seed dependency array
810 11
     *
811
     * @param AbstractSeed $seed Seed
812 11
     *
813
     * @return AbstractSeed[]
814
     */
815
    private function getSeedDependenciesInstances(AbstractSeed $seed)
816
    {
817
        $dependenciesInstances = [];
818
        $dependencies = $seed->getDependencies();
819
        if (!empty($dependencies)) {
820 11
            foreach ($dependencies as $dependency) {
821
                foreach ($this->seeds as $seed) {
822 11
                    if (get_class($seed) === $dependency) {
823 11
                        $dependenciesInstances[get_class($seed)] = $seed;
824 11
                    }
825
                }
826 11
            }
827 11
        }
828 11
829 11
        return $dependenciesInstances;
830 11
    }
831 11
832
    /**
833 11
     * Order seeds by dependencies
834
     *
835
     * @param AbstractSeed[] $seeds Seeds
836
     *
837
     * @return AbstractSeed[]
838
     */
839
    private function orderSeedsByDependencies(array $seeds)
840
    {
841
        $orderedSeeds = [];
842 400
        foreach ($seeds as $seed) {
843
            $key = get_class($seed);
844 400
            $dependencies = $this->getSeedDependenciesInstances($seed);
845 400
            if (!empty($dependencies)) {
846
                $orderedSeeds[$key] = $seed;
847
                $orderedSeeds = array_merge($this->orderSeedsByDependencies($dependencies), $orderedSeeds);
848
            } else {
849
                $orderedSeeds[$key] = $seed;
850
            }
851
        }
852
853 399
        return $orderedSeeds;
854
    }
855 399
856
    /**
857
     * Gets an array of database seeders.
858
     *
859
     * @throws \InvalidArgumentException
860
     * @return \Phinx\Seed\AbstractSeed[]
861
     */
862
    public function getSeeds()
863
    {
864
        if ($this->seeds === null) {
865 2
            $phpFiles = $this->getSeedFiles();
866
867 2
            // filter the files to only get the ones that match our naming scheme
868 2
            $fileNames = [];
869 2
            /** @var \Phinx\Seed\AbstractSeed[] $seeds */
870 2
            $seeds = [];
871
872 2
            foreach ($phpFiles as $filePath) {
873
                if (Util::isValidSeedFileName(basename($filePath))) {
874
                    $config = $this->getConfig();
875
                    $namespace = $config instanceof NamespaceAwareInterface ? $config->getSeedNamespaceByPath(dirname($filePath)) : null;
876 2
877 1
                    // convert the filename to a class name
878 1
                    $class = ($namespace === null ? '' : $namespace . '\\') . pathinfo($filePath, PATHINFO_FILENAME);
879 1
                    $fileNames[$class] = basename($filePath);
880
881 2
                    // load the seed file
882 1
                    /** @noinspection PhpIncludeInspection */
883 1
                    require_once $filePath;
884
                    if (!class_exists($class)) {
885 1
                        throw new \InvalidArgumentException(sprintf(
886 1
                            'Could not find class "%s" in file "%s"',
887
                            $class,
888
                            $filePath
889 1
                        ));
890
                    }
891 1
892
                    // instantiate it
893 1
                    $seed = new $class($this->getInput(), $this->getOutput());
894 1
895 1
                    if (!($seed instanceof AbstractSeed)) {
896 1
                        throw new \InvalidArgumentException(sprintf(
897 1
                            'The class "%s" in file "%s" must extend \Phinx\Seed\AbstractSeed',
898 1
                            $class,
899
                            $filePath
900
                        ));
901
                    }
902
903
                    $seeds[$class] = $seed;
904
                }
905
            }
906 1
907
            ksort($seeds);
908 1
            $this->setSeeds($seeds);
909 1
        }
910 1
911 1
        $this->seeds = $this->orderSeedsByDependencies($this->seeds);
912 1
913
        return $this->seeds;
914
    }
915
916
    /**
917
     * Returns a list of seed files found in the provided seed paths.
918
     *
919
     * @return string[]
920
     */
921 View Code Duplication
    protected function getSeedFiles()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
922
    {
923
        $config = $this->getConfig();
924
        $paths = $config->getSeedPaths();
925
        $files = [];
926
927
        foreach ($paths as $path) {
928
            $files = array_merge(
929
                $files,
930
                Util::glob($path . DIRECTORY_SEPARATOR . '*.php')
931
            );
932
        }
933
        // glob() can return the same file multiple times
934
        // This will cause the migration to fail with a
935
        // false assumption of duplicate migrations
936
        // http://php.net/manual/en/function.glob.php#110340
937
        $files = array_unique($files);
938
939
        return $files;
940
    }
941
942
    /**
943
     * Sets the config.
944
     *
945
     * @param  \Phinx\Config\ConfigInterface $config Configuration Object
946
     * @return \Phinx\Migration\Manager
947
     */
948
    public function setConfig(ConfigInterface $config)
949
    {
950
        $this->config = $config;
951
952
        return $this;
953
    }
954
955
    /**
956
     * Gets the config.
957
     *
958
     * @return \Phinx\Config\ConfigInterface
959
     */
960
    public function getConfig()
961
    {
962
        return $this->config;
963
    }
964
965
    /**
966
     * Toggles the breakpoint for a specific version.
967
     *
968
     * @param string $environment
969
     * @param int|null $version
970
     * @return void
971
     */
972
    public function toggleBreakpoint($environment, $version)
973
    {
974
        $migrations = $this->getMigrations($environment);
975
        $this->getMigrations($environment);
976
        $env = $this->getEnvironment($environment);
977
        $versions = $env->getVersionLog();
978
979
        if (empty($versions) || empty($migrations)) {
980
            return;
981
        }
982
983
        if ($version === null) {
984
            $lastVersion = end($versions);
985
            $version = $lastVersion['version'];
986
        }
987
988 View Code Duplication
        if (0 != $version && !isset($migrations[$version])) {
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...
989
            $this->output->writeln(sprintf(
990
                '<comment>warning</comment> %s is not a valid version',
991
                $version
992
            ));
993
994
            return;
995
        }
996
997
        $env->getAdapter()->toggleBreakpoint($migrations[$version]);
998
999
        $versions = $env->getVersionLog();
1000
1001
        $this->getOutput()->writeln(
1002
            ' Breakpoint ' . ($versions[$version]['breakpoint'] ? 'set' : 'cleared') .
1003
            ' for <info>' . $version . '</info>' .
1004
            ' <comment>' . $migrations[$version]->getName() . '</comment>'
1005
        );
1006
    }
1007
1008
    /**
1009
     * Remove all breakpoints
1010
     *
1011
     * @param string $environment
1012
     * @return void
1013
     */
1014
    public function removeBreakpoints($environment)
1015
    {
1016
        $this->getOutput()->writeln(sprintf(
1017
            ' %d breakpoints cleared.',
1018
            $this->getEnvironment($environment)->getAdapter()->resetAllBreakpoints()
1019
        ));
1020
    }
1021
}
1022