Completed
Pull Request — master (#1244)
by Šarūnas
01:29
created

Manager::orderSeedsByDependencies()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 18
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 18
ccs 8
cts 8
cp 1
rs 9.2
c 0
b 0
f 0
cc 4
eloc 12
nc 4
nop 1
crap 4
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\OrderedSeedInterface;
36
use Phinx\Seed\SeedInterface;
37
use Phinx\Util\Util;
38
use Symfony\Component\Console\Input\InputInterface;
39
use Symfony\Component\Console\Output\OutputInterface;
40
41
class Manager
42
{
43
    /**
44
     * @var \Phinx\Config\ConfigInterface
45
     */
46
    protected $config;
47
48
    /**
49
     * @var \Symfony\Component\Console\Input\InputInterface
50
     */
51
    protected $input;
52
53
    /**
54
     * @var \Symfony\Component\Console\Output\OutputInterface
55
     */
56
    protected $output;
57
58
    /**
59
     * @var array
60
     */
61
    protected $environments;
62
63
    /**
64
     * @var array
65
     */
66
    protected $migrations;
67
68
    /**
69
     * @var array
70
     */
71
    protected $seeds;
72
73
    /**
74
     * @var integer
75
     */
76
    const EXIT_STATUS_DOWN = 1;
77
78
    /**
79
     * @var integer
80
     */
81
    const EXIT_STATUS_MISSING = 2;
82
83
    /**
84
     * Class Constructor.
85
     *
86
     * @param \Phinx\Config\ConfigInterface $config Configuration Object
87
     * @param \Symfony\Component\Console\Input\InputInterface $input Console Input
88
     * @param \Symfony\Component\Console\Output\OutputInterface $output Console Output
89 432
     */
90
    public function __construct(ConfigInterface $config, InputInterface $input, OutputInterface $output)
91 432
    {
92 432
        $this->setConfig($config);
93 432
        $this->setInput($input);
94 432
        $this->setOutput($output);
95
    }
96
97
    /**
98
     * Prints the specified environment's migration status.
99
     *
100
     * @param string $environment
101
     * @param null $format
102
     * @return int 0 if all migrations are up, or an error code
103 22
     */
104
    public function printStatus($environment, $format = null)
105 22
    {
106 22
        $output = $this->getOutput();
107 22
        $migrations = [];
0 ignored issues
show
Unused Code introduced by
$migrations is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
108 22
        $hasDownMigration = false;
109 22
        $hasMissingMigration = false;
110 22
        $migrations = $this->getMigrations();
111
        if (count($migrations)) {
112
            // TODO - rewrite using Symfony Table Helper as we already have this library
113 21
            // included and it will fix formatting issues (e.g drawing the lines)
114
            $output->writeln('');
115 21
116 21
            switch ($this->getConfig()->getVersionOrder()) {
117 19
                case \Phinx\Config\Config::VERSION_ORDER_CREATION_TIME:
118 19
                    $migrationIdAndStartedHeader = "<info>[Migration ID]</info>  Started            ";
119 2
                    break;
120 1
                case \Phinx\Config\Config::VERSION_ORDER_EXECUTION_TIME:
121 1
                    $migrationIdAndStartedHeader = "Migration ID    <info>[Started          ]</info>";
122 1
                    break;
123 1
                default:
124 21
                    throw new \RuntimeException('Invalid version_order configuration option');
125
            }
126 20
127 20
            $output->writeln(" Status  $migrationIdAndStartedHeader  Finished             Migration Name ");
128
            $output->writeln('----------------------------------------------------------------------------------');
129 20
130 20
            $env = $this->getEnvironment($environment);
131
            $versions = $env->getVersionLog();
132
133 17
            $maxNameLength = $versions ? max(array_map(function ($version) {
134 20
                return strlen($version['migration_name']);
135
            }, $versions)) : 0;
136 20
137
            $missingVersions = array_diff_key($versions, $migrations);
138 20
139
            $hasMissingMigration = !empty($missingVersions);
140
141 20
            // get the migrations sorted in the same way as the versions
142
            $sortedMigrations = [];
143 20
144 17
            foreach ($versions as $versionCreationTime => $version) {
145 13
                if (isset($migrations[$versionCreationTime])) {
146 13
                    array_push($sortedMigrations, $migrations[$versionCreationTime]);
147 13
                    unset($migrations[$versionCreationTime]);
148 20
                }
149
            }
150 20
151
            if (empty($sortedMigrations) && !empty($missingVersions)) {
152
                // this means we have no up migrations, so we write all the missing versions already so they show up
153 4
                // before any possible down migration
154 4
                foreach ($missingVersions as $missingVersionCreationTime => $missingVersion) {
155
                    $this->printMissingVersion($missingVersion, $maxNameLength);
156 4
157 4
                    unset($missingVersions[$missingVersionCreationTime]);
158 4
                }
159
            }
160
161
            // any migration left in the migrations (ie. not unset when sorting the migrations by the version order) is
162 20
            // a migration that is down, so we add them to the end of the sorted migrations list
163 13
            if (!empty($migrations)) {
164 13
                $sortedMigrations = array_merge($sortedMigrations, $migrations);
165
            }
166 20
167 20
            foreach ($sortedMigrations as $migration) {
168 20
                $version = array_key_exists($migration->getVersion(), $versions) ? $versions[$migration->getVersion()] : false;
169
                if ($version) {
170 13
                    // check if there are missing versions before this version
171 6
                    foreach ($missingVersions as $missingVersionCreationTime => $missingVersion) {
172 6
                        if ($this->getConfig()->isVersionOrderCreationTime()) {
173 4
                            if ($missingVersion['version'] > $version['version']) {
174
                                break;
175 3
                            }
176
                        } else {
177
                            if ($missingVersion['start_time'] > $version['start_time']) {
178
                                break;
179
                            } elseif ($missingVersion['start_time'] == $version['start_time'] &&
180
                                $missingVersion['version'] > $version['version']) {
181
                                break;
182
                            }
183
                        }
184 3
185
                        $this->printMissingVersion($missingVersion, $maxNameLength);
186 3
187 13
                        unset($missingVersions[$missingVersionCreationTime]);
188
                    }
189 13
190 13
                    $status = '     <info>up</info> ';
191 13
                } else {
192 13
                    $hasDownMigration = true;
193
                    $status = '   <error>down</error> ';
194 20
                }
195
                $maxNameLength = max($maxNameLength, strlen($migration->getName()));
196 20
197 20
                $output->writeln(sprintf(
198 20
                    '%s %14.0f  %19s  %19s  <comment>%s</comment>',
199 20
                    $status,
200 20
                    $migration->getVersion(),
201 20
                    $version['start_time'],
202 20
                    $version['end_time'],
203 20
                    $migration->getName()
204
                ));
205 20
206 1
                if ($version && $version['breakpoint']) {
207 1
                    $output->writeln('         <error>BREAKPOINT SET</error>');
208
                }
209 20
210 20
                $migrations[] = ['migration_status' => trim(strip_tags($status)), 'migration_id' => sprintf('%14.0f', $migration->getVersion()), 'migration_name' => $migration->getName()];
211 20
                unset($versions[$migration->getVersion()]);
212
            }
213
214 20
            // and finally add any possibly-remaining missing migrations
215 4
            foreach ($missingVersions as $missingVersionCreationTime => $missingVersion) {
216
                $this->printMissingVersion($missingVersion, $maxNameLength);
217 4
218 20
                unset($missingVersions[$missingVersionCreationTime]);
219 20
            }
220
        } else {
221 1
            // there are no migrations
222 1
            $output->writeln('');
223
            $output->writeln('There are no available migrations. Try creating one using the <info>create</info> command.');
224
        }
225
226 21
        // write an empty line
227 21
        $output->writeln('');
228
        if ($format !== null) {
229
            switch ($format) {
230
                case 'json':
231
                    $output->writeln(json_encode(
232
                        [
233
                            'pending_count' => count($this->getMigrations()),
234
                            'migrations' => $migrations
235
                        ]
236
                    ));
237
                    break;
238
                default:
239
                    $output->writeln('<info>Unsupported format: ' . $format . '</info>');
240
            }
241
        }
242 21
243 10
        if ($hasMissingMigration) {
244 11
            return self::EXIT_STATUS_MISSING;
245 6
        } elseif ($hasDownMigration) {
246
            return self::EXIT_STATUS_DOWN;
247 5
        } else {
248
            return 0;
249
        }
250
    }
251
252
    /**
253
     * Print Missing Version
254
     *
255
     * @param array     $version        The missing version to print (in the format returned by Environment.getVersionLog).
256
     * @param int   $maxNameLength  The maximum migration name length.
257 10
     */
258
    private function printMissingVersion($version, $maxNameLength)
259 10
    {
260 10
        $this->getOutput()->writeln(sprintf(
261 10
            '     <error>up</error>  %14.0f  %19s  %19s  <comment>%s</comment>  <error>** MISSING **</error>',
262 10
            $version['version'],
263 10
            $version['start_time'],
264 10
            $version['end_time'],
265 10
            str_pad($version['migration_name'], $maxNameLength, ' ')
266
        ));
267 10
268 1
        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...
269 1
            $this->getOutput()->writeln('         <error>BREAKPOINT SET</error>');
270 10
        }
271
    }
272
273
    /**
274
     * Migrate to the version of the database on a given date.
275
     *
276
     * @param string    $environment Environment
277
     * @param \DateTime $dateTime    Date to migrate to
278
     *
279
     * @return void
280 4
     */
281
    public function migrateToDateTime($environment, \DateTime $dateTime)
282 4
    {
283 4
        $versions = array_keys($this->getMigrations());
284
        $dateString = $dateTime->format('YmdHis');
285
286 4
        $outstandingMigrations = array_filter($versions, function ($version) use ($dateString) {
287 4
            return $version <= $dateString;
288
        });
289 4
290 3
        if (count($outstandingMigrations) > 0) {
291 3
            $migration = max($outstandingMigrations);
292 3
            $this->getOutput()->writeln('Migrating to version ' . $migration);
293 3
            $this->migrate($environment, $migration);
294 4
        }
295
    }
296
297
    /**
298
     * Migrate an environment to the specified version.
299
     *
300
     * @param string $environment Environment
301
     * @param int $version
302
     * @return void
303 8
     */
304
    public function migrate($environment, $version = null)
305 8
    {
306 8
        $migrations = $this->getMigrations();
307 8
        $env = $this->getEnvironment($environment);
308 8
        $versions = $env->getVersions();
309
        $current = $env->getCurrentVersion();
310 8
311
        if (empty($versions) && empty($migrations)) {
312
            return;
313
        }
314 8
315 5
        if ($version === null) {
316 5
            $version = max(array_merge($versions, array_keys($migrations)));
317 3 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...
318
            if (0 != $version && !isset($migrations[$version])) {
319
                $this->output->writeln(sprintf(
320
                    '<comment>warning</comment> %s is not a valid version',
321
                    $version
322
                ));
323
324
                return;
325
            }
326
        }
327 8
328
        // are we migrating up or down?
329 8
        $direction = $version > $current ? MigrationInterface::UP : MigrationInterface::DOWN;
330
331
        if ($direction === MigrationInterface::DOWN) {
332
            // run downs first
333
            krsort($migrations);
334 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...
335
                if ($migration->getVersion() <= $version) {
336
                    break;
337
                }
338
339
                if (in_array($migration->getVersion(), $versions)) {
340
                    $this->executeMigration($environment, $migration, MigrationInterface::DOWN);
341
                }
342
            }
343 8
        }
344 8
345 8
        ksort($migrations);
346 2 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...
347
            if ($migration->getVersion() > $version) {
348
                break;
349 8
            }
350 5
351 5
            if (!in_array($migration->getVersion(), $versions)) {
352 8
                $this->executeMigration($environment, $migration, MigrationInterface::UP);
353 8
            }
354
        }
355
    }
356
357
    /**
358
     * Execute a migration against the specified environment.
359
     *
360
     * @param string $name Environment Name
361
     * @param \Phinx\Migration\MigrationInterface $migration Migration
362
     * @param string $direction Direction
363 119
     * @return void
364
     */
365 119
    public function executeMigration($name, MigrationInterface $migration, $direction = MigrationInterface::UP)
366 119
    {
367
        $this->getOutput()->writeln('');
368 119
        $this->getOutput()->writeln(
369 119
            ' ==' .
370 119
            ' <info>' . $migration->getVersion() . ' ' . $migration->getName() . ':</info>' .
371
            ' <comment>' . ($direction === MigrationInterface::UP ? 'migrating' : 'reverting') . '</comment>'
372
        );
373 119
374 119
        // Execute the migration and log the time elapsed.
375 119
        $start = microtime(true);
376
        $this->getEnvironment($name)->executeMigration($migration, $direction);
377 119
        $end = microtime(true);
378
379 119
        $this->getOutput()->writeln(
380 119
            ' ==' .
381 119
            ' <info>' . $migration->getVersion() . ' ' . $migration->getName() . ':</info>' .
382 119
            ' <comment>' . ($direction === MigrationInterface::UP ? 'migrated' : 'reverted') .
383 119
            ' ' . sprintf('%.4fs', $end - $start) . '</comment>'
384
        );
385
    }
386
387
    /**
388
     * Execute a seeder against the specified environment.
389
     *
390
     * @param string $name Environment Name
391
     * @param \Phinx\Seed\SeedInterface $seed Seed
392 6
     * @return void
393
     */
394 6
    public function executeSeed($name, SeedInterface $seed)
395 6
    {
396
        $this->getOutput()->writeln('');
397 6
        $this->getOutput()->writeln(
398 6
            ' ==' .
399 6
            ' <info>' . $seed->getName() . ':</info>' .
400
            ' <comment>seeding</comment>'
401
        );
402 6
403 6
        // Execute the seeder and log the time elapsed.
404 6
        $start = microtime(true);
405
        $this->getEnvironment($name)->executeSeed($seed);
406 6
        $end = microtime(true);
407
408 6
        $this->getOutput()->writeln(
409 6
            ' ==' .
410 6
            ' <info>' . $seed->getName() . ':</info>' .
411 6
            ' <comment>seeded' .
412 6
            ' ' . sprintf('%.4fs', $end - $start) . '</comment>'
413
        );
414
    }
415
416
    /**
417
     * Rollback an environment to the specified version.
418
     *
419
     * @param string $environment Environment
420
     * @param int|string $target
421
     * @param bool $force
422
     * @param bool $targetMustMatchVersion
423 349
     * @return void
424
     */
425
    public function rollback($environment, $target = null, $force = false, $targetMustMatchVersion = true)
426 349
    {
427
        // note that the migrations are indexed by name (aka creation time) in ascending order
428
        $migrations = $this->getMigrations();
429 349
430
        // note that the version log are also indexed by name with the proper ascending order according to the version order
431
        $executedVersions = $this->getEnvironment($environment)->getVersionLog();
432 349
433
        // get a list of migrations sorted in the opposite way of the executed versions
434 349
        $sortedMigrations = [];
435
436
        foreach ($executedVersions as $versionCreationTime => &$executedVersion) {
437 349
            // if we have a date (ie. the target must not match a version) and we are sorting by execution time, we
438 48
            // convert the version start time so we can compare directly with the target date
439 48
            if (!$this->getConfig()->isVersionOrderCreationTime() && !$targetMustMatchVersion) {
440 48
                $dateTime = \DateTime::createFromFormat('Y-m-d H:i:s', $executedVersion['start_time']);
441
                $executedVersion['start_time'] = $dateTime->format('YmdHis');
442 349
            }
443 349
444 349
            if (isset($migrations[$versionCreationTime])) {
445
                array_unshift($sortedMigrations, $migrations[$versionCreationTime]);
446
            } else {
447 47
                // this means the version is missing so we unset it so that we don't consider it when rolling back
448
                // migrations (or choosing the last up version as target)
449 349
                unset($executedVersions[$versionCreationTime]);
450
            }
451 349
        }
452 23
453 349
        if ($target === 'all' || $target === '0') {
454
            $target = 0;
455 20
        } elseif (!is_numeric($target) && !is_null($target)) { // try to find a target version based on name
456 20
            // search through the migrations using the name
457 20
            $migrationNames = array_map(function ($item) {
458 20
                return $item['migration_name'];
459
            }, $executedVersions);
460
            $found = array_search($target, $migrationNames);
461 20
462 20
            // check on was found
463 20
            if ($found !== false) {
464
                $target = (string)$found;
465
            } else {
466
                $this->getOutput()->writeln("<error>No migration found with name ($target)</error>");
467 20
468
                return;
469
            }
470 349
        }
471 349
472 53
        // Check we have at least 1 migration to revert
473 53
        $executedVersionCreationTimes = array_keys($executedVersions);
474
        if (empty($executedVersionCreationTimes) || $target == end($executedVersionCreationTimes)) {
475
            $this->getOutput()->writeln('<error>No migrations to rollback</error>');
476
477 296
            return;
478
        }
479 94
480 94
        // If no target was supplied, revert the last migration
481 94
        if ($target === null) {
482
            // Get the migration before the last run migration
483
            $prev = count($executedVersionCreationTimes) - 2;
484 296
            $target = $prev >= 0 ? $executedVersionCreationTimes[$prev] : 0;
485 46
        }
486 46
487
        // If the target must match a version, check the target version exists
488
        if ($targetMustMatchVersion && 0 !== $target && !isset($migrations[$target])) {
489
            $this->getOutput()->writeln("<error>Target version ($target) not found</error>");
490 250
491
            return;
492 250
        }
493 250
494 70
        // Rollback all versions until we find the wanted rollback target
495
        $rollbacked = false;
496
497 250
        foreach ($sortedMigrations as $migration) {
498 250
            if ($targetMustMatchVersion && $migration->getVersion() == $target) {
499
                break;
500 250
            }
501 96
502 96
            if (in_array($migration->getVersion(), $executedVersionCreationTimes)) {
503 42
                $executedVersion = $executedVersions[$migration->getVersion()];
504
505 68
                if (!$targetMustMatchVersion) {
506
                    if (($this->getConfig()->isVersionOrderCreationTime() && $executedVersion['version'] <= $target) ||
507 222
                        (!$this->getConfig()->isVersionOrderCreationTime() && $executedVersion['start_time'] <= $target)) {
508 121
                        break;
509 121
                    }
510
                }
511 117
512 117
                if (0 != $executedVersion['breakpoint'] && !$force) {
513 117
                    $this->getOutput()->writeln('<error>Breakpoint reached. Further rollbacks inhibited.</error>');
514 250
                    break;
515
                }
516 250
                $this->executeMigration($environment, $migration, MigrationInterface::DOWN);
517 133
                $rollbacked = true;
518 133
            }
519 250
        }
520
521
        if (!$rollbacked) {
522
            $this->getOutput()->writeln('<error>No migrations to rollback</error>');
523
        }
524
    }
525
526
    /**
527
     * Run database seeders against an environment.
528 9
     *
529
     * @param string $environment Environment
530 9
     * @param string $seed Seeder
531
     * @return void
532 9
     */
533
    public function seed($environment, $seed = null)
534 3
    {
535 3
        $seeds = $this->getSeeds();
536 3
537 3
        if ($seed === null) {
538 3
            // run all seeders
539 3
            foreach ($seeds as $seeder) {
540
                if (array_key_exists($seeder->getName(), $seeds)) {
541 6
                    $this->executeSeed($environment, $seeder);
542 3
                }
543 3
            }
544 3
        } else {
545
            // run only one seeder
546
            if (array_key_exists($seed, $seeds)) {
547 6
                $this->executeSeed($environment, $seeds[$seed]);
548
            } else {
549
                throw new \InvalidArgumentException(sprintf('The seed class "%s" does not exist', $seed));
550
            }
551
        }
552
    }
553
554
    /**
555 381
     * Sets the environments.
556
     *
557 381
     * @param array $environments Environments
558 381
     * @return \Phinx\Migration\Manager
559
     */
560
    public function setEnvironments($environments = [])
561
    {
562
        $this->environments = $environments;
563
564
        return $this;
565
    }
566
567
    /**
568 382
     * Gets the manager class for the given environment.
569
     *
570 382
     * @param string $name Environment Name
571 380
     * @throws \InvalidArgumentException
572
     * @return \Phinx\Migration\Manager\Environment
573
     */
574
    public function getEnvironment($name)
575 7
    {
576 1
        if (isset($this->environments[$name])) {
577 1
            return $this->environments[$name];
578
        }
579 1
580
        // check the environment exists
581
        if (!$this->getConfig()->hasEnvironment($name)) {
582
            throw new \InvalidArgumentException(sprintf(
583 6
                'The environment "%s" does not exist',
584 6
                $name
585
            ));
586 6
        }
587 6
588 6
        // create an environment instance and cache it
589 6
        $envOptions = $this->getConfig()->getEnvironment($name);
590
        $envOptions['version_order'] = $this->getConfig()->getVersionOrder();
591 6
592
        $environment = new Environment($name, $envOptions);
593
        $this->environments[$name] = $environment;
594
        $environment->setInput($this->getInput());
595
        $environment->setOutput($this->getOutput());
596
597
        return $environment;
598
    }
599
600 400
    /**
601
     * Sets the console input.
602 400
     *
603 400
     * @param \Symfony\Component\Console\Input\InputInterface $input Input
604
     * @return \Phinx\Migration\Manager
605
     */
606
    public function setInput(InputInterface $input)
607
    {
608
        $this->input = $input;
609
610
        return $this;
611 393
    }
612
613 393
    /**
614
     * Gets the console input.
615
     *
616
     * @return \Symfony\Component\Console\Input\InputInterface
617
     */
618
    public function getInput()
619
    {
620
        return $this->input;
621
    }
622 400
623
    /**
624 400
     * Sets the console output.
625 400
     *
626
     * @param \Symfony\Component\Console\Output\OutputInterface $output Output
627
     * @return \Phinx\Migration\Manager
628
     */
629
    public function setOutput(OutputInterface $output)
630
    {
631
        $this->output = $output;
632
633 395
        return $this;
634
    }
635 395
636
    /**
637
     * Gets the console output.
638
     *
639
     * @return \Symfony\Component\Console\Output\OutputInterface
640
     */
641
    public function getOutput()
642
    {
643
        return $this->output;
644 379
    }
645
646 379
    /**
647 379
     * Sets the database migrations.
648
     *
649
     * @param array $migrations Migrations
650
     * @return \Phinx\Migration\Manager
651
     */
652
    public function setMigrations(array $migrations)
653
    {
654
        $this->migrations = $migrations;
655
656
        return $this;
657 388
    }
658
659 388
    /**
660 388
     * Gets an array of the database migrations, indexed by migration name (aka creation time) and sorted in ascending
661
     * order
662
     *
663 388
     * @throws \InvalidArgumentException
664
     * @return \Phinx\Migration\AbstractMigration[]
665 388
     */
666
    public function getMigrations()
667 388
    {
668 387
        if ($this->migrations === null) {
669 387
            $phpFiles = $this->getMigrationFiles();
670
671 387
            // filter the files to only get the ones that match our naming scheme
672 3
            $fileNames = [];
673
            /** @var \Phinx\Migration\AbstractMigration[] $versions */
674
            $versions = [];
675 387
676 387
            foreach ($phpFiles as $filePath) {
677
                if (Util::isValidMigrationFileName(basename($filePath))) {
678
                    $version = Util::getVersionFromFileName(basename($filePath));
679 387
680
                    if (isset($versions[$version])) {
681 387
                        throw new \InvalidArgumentException(sprintf('Duplicate migration - "%s" has the same version as "%s"', $filePath, $versions[$version]->getVersion()));
682 2
                    }
683 2
684 2
                    $config = $this->getConfig();
685 2
                    $namespace = $config instanceof NamespaceAwareInterface ? $config->getMigrationNamespaceByPath(dirname($filePath)) : null;
686 2
687
                    // convert the filename to a class name
688
                    $class = ($namespace === null ? '' : $namespace . '\\') . Util::mapFileNameToClassName(basename($filePath));
689 387
690
                    if (isset($fileNames[$class])) {
691
                        throw new \InvalidArgumentException(sprintf(
692
                            'Migration "%s" has the same name as "%s"',
693 387
                            basename($filePath),
694 387
                            $fileNames[$class]
695 2
                        ));
696 2
                    }
697 2
698
                    $fileNames[$class] = basename($filePath);
699 2
700
                    // load the migration file
701
                    /** @noinspection PhpIncludeInspection */
702
                    require_once $filePath;
703 385
                    if (!class_exists($class)) {
704
                        throw new \InvalidArgumentException(sprintf(
705 385
                            'Could not find class "%s" in file "%s"',
706 2
                            $class,
707 2
                            $filePath
708 2
                        ));
709
                    }
710 2
711
                    // instantiate it
712
                    $migration = new $class($version, $this->getInput(), $this->getOutput());
713 383
714 383
                    if (!($migration instanceof AbstractMigration)) {
715 384
                        throw new \InvalidArgumentException(sprintf(
716
                            'The class "%s" in file "%s" must extend \Phinx\Migration\AbstractMigration',
717 379
                            $class,
718 379
                            $filePath
719 379
                        ));
720
                    }
721 379
722
                    $versions[$version] = $migration;
723
                }
724
            }
725
726
            ksort($versions);
727
            $this->setMigrations($versions);
728
        }
729 388
730
        return $this->migrations;
731 388
    }
732 388
733 388
    /**
734
     * Returns a list of migration files found in the provided migration paths.
735 388
     *
736 388
     * @return string[]
737 388
     */
738 388 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...
739 388
    {
740 388
        $config = $this->getConfig();
741
        $paths = $config->getMigrationPaths();
742 388
        $files = [];
743
744
        foreach ($paths as $path) {
745
            $files = array_merge(
746
                $files,
747
                Util::glob($path . DIRECTORY_SEPARATOR . '*.php')
748
            );
749
        }
750
        // glob() can return the same file multiple times
751 11
        // This will cause the migration to fail with a
752
        // false assumption of duplicate migrations
753 11
        // http://php.net/manual/en/function.glob.php#110340
754 11
        $files = array_unique($files);
755
756
        return $files;
757
    }
758
759
    /**
760
     * Sets the database seeders.
761
     *
762
     * @param array $seeds Seeders
763 11
     * @return \Phinx\Migration\Manager
764
     */
765 11
    public function setSeeds(array $seeds)
766 11
    {
767
        $this->seeds = $seeds;
768
769 11
        return $this;
770
    }
771 11
772
    /**
773 11
     * Get seed dependencies instances from seed dependency array
774 11
     *
775 11
     * @param OrderedSeedInterface $seed Seed
776 11
     *
777
     * @return AbstractSeed[]
778
     */
779 11
    protected function getSeedDependenciesInstances(OrderedSeedInterface $seed)
780 11
    {
781
        if (empty($this->seeds)) {
782
            return [];
783
        }
784 11
785 11
        $dependenciesInstances = [];
786
        $dependencies = $seed->getDependencies();
787
        foreach ($dependencies as $dependency) {
788
            foreach ($this->seeds as $seed) {
789
                if (get_class($seed) === $dependency) {
790
                    $dependenciesInstances[get_class($seed)] = $seed;
791
                }
792
            }
793
        }
794 11
795
        return $dependenciesInstances;
796 11
    }
797
798
    /**
799
     * Order seeds by dependencies
800
     *
801
     * @param AbstractSeed[] $seeds Seeds
802
     *
803
     * @return AbstractSeed[]
804 11
     */
805 11
    protected function orderSeedsByDependencies(array $seeds)
806 11
    {
807
        $orderedSeeds = [];
808 11
        foreach ($seeds as $seed) {
809 11
            $key = get_class($seed);
810 11
            if ($seed instanceof OrderedSeedInterface) {
811
                $dependencies = $this->getSeedDependenciesInstances($seed);
812 11
                $orderedSeeds[$key] = $seed;
813
                if (!empty($dependencies)) {
814
                    $orderedSeeds = array_merge($this->orderSeedsByDependencies($dependencies), $orderedSeeds);
815
                }
816
            } else {
817
                $orderedSeeds[$key] = $seed;
818
            }
819
        }
820 11
821
        return $orderedSeeds;
822 11
    }
823 11
824 11
    /**
825
     * Gets an array of database seeders.
826 11
     *
827 11
     * @throws \InvalidArgumentException
828 11
     * @return \Phinx\Seed\AbstractSeed[]
829 11
     */
830 11
    public function getSeeds()
831 11
    {
832
        if ($this->seeds === null) {
833 11
            $phpFiles = $this->getSeedFiles();
834
835
            // filter the files to only get the ones that match our naming scheme
836
            $fileNames = [];
837
            /** @var \Phinx\Seed\AbstractSeed[] $seeds */
838
            $seeds = [];
839
840
            foreach ($phpFiles as $filePath) {
841
                if (Util::isValidSeedFileName(basename($filePath))) {
842 400
                    $config = $this->getConfig();
843
                    $namespace = $config instanceof NamespaceAwareInterface ? $config->getSeedNamespaceByPath(dirname($filePath)) : null;
844 400
845 400
                    // convert the filename to a class name
846
                    $class = ($namespace === null ? '' : $namespace . '\\') . pathinfo($filePath, PATHINFO_FILENAME);
847
                    $fileNames[$class] = basename($filePath);
848
849
                    // load the seed file
850
                    /** @noinspection PhpIncludeInspection */
851
                    require_once $filePath;
852
                    if (!class_exists($class)) {
853 399
                        throw new \InvalidArgumentException(sprintf(
854
                            'Could not find class "%s" in file "%s"',
855 399
                            $class,
856
                            $filePath
857
                        ));
858
                    }
859
860
                    // instantiate it
861
                    $seed = new $class($this->getInput(), $this->getOutput());
862
863
                    if (!($seed instanceof AbstractSeed)) {
864
                        throw new \InvalidArgumentException(sprintf(
865 2
                            'The class "%s" in file "%s" must extend \Phinx\Seed\AbstractSeed',
866
                            $class,
867 2
                            $filePath
868 2
                        ));
869 2
                    }
870 2
871
                    $seeds[$class] = $seed;
872 2
                }
873
            }
874
875
            ksort($seeds);
876 2
            $this->setSeeds($seeds);
877 1
        }
878 1
879 1
        $this->seeds = $this->orderSeedsByDependencies($this->seeds);
880
        return $this->seeds;
881 2
    }
882 1
883 1
    /**
884
     * Returns a list of seed files found in the provided seed paths.
885 1
     *
886 1
     * @return string[]
887
     */
888 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...
889 1
    {
890
        $config = $this->getConfig();
891 1
        $paths = $config->getSeedPaths();
892
        $files = [];
893 1
894 1
        foreach ($paths as $path) {
895 1
            $files = array_merge(
896 1
                $files,
897 1
                Util::glob($path . DIRECTORY_SEPARATOR . '*.php')
898 1
            );
899
        }
900
        // glob() can return the same file multiple times
901
        // This will cause the migration to fail with a
902
        // false assumption of duplicate migrations
903
        // http://php.net/manual/en/function.glob.php#110340
904
        $files = array_unique($files);
905
906 1
        return $files;
907
    }
908 1
909 1
    /**
910 1
     * Sets the config.
911 1
     *
912 1
     * @param  \Phinx\Config\ConfigInterface $config Configuration Object
913
     * @return \Phinx\Migration\Manager
914
     */
915
    public function setConfig(ConfigInterface $config)
916
    {
917
        $this->config = $config;
918
919
        return $this;
920
    }
921
922
    /**
923
     * Gets the config.
924
     *
925
     * @return \Phinx\Config\ConfigInterface
926
     */
927
    public function getConfig()
928
    {
929
        return $this->config;
930
    }
931
932
    /**
933
     * Toggles the breakpoint for a specific version.
934
     *
935
     * @param string $environment
936
     * @param int $version
937
     * @return void
938
     */
939
    public function toggleBreakpoint($environment, $version)
940
    {
941
        $migrations = $this->getMigrations();
942
        $this->getMigrations();
943
        $env = $this->getEnvironment($environment);
944
        $versions = $env->getVersionLog();
945
946
        if (empty($versions) || empty($migrations)) {
947
            return;
948
        }
949
950
        if ($version === null) {
951
            $lastVersion = end($versions);
952
            $version = $lastVersion['version'];
953
        }
954
955 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...
956
            $this->output->writeln(sprintf(
957
                '<comment>warning</comment> %s is not a valid version',
958
                $version
959
            ));
960
961
            return;
962
        }
963
964
        $env->getAdapter()->toggleBreakpoint($migrations[$version]);
965
966
        $versions = $env->getVersionLog();
967
968
        $this->getOutput()->writeln(
969
            ' Breakpoint ' . ($versions[$version]['breakpoint'] ? 'set' : 'cleared') .
970
            ' for <info>' . $version . '</info>' .
971
            ' <comment>' . $migrations[$version]->getName() . '</comment>'
972
        );
973
    }
974
975
    /**
976
     * Remove all breakpoints
977
     *
978
     * @param string $environment
979
     * @return void
980
     */
981
    public function removeBreakpoints($environment)
982
    {
983
        $this->getOutput()->writeln(sprintf(
984
            ' %d breakpoints cleared.',
985
            $this->getEnvironment($environment)->getAdapter()->resetAllBreakpoints()
986
        ));
987
    }
988
}
989