Completed
Pull Request — master (#1390)
by
unknown
02:31
created

Manager::setEnvironments()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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

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

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

Loading history...
275
            $this->getOutput()->writeln('         <error>BREAKPOINT SET</error>');
276
        }
277
    }
278
279
    /**
280 4
     * Migrate to the version of the database on a given date.
281
     *
282 4
     * @param string    $environment Environment
283 4
     * @param \DateTime $dateTime    Date to migrate to
284
     * @param bool      $fake        flag that if true, we just record running the migration, but not actually do the
285
     *                               migration
286 4
     *
287 4
     * @return void
288
     */
289 4
    public function migrateToDateTime($environment, \DateTime $dateTime, $fake = false)
290 3
    {
291 3
        $versions = array_keys($this->getMigrations($environment));
292 3
        $dateString = $dateTime->format('YmdHis');
293 3
294 4
        $outstandingMigrations = array_filter($versions, function ($version) use ($dateString) {
295
            return $version <= $dateString;
296
        });
297
298
        if (count($outstandingMigrations) > 0) {
299
            $migration = max($outstandingMigrations);
300
            $this->getOutput()->writeln('Migrating to version ' . $migration);
301
            $this->migrate($environment, $migration, $fake);
302
        }
303 8
    }
304
305 8
    /**
306 8
     * Migrate an environment to the specified version.
307 8
     *
308 8
     * @param string $environment Environment
309
     * @param int    $version     version to migrate to
310 8
     * @param bool   $fake        flag that if true, we just record running the migration, but not actually do the migration
311
     * @return void
312
     */
313
    public function migrate($environment, $version = null, $fake = false)
314 8
    {
315 5
        $migrations = $this->getMigrations($environment);
316 5
        $env = $this->getEnvironment($environment);
317 3
        $versions = $env->getVersions();
318
        $current = $env->getCurrentVersion();
319
320
        if (empty($versions) && empty($migrations)) {
321
            return;
322
        }
323
324
        if ($version === null) {
325
            $version = max(array_merge($versions, array_keys($migrations)));
326 View Code Duplication
        } else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
327 8
            if (0 != $version && !isset($migrations[$version])) {
328
                $this->output->writeln(sprintf(
329 8
                    '<comment>warning</comment> %s is not a valid version',
330
                    $version
331
                ));
332
333
                return;
334
            }
335
        }
336
337
        // are we migrating up or down?
338
        $direction = $version > $current ? MigrationInterface::UP : MigrationInterface::DOWN;
339
340
        if ($direction === MigrationInterface::DOWN) {
341
            // run downs first
342
            krsort($migrations);
343 8 View Code Duplication
            foreach ($migrations as $migration) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
344 8
                if ($migration->getVersion() <= $version) {
345 8
                    break;
346 2
                }
347
348
                if (in_array($migration->getVersion(), $versions)) {
349 8
                    $this->executeMigration($environment, $migration, MigrationInterface::DOWN, $fake);
350 5
                }
351 5
            }
352 8
        }
353 8
354
        ksort($migrations);
355 View Code Duplication
        foreach ($migrations as $migration) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
356
            if ($migration->getVersion() > $version) {
357
                break;
358
            }
359
360
            if (!in_array($migration->getVersion(), $versions)) {
361
                $this->executeMigration($environment, $migration, MigrationInterface::UP, $fake);
362
            }
363 119
        }
364
    }
365 119
366 119
    /**
367
     * Execute a migration against the specified environment.
368 119
     *
369 119
     * @param string $name Environment Name
370 119
     * @param \Phinx\Migration\MigrationInterface $migration Migration
371
     * @param string $direction Direction
372
     * @param bool $fake flag that if true, we just record running the migration, but not actually do the migration
373 119
     * @return void
374 119
     */
375 119
    public function executeMigration($name, MigrationInterface $migration, $direction = MigrationInterface::UP, $fake = false)
376
    {
377 119
        $this->getOutput()->writeln('');
378
        $this->getOutput()->writeln(
379 119
            ' ==' .
380 119
            ' <info>' . $migration->getVersion() . ' ' . $migration->getName() . ':</info>' .
381 119
            ' <comment>' . ($direction === MigrationInterface::UP ? 'migrating' : 'reverting') . '</comment>'
382 119
        );
383 119
        $migration->preFlightCheck($direction);
384
385
        // Execute the migration and log the time elapsed.
386
        $start = microtime(true);
387
        $this->getEnvironment($name)->executeMigration($migration, $direction, $fake);
388
        $end = microtime(true);
389
390
        $this->getOutput()->writeln(
391
            ' ==' .
392 6
            ' <info>' . $migration->getVersion() . ' ' . $migration->getName() . ':</info>' .
393
            ' <comment>' . ($direction === MigrationInterface::UP ? 'migrated' : 'reverted') .
394 6
            ' ' . sprintf('%.4fs', $end - $start) . '</comment>'
395 6
        );
396
    }
397 6
398 6
    /**
399 6
     * Execute a seeder against the specified environment.
400
     *
401
     * @param string $name Environment Name
402 6
     * @param \Phinx\Seed\SeedInterface $seed Seed
403 6
     * @return void
404 6
     */
405
    public function executeSeed($name, SeedInterface $seed)
406 6
    {
407
        $this->getOutput()->writeln('');
408 6
        $this->getOutput()->writeln(
409 6
            ' ==' .
410 6
            ' <info>' . $seed->getName() . ':</info>' .
411 6
            ' <comment>seeding</comment>'
412 6
        );
413
414
        // Execute the seeder and log the time elapsed.
415
        $start = microtime(true);
416
        $this->getEnvironment($name)->executeSeed($seed);
417
        $end = microtime(true);
418
419
        $this->getOutput()->writeln(
420
            ' ==' .
421
            ' <info>' . $seed->getName() . ':</info>' .
422
            ' <comment>seeded' .
423 349
            ' ' . sprintf('%.4fs', $end - $start) . '</comment>'
424
        );
425
    }
426 349
427
    /**
428
     * Rollback an environment to the specified version.
429 349
     *
430
     * @param string $environment Environment
431
     * @param int|string $target
432 349
     * @param bool $force
433
     * @param bool $targetMustMatchVersion
434 349
     * @param bool $fake flag that if true, we just record running the migration, but not actually do the migration
435
     * @return void
436
     */
437 349
    public function rollback($environment, $target = null, $force = false, $targetMustMatchVersion = true, $fake = false)
438 48
    {
439 48
        // note that the migrations are indexed by name (aka creation time) in ascending order
440 48
        $migrations = $this->getMigrations($environment);
441
442 349
        // note that the version log are also indexed by name with the proper ascending order according to the version order
443 349
        $executedVersions = $this->getEnvironment($environment)->getVersionLog();
444 349
445
        // get a list of migrations sorted in the opposite way of the executed versions
446
        $sortedMigrations = [];
447 47
448
        foreach ($executedVersions as $versionCreationTime => &$executedVersion) {
449 349
            // if we have a date (ie. the target must not match a version) and we are sorting by execution time, we
450
            // convert the version start time so we can compare directly with the target date
451 349
            if (!$this->getConfig()->isVersionOrderCreationTime() && !$targetMustMatchVersion) {
452 23
                $dateTime = \DateTime::createFromFormat('Y-m-d H:i:s', $executedVersion['start_time']);
453 349
                $executedVersion['start_time'] = $dateTime->format('YmdHis');
454
            }
455 20
456 20
            if (isset($migrations[$versionCreationTime])) {
457 20
                array_unshift($sortedMigrations, $migrations[$versionCreationTime]);
458 20
            } else {
459
                // this means the version is missing so we unset it so that we don't consider it when rolling back
460
                // migrations (or choosing the last up version as target)
461 20
                unset($executedVersions[$versionCreationTime]);
462 20
            }
463 20
        }
464
465
        if ($target === 'all' || $target === '0') {
466
            $target = 0;
467 20
        } elseif (!is_numeric($target) && !is_null($target)) { // try to find a target version based on name
468
            // search through the migrations using the name
469
            $migrationNames = array_map(function ($item) {
470 349
                return $item['migration_name'];
471 349
            }, $executedVersions);
472 53
            $found = array_search($target, $migrationNames);
473 53
474
            // check on was found
475
            if ($found !== false) {
476
                $target = (string)$found;
477 296
            } else {
478
                $this->getOutput()->writeln("<error>No migration found with name ($target)</error>");
479 94
480 94
                return;
481 94
            }
482
        }
483
484 296
        // Check we have at least 1 migration to revert
485 46
        $executedVersionCreationTimes = array_keys($executedVersions);
486 46
        if (empty($executedVersionCreationTimes) || $target == end($executedVersionCreationTimes)) {
487
            $this->getOutput()->writeln('<error>No migrations to rollback</error>');
488
489
            return;
490 250
        }
491
492 250
        // If no target was supplied, revert the last migration
493 250
        if ($target === null) {
494 70
            // Get the migration before the last run migration
495
            $prev = count($executedVersionCreationTimes) - 2;
496
            $target = $prev >= 0 ? $executedVersionCreationTimes[$prev] : 0;
497 250
        }
498 250
499
        // If the target must match a version, check the target version exists
500 250
        if ($targetMustMatchVersion && 0 !== $target && !isset($migrations[$target])) {
501 96
            $this->getOutput()->writeln("<error>Target version ($target) not found</error>");
502 96
503 42
            return;
504
        }
505 68
506
        // Rollback all versions until we find the wanted rollback target
507 222
        $rollbacked = false;
508 121
509 121
        foreach ($sortedMigrations as $migration) {
510
            if ($targetMustMatchVersion && $migration->getVersion() == $target) {
511 117
                break;
512 117
            }
513 117
514 250
            if (in_array($migration->getVersion(), $executedVersionCreationTimes)) {
515
                $executedVersion = $executedVersions[$migration->getVersion()];
516 250
517 133
                if (!$targetMustMatchVersion) {
518 133
                    if (($this->getConfig()->isVersionOrderCreationTime() && $executedVersion['version'] <= $target) ||
519 250
                        (!$this->getConfig()->isVersionOrderCreationTime() && $executedVersion['start_time'] <= $target)) {
520
                        break;
521
                    }
522
                }
523
524
                if (0 != $executedVersion['breakpoint'] && !$force) {
525
                    $this->getOutput()->writeln('<error>Breakpoint reached. Further rollbacks inhibited.</error>');
526
                    break;
527
                }
528 9
                $this->executeMigration($environment, $migration, MigrationInterface::DOWN, $fake);
529
                $rollbacked = true;
530 9
            }
531
        }
532 9
533
        if (!$rollbacked) {
534 3
            $this->getOutput()->writeln('<error>No migrations to rollback</error>');
535 3
        }
536 3
    }
537 3
538 3
    /**
539 3
     * Run database seeders against an environment.
540
     *
541 6
     * @param string $environment Environment
542 3
     * @param string $seed Seeder
543 3
     * @return void
544 3
     */
545
    public function seed($environment, $seed = null)
546
    {
547 6
        $seeds = $this->getSeeds();
548
549
        if ($seed === null) {
550
            // run all seeders
551
            foreach ($seeds as $seeder) {
552
                if (array_key_exists($seeder->getName(), $seeds)) {
553
                    $this->executeSeed($environment, $seeder);
554
                }
555 381
            }
556
        } else {
557 381
            // run only one seeder
558 381
            if (array_key_exists($seed, $seeds)) {
559
                $this->executeSeed($environment, $seeds[$seed]);
560
            } else {
561
                throw new \InvalidArgumentException(sprintf('The seed class "%s" does not exist', $seed));
562
            }
563
        }
564
    }
565
566
    /**
567
     * Sets the environments.
568 382
     *
569
     * @param array $environments Environments
570 382
     * @return \Phinx\Migration\Manager
571 380
     */
572
    public function setEnvironments($environments = [])
573
    {
574
        $this->environments = $environments;
575 7
576 1
        return $this;
577 1
    }
578
579 1
    /**
580
     * Gets the manager class for the given environment.
581
     *
582
     * @param string $name Environment Name
583 6
     * @throws \InvalidArgumentException
584 6
     * @return \Phinx\Migration\Manager\Environment
585
     */
586 6
    public function getEnvironment($name)
587 6
    {
588 6
        if (isset($this->environments[$name])) {
589 6
            return $this->environments[$name];
590
        }
591 6
592
        // check the environment exists
593
        if (!$this->getConfig()->hasEnvironment($name)) {
594
            throw new \InvalidArgumentException(sprintf(
595
                'The environment "%s" does not exist',
596
                $name
597
            ));
598
        }
599
600 400
        // create an environment instance and cache it
601
        $envOptions = $this->getConfig()->getEnvironment($name);
602 400
        $envOptions['version_order'] = $this->getConfig()->getVersionOrder();
603 400
604
        $environment = new Environment($name, $envOptions);
605
        $this->environments[$name] = $environment;
606
        $environment->setInput($this->getInput());
607
        $environment->setOutput($this->getOutput());
608
609
        return $environment;
610
    }
611 393
612
    /**
613 393
     * Sets the console input.
614
     *
615
     * @param \Symfony\Component\Console\Input\InputInterface $input Input
616
     * @return \Phinx\Migration\Manager
617
     */
618
    public function setInput(InputInterface $input)
619
    {
620
        $this->input = $input;
621
622 400
        return $this;
623
    }
624 400
625 400
    /**
626
     * Gets the console input.
627
     *
628
     * @return \Symfony\Component\Console\Input\InputInterface
629
     */
630
    public function getInput()
631
    {
632
        return $this->input;
633 395
    }
634
635 395
    /**
636
     * Sets the console output.
637
     *
638
     * @param \Symfony\Component\Console\Output\OutputInterface $output Output
639
     * @return \Phinx\Migration\Manager
640
     */
641
    public function setOutput(OutputInterface $output)
642
    {
643
        $this->output = $output;
644 379
645
        return $this;
646 379
    }
647 379
648
    /**
649
     * Gets the console output.
650
     *
651
     * @return \Symfony\Component\Console\Output\OutputInterface
652
     */
653
    public function getOutput()
654
    {
655
        return $this->output;
656
    }
657 388
658
    /**
659 388
     * Sets the database migrations.
660 388
     *
661
     * @param array $migrations Migrations
662
     * @return \Phinx\Migration\Manager
663 388
     */
664
    public function setMigrations(array $migrations)
665 388
    {
666
        $this->migrations = $migrations;
667 388
668 387
        return $this;
669 387
    }
670
671 387
    /**
672 3
     * Gets an array of the database migrations, indexed by migration name (aka creation time) and sorted in ascending
673
     * order
674
     *
675 387
     * @param string $environment Environment
676 387
     * @throws \InvalidArgumentException
677
     * @return \Phinx\Migration\AbstractMigration[]
678
     */
679 387
    public function getMigrations($environment)
680
    {
681 387
        if ($this->migrations === null) {
682 2
            $phpFiles = $this->getMigrationFiles();
683 2
684 2 View Code Duplication
            if ($this->getOutput()->getVerbosity() >= OutputInterface::VERBOSITY_DEBUG) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
685 2
                $this->getOutput()->writeln('Migration file');
686 2
                $this->getOutput()->writeln(
687
                    array_map(
0 ignored issues
show
Documentation introduced by
array_map(function ($php...$phpFile; }, $phpFiles) is of type array, but the function expects a string|object<Symfony\Co...onsole\Output\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
688
                        function ($phpFile) {
689 387
                            return '    ' . $phpFile;
690
                        },
691
                        $phpFiles
692
                    )
693 387
                );
694 387
            }
695 2
696 2
            // filter the files to only get the ones that match our naming scheme
697 2
            $fileNames = [];
698
            /** @var \Phinx\Migration\AbstractMigration[] $versions */
699 2
            $versions = [];
700
701
            foreach ($phpFiles as $filePath) {
702
                if (Util::isValidMigrationFileName(basename($filePath))) {
703 385 View Code Duplication
                    if ($this->getOutput()->getVerbosity() >= OutputInterface::VERBOSITY_DEBUG) {
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...
704
                        $this->getOutput()->writeln('Valid migration file');
705 385
                        $this->getOutput()->writeln(
706 2
                            array_map(
0 ignored issues
show
Documentation introduced by
array_map(function ($php...$phpFile; }, $phpFiles) is of type array, but the function expects a string|object<Symfony\Co...onsole\Output\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
707 2
                                function ($phpFile) {
708 2
                                    return '    ' . $phpFile;
709
                                },
710 2
                                $phpFiles
711
                            )
712
                        );
713 383
                    }
714 383
715 384
                    $version = Util::getVersionFromFileName(basename($filePath));
716
717 379
                    if (isset($versions[$version])) {
718 379
                        throw new \InvalidArgumentException(sprintf('Duplicate migration - "%s" has the same version as "%s"', $filePath, $versions[$version]->getVersion()));
719 379
                    }
720
721 379
                    $config = $this->getConfig();
722
                    $namespace = $config instanceof NamespaceAwareInterface ? $config->getMigrationNamespaceByPath(dirname($filePath)) : null;
723
724
                    // convert the filename to a class name
725
                    $class = ($namespace === null ? '' : $namespace . '\\') . Util::mapFileNameToClassName(basename($filePath));
726
727
                    if (isset($fileNames[$class])) {
728
                        throw new \InvalidArgumentException(sprintf(
729 388
                            'Migration "%s" has the same name as "%s"',
730
                            basename($filePath),
731 388
                            $fileNames[$class]
732 388
                        ));
733 388
                    }
734
735 388
                    $fileNames[$class] = basename($filePath);
736 388
737 388
                    if ($this->getOutput()->getVerbosity() >= OutputInterface::VERBOSITY_DEBUG) {
738 388
                        $this->getOutput()->writeln("Loading class $class from $filePath");
739 388
                    }
740 388
741
                    // load the migration file
742 388
                    $orig_display_errors_setting = ini_get('display_errors');
743
                    ini_set('display_errors', 'On');
744
                    /** @noinspection PhpIncludeInspection */
745
                    require_once $filePath;
746
                    ini_set('display_errors', $orig_display_errors_setting);
747
                    if (!class_exists($class)) {
748
                        throw new \InvalidArgumentException(sprintf(
749
                            'Could not find class "%s" in file "%s"',
750
                            $class,
751 11
                            $filePath
752
                        ));
753 11
                    }
754 11
755
                    if ($this->getOutput()->getVerbosity() >= OutputInterface::VERBOSITY_DEBUG) {
756
                        $this->getOutput()->writeln("Running $class");
757
                    }
758
759
                    // instantiate it
760
                    $migration = new $class($environment, $version, $this->getInput(), $this->getOutput());
761
762
                    if (!($migration instanceof AbstractMigration)) {
763 11
                        throw new \InvalidArgumentException(sprintf(
764
                            'The class "%s" in file "%s" must extend \Phinx\Migration\AbstractMigration',
765 11
                            $class,
766 11
                            $filePath
767
                        ));
768
                    }
769 11
770
                    $versions[$version] = $migration;
771 11 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...
772
                    if ($this->getOutput()->getVerbosity() >= OutputInterface::VERBOSITY_DEBUG) {
773 11
                        $this->getOutput()->writeln('Invalid migration file');
774 11
                        $this->getOutput()->writeln(
775 11
                            array_map(
0 ignored issues
show
Documentation introduced by
array_map(function ($php...$phpFile; }, $phpFiles) is of type array, but the function expects a string|object<Symfony\Co...onsole\Output\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
776 11
                                function ($phpFile) {
777
                                    return '  ' . $phpFile;
778
                                },
779 11
                                $phpFiles
780 11
                            )
781
                        );
782
                    }
783
                }
784 11
            }
785 11
786
            ksort($versions);
787
            $this->setMigrations($versions);
788
        }
789
790
        return $this->migrations;
791
    }
792
793
    /**
794 11
     * Returns a list of migration files found in the provided migration paths.
795
     *
796 11
     * @return string[]
797
     */
798 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...
799
    {
800
        $config = $this->getConfig();
801
        $paths = $config->getMigrationPaths();
802
        $files = [];
803
804 11
        foreach ($paths as $path) {
805 11
            $files = array_merge(
806 11
                $files,
807
                Util::glob($path . DIRECTORY_SEPARATOR . '*.php')
808 11
            );
809 11
        }
810 11
        // glob() can return the same file multiple times
811
        // This will cause the migration to fail with a
812 11
        // false assumption of duplicate migrations
813
        // http://php.net/manual/en/function.glob.php#110340
814
        $files = array_unique($files);
815
816
        return $files;
817
    }
818
819
    /**
820 11
     * Sets the database seeders.
821
     *
822 11
     * @param array $seeds Seeders
823 11
     * @return \Phinx\Migration\Manager
824 11
     */
825
    public function setSeeds(array $seeds)
826 11
    {
827 11
        $this->seeds = $seeds;
828 11
829 11
        return $this;
830 11
    }
831 11
832
    /**
833 11
     * Get seed dependencies instances from seed dependency array
834
     *
835
     * @param AbstractSeed $seed Seed
836
     *
837
     * @return AbstractSeed[]
838
     */
839
    private function getSeedDependenciesInstances(AbstractSeed $seed)
840
    {
841
        $dependenciesInstances = [];
842 400
        $dependencies = $seed->getDependencies();
843
        if (!empty($dependencies)) {
844 400
            foreach ($dependencies as $dependency) {
845 400
                foreach ($this->seeds as $seed) {
846
                    if (get_class($seed) === $dependency) {
847
                        $dependenciesInstances[get_class($seed)] = $seed;
848
                    }
849
                }
850
            }
851
        }
852
853 399
        return $dependenciesInstances;
854
    }
855 399
856
    /**
857
     * Order seeds by dependencies
858
     *
859
     * @param AbstractSeed[] $seeds Seeds
860
     *
861
     * @return AbstractSeed[]
862
     */
863
    private function orderSeedsByDependencies(array $seeds)
864
    {
865 2
        $orderedSeeds = [];
866
        foreach ($seeds as $seed) {
867 2
            $key = get_class($seed);
868 2
            $dependencies = $this->getSeedDependenciesInstances($seed);
869 2
            if (!empty($dependencies)) {
870 2
                $orderedSeeds[$key] = $seed;
871
                $orderedSeeds = array_merge($this->orderSeedsByDependencies($dependencies), $orderedSeeds);
872 2
            } else {
873
                $orderedSeeds[$key] = $seed;
874
            }
875
        }
876 2
877 1
        return $orderedSeeds;
878 1
    }
879 1
880
    /**
881 2
     * Gets an array of database seeders.
882 1
     *
883 1
     * @throws \InvalidArgumentException
884
     * @return \Phinx\Seed\AbstractSeed[]
885 1
     */
886 1
    public function getSeeds()
887
    {
888
        if ($this->seeds === null) {
889 1
            $phpFiles = $this->getSeedFiles();
890
891 1
            // filter the files to only get the ones that match our naming scheme
892
            $fileNames = [];
893 1
            /** @var \Phinx\Seed\AbstractSeed[] $seeds */
894 1
            $seeds = [];
895 1
896 1
            foreach ($phpFiles as $filePath) {
897 1
                if (Util::isValidSeedFileName(basename($filePath))) {
898 1
                    $config = $this->getConfig();
899
                    $namespace = $config instanceof NamespaceAwareInterface ? $config->getSeedNamespaceByPath(dirname($filePath)) : null;
900
901
                    // convert the filename to a class name
902
                    $class = ($namespace === null ? '' : $namespace . '\\') . pathinfo($filePath, PATHINFO_FILENAME);
903
                    $fileNames[$class] = basename($filePath);
904
905
                    // load the seed file
906 1
                    /** @noinspection PhpIncludeInspection */
907
                    require_once $filePath;
908 1
                    if (!class_exists($class)) {
909 1
                        throw new \InvalidArgumentException(sprintf(
910 1
                            'Could not find class "%s" in file "%s"',
911 1
                            $class,
912 1
                            $filePath
913
                        ));
914
                    }
915
916
                    // instantiate it
917
                    $seed = new $class($this->getInput(), $this->getOutput());
918
919
                    if (!($seed instanceof AbstractSeed)) {
920
                        throw new \InvalidArgumentException(sprintf(
921
                            'The class "%s" in file "%s" must extend \Phinx\Seed\AbstractSeed',
922
                            $class,
923
                            $filePath
924
                        ));
925
                    }
926
927
                    $seeds[$class] = $seed;
928
                }
929
            }
930
931
            ksort($seeds);
932
            $this->setSeeds($seeds);
933
        }
934
935
        $this->seeds = $this->orderSeedsByDependencies($this->seeds);
936
937
        return $this->seeds;
938
    }
939
940
    /**
941
     * Returns a list of seed files found in the provided seed paths.
942
     *
943
     * @return string[]
944
     */
945 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...
946
    {
947
        $config = $this->getConfig();
948
        $paths = $config->getSeedPaths();
949
        $files = [];
950
951
        foreach ($paths as $path) {
952
            $files = array_merge(
953
                $files,
954
                Util::glob($path . DIRECTORY_SEPARATOR . '*.php')
955
            );
956
        }
957
        // glob() can return the same file multiple times
958
        // This will cause the migration to fail with a
959
        // false assumption of duplicate migrations
960
        // http://php.net/manual/en/function.glob.php#110340
961
        $files = array_unique($files);
962
963
        return $files;
964
    }
965
966
    /**
967
     * Sets the config.
968
     *
969
     * @param  \Phinx\Config\ConfigInterface $config Configuration Object
970
     * @return \Phinx\Migration\Manager
971
     */
972
    public function setConfig(ConfigInterface $config)
973
    {
974
        $this->config = $config;
975
976
        return $this;
977
    }
978
979
    /**
980
     * Gets the config.
981
     *
982
     * @return \Phinx\Config\ConfigInterface
983
     */
984
    public function getConfig()
985
    {
986
        return $this->config;
987
    }
988
989
    /**
990
     * Toggles the breakpoint for a specific version.
991
     *
992
     * @param string $environment
993
     * @param int|null $version
994
     * @return void
995
     */
996
    public function toggleBreakpoint($environment, $version)
997
    {
998
        $migrations = $this->getMigrations($environment);
999
        $this->getMigrations($environment);
1000
        $env = $this->getEnvironment($environment);
1001
        $versions = $env->getVersionLog();
1002
1003
        if (empty($versions) || empty($migrations)) {
1004
            return;
1005
        }
1006
1007
        if ($version === null) {
1008
            $lastVersion = end($versions);
1009
            $version = $lastVersion['version'];
1010
        }
1011
1012 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...
1013
            $this->output->writeln(sprintf(
1014
                '<comment>warning</comment> %s is not a valid version',
1015
                $version
1016
            ));
1017
1018
            return;
1019
        }
1020
1021
        $env->getAdapter()->toggleBreakpoint($migrations[$version]);
1022
1023
        $versions = $env->getVersionLog();
1024
1025
        $this->getOutput()->writeln(
1026
            ' Breakpoint ' . ($versions[$version]['breakpoint'] ? 'set' : 'cleared') .
1027
            ' for <info>' . $version . '</info>' .
1028
            ' <comment>' . $migrations[$version]->getName() . '</comment>'
1029
        );
1030
    }
1031
1032
    /**
1033
     * Remove all breakpoints
1034
     *
1035
     * @param string $environment
1036
     * @return void
1037
     */
1038
    public function removeBreakpoints($environment)
1039
    {
1040
        $this->getOutput()->writeln(sprintf(
1041
            ' %d breakpoints cleared.',
1042
            $this->getEnvironment($environment)->getAdapter()->resetAllBreakpoints()
1043
        ));
1044
    }
1045
}
1046