Completed
Pull Request — master (#1087)
by Cas
01:49
created

Manager::setConfig()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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