Completed
Push — master ( 3c49e9...3c9a70 )
by José
02:51 queued 01:08
created

Manager::getEnvironment()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 25
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 3

Importance

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