Completed
Pull Request — master (#1241)
by
unknown
04:16
created

Manager::getMultiEnvironment()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 28
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 28
ccs 16
cts 16
cp 1
rs 8.8571
c 0
b 0
f 0
cc 3
eloc 15
nc 3
nop 1
crap 3
1
<?php
2
/**
3
 * Phinx
4
 *
5
 * (The MIT license)
6
 * Copyright (c) 2015 Rob Morgan
7
 *
8
 * Permission is hereby granted, free of charge, to any person obtaining a copy
9
 * of this software and associated * documentation files (the "Software"), to
10
 * deal in the Software without restriction, including without limitation the
11
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
12
 * sell copies of the Software, and to permit persons to whom the Software is
13
 * furnished to do so, subject to the following conditions:
14
 *
15
 * The above copyright notice and this permission notice shall be included in
16
 * all copies or substantial portions of the Software.
17
 *
18
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
24
 * IN THE SOFTWARE.
25
 *
26
 * @package    Phinx
27
 * @subpackage Phinx\Migration
28
 */
29
namespace Phinx\Migration;
30
31
use Phinx\Config\ConfigInterface;
32
use Phinx\Config\NamespaceAwareInterface;
33
use Phinx\Migration\Manager\Environment;
34
use Phinx\Seed\AbstractSeed;
35
use Phinx\Seed\SeedInterface;
36
use Phinx\Util\Util;
37
use Symfony\Component\Console\Input\InputInterface;
38
use Symfony\Component\Console\Output\OutputInterface;
39
40
class Manager
41
{
42
    /**
43
     * @var \Phinx\Config\ConfigInterface
44
     */
45
    protected $config;
46
47
    /**
48
     * @var \Symfony\Component\Console\Input\InputInterface
49
     */
50
    protected $input;
51
52
    /**
53
     * @var \Symfony\Component\Console\Output\OutputInterface
54
     */
55
    protected $output;
56
57
    /**
58
     * @var array
59
     */
60
    protected $environments;
61
62
    /**
63
     * @var string
64
     */
65
    protected $environmentName;
66
67
    /**
68
     * @var string
69
     */
70
    protected $adapter;
71
72
    /**
73
     * @var array
74
     */
75
    protected $migrations;
76
77
    /**
78
     * @var array
79
     */
80
    protected $seeds;
81
82
    /**
83
     * @var integer
84
     */
85
    const EXIT_STATUS_DOWN = 1;
86
87
    /**
88
     * @var integer
89 432
     */
90
    const EXIT_STATUS_MISSING = 2;
91 432
92 432
    /**
93 432
     * Class Constructor.
94 432
     *
95
     * @param \Phinx\Config\ConfigInterface $config Configuration Object
96
     * @param \Symfony\Component\Console\Input\InputInterface $input Console Input
97
     * @param \Symfony\Component\Console\Output\OutputInterface $output Console Output
98
     */
99
    public function __construct(ConfigInterface $config, InputInterface $input, OutputInterface $output)
100
    {
101
        $this->setConfig($config);
102
        $this->setInput($input);
103 22
        $this->setOutput($output);
104
    }
105 22
106 22
    /**
107 22
     * Prints the specified environment's migration status.
108 22
     *
109 22
     * @param string $environment
110 22
     * @param null $format
111
     * @return int 0 if all migrations are up, or an error code
112
     */
113 21
    public function printStatus($environment, $format = null)
114
    {
115 21
        $this->setEnvironmentName($environment);
116 21
        $output = $this->getOutput();
117 19
        $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...
118 19
        $hasDownMigration = false;
119 2
        $hasMissingMigration = false;
120 1
        $migrations = $this->getMigrations();
121 1
        if (count($migrations)) {
122 1
            // TODO - rewrite using Symfony Table Helper as we already have this library
123 1
            // included and it will fix formatting issues (e.g drawing the lines)
124 21
            $output->writeln('');
125
126 20
            switch ($this->getConfig()->getVersionOrder()) {
127 20
                case \Phinx\Config\Config::VERSION_ORDER_CREATION_TIME:
128
                    $migrationIdAndStartedHeader = "<info>[Migration ID]</info>  Started            ";
129 20
                    break;
130 20
                case \Phinx\Config\Config::VERSION_ORDER_EXECUTION_TIME:
131
                    $migrationIdAndStartedHeader = "Migration ID    <info>[Started          ]</info>";
132
                    break;
133 17
                default:
134 20
                    throw new \RuntimeException('Invalid version_order configuration option');
135
            }
136 20
137
            $output->writeln(" Status  $migrationIdAndStartedHeader  Finished             Migration Name ");
138 20
            $output->writeln('----------------------------------------------------------------------------------');
139
140
            $env = $this->getEnvironment($environment);
141 20
            $versions = $env->getVersionLog();
142
143 20
            $maxNameLength = $versions ? max(array_map(function ($version) {
144 17
                return strlen($version['migration_name']);
145 13
            }, $versions)) : 0;
146 13
147 13
            $missingVersions = array_diff_key($versions, $migrations);
148 20
149
            $hasMissingMigration = !empty($missingVersions);
150 20
151
            // get the migrations sorted in the same way as the versions
152
            $sortedMigrations = [];
153 4
154 4
            foreach ($versions as $versionCreationTime => $version) {
155
                if (isset($migrations[$versionCreationTime])) {
156 4
                    array_push($sortedMigrations, $migrations[$versionCreationTime]);
157 4
                    unset($migrations[$versionCreationTime]);
158 4
                }
159
            }
160
161
            if (empty($sortedMigrations) && !empty($missingVersions)) {
162 20
                // this means we have no up migrations, so we write all the missing versions already so they show up
163 13
                // before any possible down migration
164 13
                foreach ($missingVersions as $missingVersionCreationTime => $missingVersion) {
165
                    $this->printMissingVersion($missingVersion, $maxNameLength);
166 20
167 20
                    unset($missingVersions[$missingVersionCreationTime]);
168 20
                }
169
            }
170 13
171 6
            // any migration left in the migrations (ie. not unset when sorting the migrations by the version order) is
172 6
            // a migration that is down, so we add them to the end of the sorted migrations list
173 4
            if (!empty($migrations)) {
174
                $sortedMigrations = array_merge($sortedMigrations, $migrations);
175 3
            }
176
177
            foreach ($sortedMigrations as $migration) {
178
                $version = array_key_exists($migration->getVersion(), $versions) ? $versions[$migration->getVersion()] : false;
179
                if ($version) {
180
                    // check if there are missing versions before this version
181
                    foreach ($missingVersions as $missingVersionCreationTime => $missingVersion) {
182
                        if ($this->getConfig()->isVersionOrderCreationTime()) {
183
                            if ($missingVersion['version'] > $version['version']) {
184 3
                                break;
185
                            }
186 3
                        } else {
187 13
                            if ($missingVersion['start_time'] > $version['start_time']) {
188
                                break;
189 13
                            } elseif ($missingVersion['start_time'] == $version['start_time'] &&
190 13
                                $missingVersion['version'] > $version['version']) {
191 13
                                break;
192 13
                            }
193
                        }
194 20
195
                        $this->printMissingVersion($missingVersion, $maxNameLength);
196 20
197 20
                        unset($missingVersions[$missingVersionCreationTime]);
198 20
                    }
199 20
200 20
                    $status = '     <info>up</info> ';
201 20
                } else {
202 20
                    $hasDownMigration = true;
203 20
                    $status = '   <error>down</error> ';
204
                }
205 20
                $maxNameLength = max($maxNameLength, strlen($migration->getName()));
206 1
207 1
                $output->writeln(sprintf(
208
                    '%s %14.0f  %19s  %19s  <comment>%s</comment>',
209 20
                    $status,
210 20
                    $migration->getVersion(),
211 20
                    $version['start_time'],
212
                    $version['end_time'],
213
                    $migration->getName()
214 20
                ));
215 4
216
                if ($version && $version['breakpoint']) {
217 4
                    $output->writeln('         <error>BREAKPOINT SET</error>');
218 20
                }
219 20
220
                $migrations[] = ['migration_status' => trim(strip_tags($status)), 'migration_id' => sprintf('%14.0f', $migration->getVersion()), 'migration_name' => $migration->getName()];
221 1
                unset($versions[$migration->getVersion()]);
222 1
            }
223
224
            // and finally add any possibly-remaining missing migrations
225
            foreach ($missingVersions as $missingVersionCreationTime => $missingVersion) {
226 21
                $this->printMissingVersion($missingVersion, $maxNameLength);
227 21
228
                unset($missingVersions[$missingVersionCreationTime]);
229
            }
230
        } else {
231
            // there are no migrations
232
            $output->writeln('');
233
            $output->writeln('There are no available migrations. Try creating one using the <info>create</info> command.');
234
        }
235
236
        // write an empty line
237
        $output->writeln('');
238
        if ($format !== null) {
239
            switch ($format) {
240
                case 'json':
241
                    $output->writeln(json_encode(
242 21
                        [
243 10
                            'pending_count' => count($this->getMigrations()),
244 11
                            'migrations' => $migrations
245 6
                        ]
246
                    ));
247 5
                    break;
248
                default:
249
                    $output->writeln('<info>Unsupported format: ' . $format . '</info>');
250
            }
251
        }
252
253
        if ($hasMissingMigration) {
254
            return self::EXIT_STATUS_MISSING;
255
        } elseif ($hasDownMigration) {
256
            return self::EXIT_STATUS_DOWN;
257 10
        } else {
258
            return 0;
259 10
        }
260 10
    }
261 10
262 10
    /**
263 10
     * Print Missing Version
264 10
     *
265 10
     * @param array     $version        The missing version to print (in the format returned by Environment.getVersionLog).
266
     * @param int   $maxNameLength  The maximum migration name length.
267 10
     */
268 1
    private function printMissingVersion($version, $maxNameLength)
269 1
    {
270 10
        $this->getOutput()->writeln(sprintf(
271
            '     <error>up</error>  %14.0f  %19s  %19s  <comment>%s</comment>  <error>** MISSING **</error>',
272
            $version['version'],
273
            $version['start_time'],
274
            $version['end_time'],
275
            str_pad($version['migration_name'], $maxNameLength, ' ')
276
        ));
277
278
        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...
279
            $this->getOutput()->writeln('         <error>BREAKPOINT SET</error>');
280 4
        }
281
    }
282 4
283 4
    /**
284
     * Migrate to the version of the database on a given date.
285
     *
286 4
     * @param string    $environment Environment
287 4
     * @param \DateTime $dateTime    Date to migrate to
288
     *
289 4
     * @return void
290 3
     */
291 3
    public function migrateToDateTime($environment, \DateTime $dateTime)
292 3
    {
293 3
        $versions = array_keys($this->getMigrations());
294 4
        $dateString = $dateTime->format('YmdHis');
295
296
        $outstandingMigrations = array_filter($versions, function ($version) use ($dateString) {
297
            return $version <= $dateString;
298
        });
299
300
        if (count($outstandingMigrations) > 0) {
301
            $migration = max($outstandingMigrations);
302
            $this->getOutput()->writeln('Migrating to version ' . $migration);
303 8
            $this->migrate($environment, $migration);
304
        }
305 8
    }
306 8
307 8
    /**
308 8
     * Migrate an environment to the specified version.
309
     *
310 8
     * @param string $environment Environment
311
     * @param int $version
312
     * @return void
313
     */
314 8
    public function migrate($environment, $version = null)
315 5
    {
316 5
        $this->setEnvironmentName($environment);
317 3
        $migrations = $this->getMigrations();
318
        $env = $this->getEnvironment($environment);
319
        $versions = $env->getVersions();
320
        $current = $env->getCurrentVersion();
321
322
        if (empty($versions) && empty($migrations)) {
323
            return;
324
        }
325
326
        if ($version === null) {
327 8
            $version = max(array_merge($versions, array_keys($migrations)));
328 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...
329 8
            if (0 != $version && !isset($migrations[$version])) {
330
                $this->output->writeln(sprintf(
331
                    '<comment>warning</comment> %s is not a valid version',
332
                    $version
333
                ));
334
335
                return;
336
            }
337
        }
338
339
        // are we migrating up or down?
340
        $direction = $version > $current ? MigrationInterface::UP : MigrationInterface::DOWN;
341
342
        if ($direction === MigrationInterface::DOWN) {
343 8
            // run downs first
344 8
            krsort($migrations);
345 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...
346 2
                if ($migration->getVersion() <= $version) {
347
                    break;
348
                }
349 8
350 5
                if (in_array($migration->getVersion(), $versions)) {
351 5
                    $this->executeMigration($environment, $migration, MigrationInterface::DOWN);
352 8
                }
353 8
            }
354
        }
355
356
        ksort($migrations);
357 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...
358
            if ($migration->getVersion() > $version) {
359
                break;
360
            }
361
362
            if (!in_array($migration->getVersion(), $versions)) {
363 119
                $this->executeMigration($environment, $migration, MigrationInterface::UP);
364
            }
365 119
        }
366 119
    }
367
368 119
    /**
369 119
     * Execute a migration against the specified environment.
370 119
     *
371
     * @param string $name Environment Name
372
     * @param \Phinx\Migration\MigrationInterface $migration Migration
373 119
     * @param string $direction Direction
374 119
     * @return void
375 119
     */
376
    public function executeMigration($name, MigrationInterface $migration, $direction = MigrationInterface::UP)
377 119
    {
378
        $this->getOutput()->writeln('');
379 119
        $this->getOutput()->writeln(
380 119
            ' ==' .
381 119
            ' <info>' . $migration->getVersion() . ' ' . $migration->getName() . ':</info>' .
382 119
            ' <comment>' . ($direction === MigrationInterface::UP ? 'migrating' : 'reverting') . '</comment>'
383 119
        );
384
385
        // Execute the migration and log the time elapsed.
386
        $start = microtime(true);
387
        $this->getEnvironment($name)->executeMigration($migration, $direction);
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
418
        $end = microtime(true);
419
420
        $this->getOutput()->writeln(
421
            ' ==' .
422
            ' <info>' . $seed->getName() . ':</info>' .
423 349
            ' <comment>seeded' .
424
            ' ' . sprintf('%.4fs', $end - $start) . '</comment>'
425
        );
426 349
    }
427
428
    /**
429 349
     * Rollback an environment to the specified version.
430
     *
431
     * @param string $environment Environment
432 349
     * @param int|string $target
433
     * @param bool $force
434 349
     * @param bool $targetMustMatchVersion
435
     * @return void
436
     */
437 349
    public function rollback($environment, $target = null, $force = false, $targetMustMatchVersion = true)
438 48
    {
439 48
        // note that the migrations are indexed by name (aka creation time) in ascending order
440 48
        $migrations = $this->getMigrations();
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);
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
        $this->setEnvironmentName($environment);
548
        $seeds = $this->getSeeds();
549
550
        if ($seed === null) {
551
            // run all seeders
552
            foreach ($seeds as $seeder) {
553
                if (array_key_exists($seeder->getName(), $seeds)) {
554
                    $this->executeSeed($environment, $seeder);
555 381
                    unset($this->seeds[$seeder->getName()]);
556
                }
557 381
            }
558 381
        } else {
559
            // run only one seeder
560
            if (array_key_exists($seed, $seeds)) {
561
                $this->executeSeed($environment, $seeds[$seed]);
562
                unset($this->seeds[$seed]);
563
            } else {
564
                throw new \InvalidArgumentException(sprintf('The seed class "%s" does not exist', $seed));
565
            }
566
        }
567
    }
568 382
569
    /**
570 382
     * Set the dbRef property
571 380
     *
572
     * @param string $ref
573
     * @return \Phinx\Migration\Manager
574
     */
575 7
    public function setDbRef($ref)
576 1
    {
577 1
        $this->dbRef = $ref;
0 ignored issues
show
Bug introduced by
The property dbRef does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
578
        return $this;
579 1
    }
580
581
    /**
582
     * Get the adapter property
583 6
     *
584 6
     * @return string
585
     */ 
586 6
    public function getDbRef()
587 6
    {
588 6
        return $this->dbRef;
589 6
    }
590
591 6
    /**
592
     * Sets the environments.
593
     *
594
     * @param array $environments Environments
595
     * @return \Phinx\Migration\Manager
596
     */
597
    public function setEnvironments($environments = [])
598
    {
599
        $this->environments = $environments;
600 400
601
        return $this;
602 400
    }
603 400
604
    /**
605
     * Gets the manager class for the given environment.
606
     *
607
     * @param string $name Environment Name
608
     * @throws \InvalidArgumentException
609
     * @return \Phinx\Migration\Manager\Environment
610
     */
611 393
    public function getEnvironment($name)
612
    {
613 393
        if ($this->getDbRef() !== null) {
614
            return $this->getMultiEnvironment($name);
615
        }
616
617
        return $this->getSingleEnvironment($name);
618
    }
619
620
    public function setEnvironmentName($name)
621
    {
622 400
        $this->environmentName = $name;
623
        return $this;
624 400
    }
625 400
626
    public function getEnvironmentName()
627
    {
628
        return $this->environmentName;
629
    }
630
631
    /**
632
     * Gets the manager class for the given environment when given as a single instance.
633 395
     *
634
     * @param string $name Environment Name
635 395
     * @throws \InvalidArgumentException
636
     * @return \Phinx\Migration\Manager\Environment
637
     */
638
    public function getSingleEnvironment($name)
639
    {
640
641
        if (isset($this->environments[$name])) {
642
            return $this->environments[$name];
643
        }
644 379
645
        // check the environment exists
646 379
        if (!$this->getConfig()->hasEnvironment($name)) {
647 379
            throw new \InvalidArgumentException(sprintf(
648
                'The environment "%s" does not exist',
649
                $name
650
            ));
651
        }
652
653
        // create an environment instance and cache it
654
        $envOptions = $this->getConfig()->getEnvironment($name);
655
656
        $envOptions['version_order'] = $this->getConfig()->getVersionOrder();
657 388
        $environment = new Environment($name, $envOptions);
658
        $this->environments[$name] = $environment;
659 388
660 388
        $environment->setInput($this->getInput());
661
        $environment->setOutput($this->getOutput());
662
663 388
        return $environment;
664
665 388
    }
666
667 388
    /**
668 387
     * Gets the manager class for the given environment when used as part of a mulitple deployment.
669 387
     *
670
     * @param string $name Environment Name
671 387
     * @throws \InvalidArgumentException
672 3
     * @return \Phinx\Migration\Manager\Environment
673
     */
674
    public function getMultiEnvironment($name)
675 387
    {
676 387
        if (isset($this->environments[$name][$this->getDbRef()])) {
677
            return $this->environments[$name][$this->getDbRef()];
678
        }
679 387
680
        // check the environment exists
681 387
        if (!$this->getConfig()->hasEnvironment($name)) {
682 2
            throw new \InvalidArgumentException(sprintf(
683 2
                'The environment "%s" does not exist',
684 2
                $name
685 2
            ));
686 2
        }
687
688
        // create an environment instance and cache it
689 387
        $envOptions = $this->getConfig()->getEnvironment($name);
690
        $envOptions = $envOptions[$this->getDbRef()];
691
692
        $envOptions['version_order'] = $this->getConfig()->getVersionOrder();
693 387
        $environment = new Environment($name, $envOptions);
694 387
        $this->environments[$name][$this->getDbRef()] = $environment;
695 2
696 2
        $environment->setInput($this->getInput());
697 2
        $environment->setOutput($this->getOutput());
698
699 2
        return $environment;
700
701
    }
702
703 385
    /**
704
     * Sets the console input.
705 385
     *
706 2
     * @param \Symfony\Component\Console\Input\InputInterface $input Input
707 2
     * @return \Phinx\Migration\Manager
708 2
     */
709
    public function setInput(InputInterface $input)
710 2
    {
711
        $this->input = $input;
712
713 383
        return $this;
714 383
    }
715 384
716
    /**
717 379
     * Gets the console input.
718 379
     *
719 379
     * @return \Symfony\Component\Console\Input\InputInterface
720
     */
721 379
    public function getInput()
722
    {
723
        return $this->input;
724
    }
725
726
    /**
727
     * Sets the console output.
728
     *
729 388
     * @param \Symfony\Component\Console\Output\OutputInterface $output Output
730
     * @return \Phinx\Migration\Manager
731 388
     */
732 388
    public function setOutput(OutputInterface $output)
733 388
    {
734
        $this->output = $output;
735 388
736 388
        return $this;
737 388
    }
738 388
739 388
    /**
740 388
     * Gets the console output.
741
     *
742 388
     * @return \Symfony\Component\Console\Output\OutputInterface
743
     */
744
    public function getOutput()
745
    {
746
        return $this->output;
747
    }
748
749
    /**
750
     * Sets the database migrations.
751 11
     *
752
     * @param array $migrations Migrations
753 11
     * @return \Phinx\Migration\Manager
754 11
     */
755
    public function setMigrations(array $migrations = null)
756
    {
757
        $this->migrations = $migrations;
0 ignored issues
show
Documentation Bug introduced by
It seems like $migrations can be null. However, the property $migrations is declared as array. Maybe change the type of the property to array|null or add a type check?

Our type inference engine has found an assignment of a scalar value (like a string, an integer or null) to a property which is an array.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property.

To type hint that a parameter can be either an array or null, you can set a type hint of array and a default value of null. The PHP interpreter will then accept both an array or null for that parameter.

function aContainsB(array $needle = null, array  $haystack) {
    if (!$needle) {
        return false;
    }

    return array_intersect($haystack, $needle) == $haystack;
}

The function can be called with either null or an array for the parameter $needle but will only accept an array as $haystack.

Loading history...
758
759
        return $this;
760
    }
761
762
    /**
763 11
     * Gets an array of the database migrations, indexed by migration name (aka creation time) and sorted in ascending
764
     * order
765 11
     *
766 11
     * @throws \InvalidArgumentException
767
     * @return \Phinx\Migration\AbstractMigration[]
768
     */
769 11
    public function getMigrations()
770
    {
771 11
        if ($this->migrations === null) {
772
            $phpFiles = $this->getMigrationFiles();
773 11
            // filter the files to only get the ones that match our naming scheme
774 11
            $fileNames = [];
775 11
            /** @var \Phinx\Migration\AbstractMigration[] $versions */
776 11
            $versions = [];
777
778
            foreach ($phpFiles as $filePath) {
779 11
                if (Util::isValidMigrationFileName(basename($filePath))) {
780 11
                    $version = Util::getVersionFromFileName(basename($filePath));
781
782
                    if (isset($versions[$version])) {
783
                        throw new \InvalidArgumentException(sprintf('Duplicate migration - "%s" has the same version as "%s"', $filePath, $versions[$version]->getVersion()));
784 11
                    }
785 11
786
                    $config = $this->getConfig();
787
                    $namespace = $config instanceof NamespaceAwareInterface ? $config->getMigrationNamespaceByPath(dirname($filePath)) : null;
788
789
                    // convert the filename to a class name
790
                    $class = ($namespace === null ? '' : $namespace . '\\') . Util::mapFileNameToClassName(basename($filePath));
791
792
                    if (isset($fileNames[$class])) {
793
                        throw new \InvalidArgumentException(sprintf(
794 11
                            'Migration "%s" has the same name as "%s"',
795
                            basename($filePath),
796 11
                            $fileNames[$class]
797
                        ));
798
                    }
799
800
                    $fileNames[$class] = basename($filePath);
801
802
                    // load the migration file
803
                    /** @noinspection PhpIncludeInspection */
804 11
                    require_once $filePath;
805 11
                    if (!class_exists($class)) {
806 11
                        throw new \InvalidArgumentException(sprintf(
807
                            'Could not find class "%s" in file "%s"',
808 11
                            $class,
809 11
                            $filePath
810 11
                        ));
811
                    }
812 11
813
                    // instantiate it
814
                    $migration = new $class($version, $this->getInput(), $this->getOutput());
815
816
                    if (!($migration instanceof AbstractMigration)) {
817
                        throw new \InvalidArgumentException(sprintf(
818
                            'The class "%s" in file "%s" must extend \Phinx\Migration\AbstractMigration',
819
                            $class,
820 11
                            $filePath
821
                        ));
822 11
                    }
823 11
824 11
                    $versions[$version] = $migration;
825
                }
826 11
            }
827 11
828 11
            ksort($versions);
829 11
            $this->setMigrations($versions);
830 11
        }
831 11
832
        return $this->migrations;
833 11
    }
834
835
    /**
836
     * Returns a list of migration files found in the provided migration paths.
837
     *
838
     * @return string[]
839
     */
840 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...
841
    {
842 400
        $config = $this->getConfig();
843
        $paths = $config->getMigrationPaths($this->getEnvironmentName(), $this->getDbRef());
844 400
        $files = [];
845 400
846
        foreach ($paths as $path) {
847
            $files = array_merge(
848
                $files,
849
                Util::glob($path . DIRECTORY_SEPARATOR . '*.php')
850
            );
851
        }
852
        // glob() can return the same file multiple times
853 399
        // This will cause the migration to fail with a
854
        // false assumption of duplicate migrations
855 399
        // http://php.net/manual/en/function.glob.php#110340
856
        $files = array_unique($files);
857
858
        return $files;
859
    }
860
861
    /**
862
     * Sets the database seeders.
863
     *
864
     * @param array $seeds Seeders
865 2
     * @return \Phinx\Migration\Manager
866
     */
867 2
    public function setSeeds(array $seeds = null)
868 2
    {
869 2
        $this->seeds = $seeds;
0 ignored issues
show
Documentation Bug introduced by
It seems like $seeds can be null. However, the property $seeds is declared as array. Maybe change the type of the property to array|null or add a type check?

Our type inference engine has found an assignment of a scalar value (like a string, an integer or null) to a property which is an array.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property.

To type hint that a parameter can be either an array or null, you can set a type hint of array and a default value of null. The PHP interpreter will then accept both an array or null for that parameter.

function aContainsB(array $needle = null, array  $haystack) {
    if (!$needle) {
        return false;
    }

    return array_intersect($haystack, $needle) == $haystack;
}

The function can be called with either null or an array for the parameter $needle but will only accept an array as $haystack.

Loading history...
870 2
871
        return $this;
872 2
    }
873
874
    /**
875
     * Gets an array of database seeders.
876 2
     *
877 1
     * @throws \InvalidArgumentException
878 1
     * @return \Phinx\Seed\AbstractSeed[]
879 1
     */
880
    public function getSeeds()
881 2
    {
882 1
        if ($this->seeds === null) {
883 1
            $phpFiles = $this->getSeedFiles();
884
885 1
            // filter the files to only get the ones that match our naming scheme
886 1
            $fileNames = [];
887
            /** @var \Phinx\Seed\AbstractSeed[] $seeds */
888
            $seeds = [];
889 1
890
            foreach ($phpFiles as $filePath) {
891 1
                if (Util::isValidSeedFileName(basename($filePath))) {
892
                    $config = $this->getConfig();
893 1
                    $namespace = $config instanceof NamespaceAwareInterface ? $config->getSeedNamespaceByPath(dirname($filePath)) : null;
894 1
895 1
                    // convert the filename to a class name
896 1
                    $class = ($namespace === null ? '' : $namespace . '\\') . pathinfo($filePath, PATHINFO_FILENAME);
897 1
                    $fileNames[$class] = basename($filePath);
898 1
899
                    // load the seed file
900
                    /** @noinspection PhpIncludeInspection */
901
                    require_once $filePath;
902
                    if (!class_exists($class)) {
903
                        throw new \InvalidArgumentException(sprintf(
904
                            'Could not find class "%s" in file "%s"',
905
                            $class,
906 1
                            $filePath
907
                        ));
908 1
                    }
909 1
910 1
                    // instantiate it
911 1
                    $seed = new $class($this->getInput(), $this->getOutput());
912 1
913
                    if (!($seed instanceof AbstractSeed)) {
914
                        throw new \InvalidArgumentException(sprintf(
915
                            'The class "%s" in file "%s" must extend \Phinx\Seed\AbstractSeed',
916
                            $class,
917
                            $filePath
918
                        ));
919
                    }
920
921
                    $seeds[$class] = $seed;
922
                }
923
            }
924
925
            ksort($seeds);
926
            $this->setSeeds($seeds);
927
        }
928
929
        return $this->seeds;
930
    }
931
932
    /**
933
     * Returns a list of seed files found in the provided seed paths.
934
     *
935
     * @return string[]
936
     */
937 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...
938
    {
939
        $config = $this->getConfig();
940
        $paths = $config->getSeedPaths($this->getEnvironmentName(), $this->getDbRef());
0 ignored issues
show
Unused Code introduced by
The call to ConfigInterface::getSeedPaths() has too many arguments starting with $this->getEnvironmentName().

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
941
942
        $files = [];
943
944
        foreach ($paths as $path) {
945
            $files = array_merge(
946
                $files,
947
                Util::glob($path . DIRECTORY_SEPARATOR . '*.php')
948
            );
949
        }
950
        // glob() can return the same file multiple times
951
        // This will cause the migration to fail with a
952
        // false assumption of duplicate migrations
953
        // http://php.net/manual/en/function.glob.php#110340
954
        $files = array_unique($files);
955
956
        return $files;
957
    }
958
959
    /**
960
     * Sets the config.
961
     *
962
     * @param  \Phinx\Config\ConfigInterface $config Configuration Object
963
     * @return \Phinx\Migration\Manager
964
     */
965
    public function setConfig(ConfigInterface $config)
966
    {
967
        $this->config = $config;
968
969
        return $this;
970
    }
971
972
    /**
973
     * Gets the config.
974
     *
975
     * @return \Phinx\Config\ConfigInterface
976
     */
977
    public function getConfig()
978
    {
979
        return $this->config;
980
    }
981
982
    /**
983
     * Toggles the breakpoint for a specific version.
984
     *
985
     * @param string $environment
986
     * @param int $version
987
     * @return void
988
     */
989
    public function toggleBreakpoint($environment, $version)
990
    {
991
        $migrations = $this->getMigrations();
992
        $this->getMigrations();
993
        $env = $this->getEnvironment($environment);
994
        $versions = $env->getVersionLog();
995
996
        if (empty($versions) || empty($migrations)) {
997
            return;
998
        }
999
1000
        if ($version === null) {
1001
            $lastVersion = end($versions);
1002
            $version = $lastVersion['version'];
1003
        }
1004
1005 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...
1006
            $this->output->writeln(sprintf(
1007
                '<comment>warning</comment> %s is not a valid version',
1008
                $version
1009
            ));
1010
1011
            return;
1012
        }
1013
1014
        $env->getAdapter()->toggleBreakpoint($migrations[$version]);
1015
1016
        $versions = $env->getVersionLog();
1017
1018
        $this->getOutput()->writeln(
1019
            ' Breakpoint ' . ($versions[$version]['breakpoint'] ? 'set' : 'cleared') .
1020
            ' for <info>' . $version . '</info>' .
1021
            ' <comment>' . $migrations[$version]->getName() . '</comment>'
1022
        );
1023
    }
1024
1025
    /**
1026
     * Remove all breakpoints
1027
     *
1028
     * @param string $environment
1029
     * @return void
1030
     */
1031
    public function removeBreakpoints($environment)
1032
    {
1033
        $this->getOutput()->writeln(sprintf(
1034
            ' %d breakpoints cleared.',
1035
            $this->getEnvironment($environment)->getAdapter()->resetAllBreakpoints()
1036
        ));
1037
    }
1038
}
1039