Completed
Pull Request — master (#1241)
by
unknown
01:55
created

Manager::getSingleEnvironment()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 27
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 3

Importance

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

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

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

Loading history...
351 5
                if ($migration->getVersion() <= $version) {
352 8
                    break;
353 8
                }
354
355
                if (in_array($migration->getVersion(), $versions)) {
356
                    $this->executeMigration($environment, $migration, MigrationInterface::DOWN);
357
                }
358
            }
359
        }
360
361
        ksort($migrations);
362 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...
363 119
            if ($migration->getVersion() > $version) {
364
                break;
365 119
            }
366 119
367
            if (!in_array($migration->getVersion(), $versions)) {
368 119
                $this->executeMigration($environment, $migration, MigrationInterface::UP);
369 119
            }
370 119
        }
371
    }
372
373 119
    /**
374 119
     * Execute a migration against the specified environment.
375 119
     *
376
     * @param string $name Environment Name
377 119
     * @param \Phinx\Migration\MigrationInterface $migration Migration
378
     * @param string $direction Direction
379 119
     * @return void
380 119
     */
381 119
    public function executeMigration($name, MigrationInterface $migration, $direction = MigrationInterface::UP)
382 119
    {
383 119
        $this->getOutput()->writeln('');
384
        $this->getOutput()->writeln(
385
            ' ==' .
386
            ' <info>' . $migration->getVersion() . ' ' . $migration->getName() . ':</info>' .
387
            ' <comment>' . ($direction === MigrationInterface::UP ? 'migrating' : 'reverting') . '</comment>'
388
        );
389
390
        // Execute the migration and log the time elapsed.
391
        $start = microtime(true);
392 6
        $this->getEnvironment($name)->executeMigration($migration, $direction);
393
        $end = microtime(true);
394 6
395 6
        $this->getOutput()->writeln(
396
            ' ==' .
397 6
            ' <info>' . $migration->getVersion() . ' ' . $migration->getName() . ':</info>' .
398 6
            ' <comment>' . ($direction === MigrationInterface::UP ? 'migrated' : 'reverted') .
399 6
            ' ' . sprintf('%.4fs', $end - $start) . '</comment>'
400
        );
401
    }
402 6
403 6
    /**
404 6
     * Execute a seeder against the specified environment.
405
     *
406 6
     * @param string $name Environment Name
407
     * @param \Phinx\Seed\SeedInterface $seed Seed
408 6
     * @return void
409 6
     */
410 6
    public function executeSeed($name, SeedInterface $seed)
411 6
    {
412 6
        $this->getOutput()->writeln('');
413
        $this->getOutput()->writeln(
414
            ' ==' .
415
            ' <info>' . $seed->getName() . ':</info>' .
416
            ' <comment>seeding</comment>'
417
        );
418
419
        // Execute the seeder and log the time elapsed.
420
        $start = microtime(true);
421
        $this->getEnvironment($name)->executeSeed($seed);
422
423 349
        $end = microtime(true);
424
425
        $this->getOutput()->writeln(
426 349
            ' ==' .
427
            ' <info>' . $seed->getName() . ':</info>' .
428
            ' <comment>seeded' .
429 349
            ' ' . sprintf('%.4fs', $end - $start) . '</comment>'
430
        );
431
    }
432 349
433
    /**
434 349
     * Rollback an environment to the specified version.
435
     *
436
     * @param string $environment Environment
437 349
     * @param int|string $target
438 48
     * @param bool $force
439 48
     * @param bool $targetMustMatchVersion
440 48
     * @return void
441
     */
442 349
    public function rollback($environment, $target = null, $force = false, $targetMustMatchVersion = true)
443 349
    {
444 349
        // note that the migrations are indexed by name (aka creation time) in ascending order
445
        $migrations = $this->getMigrations();
446
447 47
        // note that the version log are also indexed by name with the proper ascending order according to the version order
448
        $executedVersions = $this->getEnvironment($environment)->getVersionLog();
449 349
450
        // get a list of migrations sorted in the opposite way of the executed versions
451 349
        $sortedMigrations = [];
452 23
453 349
        foreach ($executedVersions as $versionCreationTime => &$executedVersion) {
454
            // if we have a date (ie. the target must not match a version) and we are sorting by execution time, we
455 20
            // convert the version start time so we can compare directly with the target date
456 20
            if (!$this->getConfig()->isVersionOrderCreationTime() && !$targetMustMatchVersion) {
457 20
                $dateTime = \DateTime::createFromFormat('Y-m-d H:i:s', $executedVersion['start_time']);
458 20
                $executedVersion['start_time'] = $dateTime->format('YmdHis');
459
            }
460
461 20
            if (isset($migrations[$versionCreationTime])) {
462 20
                array_unshift($sortedMigrations, $migrations[$versionCreationTime]);
463 20
            } else {
464
                // this means the version is missing so we unset it so that we don't consider it when rolling back
465
                // migrations (or choosing the last up version as target)
466
                unset($executedVersions[$versionCreationTime]);
467 20
            }
468
        }
469
470 349
        if ($target === 'all' || $target === '0') {
471 349
            $target = 0;
472 53
        } elseif (!is_numeric($target) && !is_null($target)) { // try to find a target version based on name
473 53
            // search through the migrations using the name
474
            $migrationNames = array_map(function ($item) {
475
                return $item['migration_name'];
476
            }, $executedVersions);
477 296
            $found = array_search($target, $migrationNames);
478
479 94
            // check on was found
480 94
            if ($found !== false) {
481 94
                $target = (string)$found;
482
            } else {
483
                $this->getOutput()->writeln("<error>No migration found with name ($target)</error>");
484 296
485 46
                return;
486 46
            }
487
        }
488
489
        // Check we have at least 1 migration to revert
490 250
        $executedVersionCreationTimes = array_keys($executedVersions);
491
        if (empty($executedVersionCreationTimes) || $target == end($executedVersionCreationTimes)) {
492 250
            $this->getOutput()->writeln('<error>No migrations to rollback</error>');
493 250
494 70
            return;
495
        }
496
497 250
        // If no target was supplied, revert the last migration
498 250
        if ($target === null) {
499
            // Get the migration before the last run migration
500 250
            $prev = count($executedVersionCreationTimes) - 2;
501 96
            $target = $prev >= 0 ? $executedVersionCreationTimes[$prev] : 0;
502 96
        }
503 42
504
        // If the target must match a version, check the target version exists
505 68
        if ($targetMustMatchVersion && 0 !== $target && !isset($migrations[$target])) {
506
            $this->getOutput()->writeln("<error>Target version ($target) not found</error>");
507 222
508 121
            return;
509 121
        }
510
511 117
        // Rollback all versions until we find the wanted rollback target
512 117
        $rollbacked = false;
513 117
514 250
        foreach ($sortedMigrations as $migration) {
515
            if ($targetMustMatchVersion && $migration->getVersion() == $target) {
516 250
                break;
517 133
            }
518 133
519 250
            if (in_array($migration->getVersion(), $executedVersionCreationTimes)) {
520
                $executedVersion = $executedVersions[$migration->getVersion()];
521
522
                if (!$targetMustMatchVersion) {
523
                    if (($this->getConfig()->isVersionOrderCreationTime() && $executedVersion['version'] <= $target) ||
524
                        (!$this->getConfig()->isVersionOrderCreationTime() && $executedVersion['start_time'] <= $target)) {
525
                        break;
526
                    }
527
                }
528 9
529
                if (0 != $executedVersion['breakpoint'] && !$force) {
530 9
                    $this->getOutput()->writeln('<error>Breakpoint reached. Further rollbacks inhibited.</error>');
531
                    break;
532 9
                }
533
                $this->executeMigration($environment, $migration, MigrationInterface::DOWN);
534 3
                $rollbacked = true;
535 3
            }
536 3
        }
537 3
538 3
        if (!$rollbacked) {
539 3
            $this->getOutput()->writeln('<error>No migrations to rollback</error>');
540
        }
541 6
    }
542 3
543 3
    /**
544 3
     * Run database seeders against an environment.
545
     *
546
     * @param string $environment Environment
547 6
     * @param string $seed Seeder
548
     * @return void
549
     */
550
    public function seed($environment, $seed = null)
551
    {
552
        $this->setEnvironmentName($environment);
553
        $seeds = $this->getSeeds();
554
555 381
        if ($seed === null) {
556
            // run all seeders
557 381
            foreach ($seeds as $seeder) {
558 381
                if (array_key_exists($seeder->getName(), $seeds)) {
559
                    $this->executeSeed($environment, $seeder);
560
                    unset($this->seeds[$seeder->getName()]);
561
                }
562
            }
563
        } else {
564
            // run only one seeder
565
            if (array_key_exists($seed, $seeds)) {
566
                $this->executeSeed($environment, $seeds[$seed]);
567
                unset($this->seeds[$seed]);
568 382
            } else {
569
                $this->getOutput()->writeln(sprintf('<error>The seed class "%s" does not exist</error>', $seed));
570 382
            }
571 380
        }
572
    }
573
574
    /**
575 7
     * Set the dbRef property
576 1
     *
577 1
     * @param string $ref
578
     * @return \Phinx\Migration\Manager
579 1
     */
580
    public function setDbRef($ref)
581
    {
582
        $this->dbRef = $ref;
583 6
        return $this;
584 6
    }
585
586 6
    /**
587 6
     * Get the adapter property
588 6
     *
589 6
     * @return string
590
     */
591 6
    public function getDbRef()
592
    {
593
        return $this->dbRef;
594
    }
595
596
    /**
597
     * Sets the environments.
598
     *
599
     * @param array $environments Environments
600 400
     * @return \Phinx\Migration\Manager
601
     */
602 400
    public function setEnvironments($environments = [])
603 400
    {
604
        $this->environments = $environments;
605
606
        return $this;
607
    }
608
609
    /**
610
     * Gets the manager class for the given environment.
611 393
     *
612
     * @param string $name Environment Name
613 393
     * @throws \InvalidArgumentException
614
     * @return \Phinx\Migration\Manager\Environment
615
     */
616
    public function getEnvironment($name)
617
    {
618
        if ($this->getDbRef() !== null) {
619
            return $this->getMultiEnvironment($name);
620
        }
621
622 400
        return $this->getSingleEnvironment($name);
623
    }
624 400
625 400
    public function setEnvironmentName($name)
626
    {
627
        $this->environmentName = $name;
628
        return $this;
629
    }
630
631
    public function getEnvironmentName()
632
    {
633 395
        return $this->environmentName;
634
    }
635 395
636
    /**
637
     * Gets the manager class for the given environment when given as a single instance.
638
     *
639
     * @param string $name Environment Name
640
     * @throws \InvalidArgumentException
641
     * @return \Phinx\Migration\Manager\Environment
642
     */
643
    public function getSingleEnvironment($name)
644 379
    {
645
646 379
        if (isset($this->environments[$name])) {
647 379
            return $this->environments[$name];
648
        }
649
650
        // check the environment exists
651
        if (!$this->getConfig()->hasEnvironment($name)) {
652
            throw new \InvalidArgumentException(sprintf(
653
                'The environment "%s" does not exist',
654
                $name
655
            ));
656
        }
657 388
658
        // create an environment instance and cache it
659 388
        $envOptions = $this->getConfig()->getEnvironment($name);
660 388
661
        $envOptions['version_order'] = $this->getConfig()->getVersionOrder();
662
        $environment = new Environment($name, $envOptions);
663 388
        $this->environments[$name] = $environment;
664
665 388
        $environment->setInput($this->getInput());
666
        $environment->setOutput($this->getOutput());
667 388
668 387
        return $environment;
669 387
    }
670
671 387
    /**
672 3
     * Gets the manager class for the given environment when used as part of a mulitple deployment.
673
     *
674
     * @param string $name Environment Name
675 387
     * @throws \InvalidArgumentException
676 387
     * @return \Phinx\Migration\Manager\Environment
677
     */
678
    public function getMultiEnvironment($name)
679 387
    {
680
        if (isset($this->environments[$name][$this->getDbRef()])) {
681 387
            return $this->environments[$name][$this->getDbRef()];
682 2
        }
683 2
684 2
        // check the environment exists
685 2
        if (!$this->getConfig()->hasEnvironment($name)) {
686 2
            throw new \InvalidArgumentException(sprintf(
687
                'The environment "%s" does not exist',
688
                $name
689 387
            ));
690
        }
691
692
        // create an environment instance and cache it
693 387
        $envOptions = $this->getConfig()->getEnvironment($name);
694 387
        $envOptions = $envOptions[$this->getDbRef()];
695 2
696 2
        $envOptions['version_order'] = $this->getConfig()->getVersionOrder();
697 2
        $environment = new Environment($name, $envOptions);
698
        $this->environments[$name][$this->getDbRef()] = $environment;
699 2
700
        $environment->setInput($this->getInput());
701
        $environment->setOutput($this->getOutput());
702
703 385
        return $environment;
704
    }
705 385
706 2
    /**
707 2
     * Sets the console input.
708 2
     *
709
     * @param \Symfony\Component\Console\Input\InputInterface $input Input
710 2
     * @return \Phinx\Migration\Manager
711
     */
712
    public function setInput(InputInterface $input)
713 383
    {
714 383
        $this->input = $input;
715 384
716
        return $this;
717 379
    }
718 379
719 379
    /**
720
     * Gets the console input.
721 379
     *
722
     * @return \Symfony\Component\Console\Input\InputInterface
723
     */
724
    public function getInput()
725
    {
726
        return $this->input;
727
    }
728
729 388
    /**
730
     * Sets the console output.
731 388
     *
732 388
     * @param \Symfony\Component\Console\Output\OutputInterface $output Output
733 388
     * @return \Phinx\Migration\Manager
734
     */
735 388
    public function setOutput(OutputInterface $output)
736 388
    {
737 388
        $this->output = $output;
738 388
739 388
        return $this;
740 388
    }
741
742 388
    /**
743
     * Gets the console output.
744
     *
745
     * @return \Symfony\Component\Console\Output\OutputInterface
746
     */
747
    public function getOutput()
748
    {
749
        return $this->output;
750
    }
751 11
752
    /**
753 11
     * Sets the database migrations.
754 11
     *
755
     * @param array $migrations Migrations
756
     * @return \Phinx\Migration\Manager
757
     */
758
    public function setMigrations(array $migrations = null)
759
    {
760
        $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...
761
762
        return $this;
763 11
    }
764
765 11
    /**
766 11
     * Gets an array of the database migrations, indexed by migration name (aka creation time) and sorted in ascending
767
     * order
768
     *
769 11
     * @throws \InvalidArgumentException
770
     * @return \Phinx\Migration\AbstractMigration[]
771 11
     */
772
    public function getMigrations()
773 11
    {
774 11
        if ($this->migrations === null) {
775 11
            $phpFiles = $this->getMigrationFiles();
776 11
            // filter the files to only get the ones that match our naming scheme
777
            $fileNames = [];
778
            /** @var \Phinx\Migration\AbstractMigration[] $versions */
779 11
            $versions = [];
780 11
781
            foreach ($phpFiles as $filePath) {
782
                if (Util::isValidMigrationFileName(basename($filePath))) {
783
                    $version = Util::getVersionFromFileName(basename($filePath));
784 11
785 11
                    if (isset($versions[$version])) {
786
                        throw new \InvalidArgumentException(sprintf('Duplicate migration - "%s" has the same version as "%s"', $filePath, $versions[$version]->getVersion()));
787
                    }
788
789
                    $config = $this->getConfig();
790
                    $namespace = $config instanceof NamespaceAwareInterface ? $config->getMigrationNamespaceByPath(dirname($filePath)) : null;
791
792
                    // convert the filename to a class name
793
                    $class = ($namespace === null ? '' : $namespace . '\\') . Util::mapFileNameToClassName(basename($filePath));
794 11
795
                    if (isset($fileNames[$class])) {
796 11
                        throw new \InvalidArgumentException(sprintf(
797
                            'Migration "%s" has the same name as "%s"',
798
                            basename($filePath),
799
                            $fileNames[$class]
800
                        ));
801
                    }
802
803
                    $fileNames[$class] = basename($filePath);
804 11
805 11
                    // load the migration file
806 11
                    /** @noinspection PhpIncludeInspection */
807
                    require_once $filePath;
808 11
                    if (!class_exists($class)) {
809 11
                        throw new \InvalidArgumentException(sprintf(
810 11
                            'Could not find class "%s" in file "%s"',
811
                            $class,
812 11
                            $filePath
813
                        ));
814
                    }
815
816
                    // instantiate it
817
                    $migration = new $class($version, $this->getInput(), $this->getOutput());
818
819
                    if (!($migration instanceof AbstractMigration)) {
820 11
                        throw new \InvalidArgumentException(sprintf(
821
                            'The class "%s" in file "%s" must extend \Phinx\Migration\AbstractMigration',
822 11
                            $class,
823 11
                            $filePath
824 11
                        ));
825
                    }
826 11
827 11
                    $versions[$version] = $migration;
828 11
                }
829 11
            }
830 11
831 11
            ksort($versions);
832
            $this->setMigrations($versions);
833 11
        }
834
835
        return $this->migrations;
836
    }
837
838
    /**
839
     * Returns a list of migration files found in the provided migration paths.
840
     *
841
     * @return string[]
842 400
     */
843 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...
844 400
    {
845 400
        $config = $this->getConfig();
846
        $paths = $config->getMigrationPaths($this->getEnvironmentName(), $this->getDbRef());
847
        $files = [];
848
849
        foreach ($paths as $path) {
850
            $files = array_merge(
851
                $files,
852
                Util::glob($path . DIRECTORY_SEPARATOR . '*.php')
853 399
            );
854
        }
855 399
        // glob() can return the same file multiple times
856
        // This will cause the migration to fail with a
857
        // false assumption of duplicate migrations
858
        // http://php.net/manual/en/function.glob.php#110340
859
        $files = array_unique($files);
860
861
        return $files;
862
    }
863
864
    /**
865 2
     * Sets the database seeders.
866
     *
867 2
     * @param array $seeds Seeders
868 2
     * @return \Phinx\Migration\Manager
869 2
     */
870 2
    public function setSeeds(array $seeds = null)
871
    {
872 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...
873
874
        return $this;
875
    }
876 2
877 1
    /**
878 1
     * Get seed dependencies instances from seed dependency array
879 1
     *
880
     * @param AbstractSeed $seed Seed
881 2
     *
882 1
     * @return AbstractSeed[]
883 1
     */
884
    private function getSeedDependenciesInstances(AbstractSeed $seed)
885 1
    {
886 1
        $dependenciesInstances = [];
887
        $dependencies = $seed->getDependencies();
888
        if (!empty($dependencies)) {
889 1
            foreach ($dependencies as $dependency) {
890
                foreach ($this->seeds as $seed) {
891 1
                    if (get_class($seed) === $dependency) {
892
                        $dependenciesInstances[get_class($seed)] = $seed;
893 1
                    }
894 1
                }
895 1
            }
896 1
        }
897 1
898 1
        return $dependenciesInstances;
899
    }
900
901
    /**
902
     * Order seeds by dependencies
903
     *
904
     * @param AbstractSeed[] $seeds Seeds
905
     *
906 1
     * @return AbstractSeed[]
907
     */
908 1
    private function orderSeedsByDependencies(array $seeds)
909 1
    {
910 1
        $orderedSeeds = [];
911 1
        foreach ($seeds as $seed) {
912 1
            $key = get_class($seed);
913
            $dependencies = $this->getSeedDependenciesInstances($seed);
914
            if (!empty($dependencies)) {
915
                $orderedSeeds[$key] = $seed;
916
                $orderedSeeds = array_merge($this->orderSeedsByDependencies($dependencies), $orderedSeeds);
917
            } else {
918
                $orderedSeeds[$key] = $seed;
919
            }
920
        }
921
922
        return $orderedSeeds;
923
    }
924
925
    /**
926
     * Gets an array of database seeders.
927
     *
928
     * @throws \InvalidArgumentException
929
     * @return \Phinx\Seed\AbstractSeed[]
930
     */
931
    public function getSeeds()
932
    {
933
        if ($this->seeds === null) {
934
            $phpFiles = $this->getSeedFiles();
935
936
            // filter the files to only get the ones that match our naming scheme
937
            $fileNames = [];
938
            /** @var \Phinx\Seed\AbstractSeed[] $seeds */
939
            $seeds = [];
940
941
            foreach ($phpFiles as $filePath) {
942
                if (Util::isValidSeedFileName(basename($filePath))) {
943
                    $config = $this->getConfig();
944
                    $namespace = $config instanceof NamespaceAwareInterface ? $config->getSeedNamespaceByPath(dirname($filePath)) : null;
945
946
                    // convert the filename to a class name
947
                    $class = ($namespace === null ? '' : $namespace . '\\') . pathinfo($filePath, PATHINFO_FILENAME);
948
                    $fileNames[$class] = basename($filePath);
949
950
                    // load the seed file
951
                    /** @noinspection PhpIncludeInspection */
952
                    require_once $filePath;
953
                    if (!class_exists($class)) {
954
                        throw new \InvalidArgumentException(sprintf(
955
                            'Could not find class "%s" in file "%s"',
956
                            $class,
957
                            $filePath
958
                        ));
959
                    }
960
961
                    // instantiate it
962
                    $seed = new $class($this->getInput(), $this->getOutput());
963
964
                    if (!($seed instanceof AbstractSeed)) {
965
                        throw new \InvalidArgumentException(sprintf(
966
                            'The class "%s" in file "%s" must extend \Phinx\Seed\AbstractSeed',
967
                            $class,
968
                            $filePath
969
                        ));
970
                    }
971
972
                    $seeds[$class] = $seed;
973
                }
974
            }
975
976
            ksort($seeds);
977
            $this->setSeeds($seeds);
978
        }
979
980
        $this->seeds = $this->orderSeedsByDependencies($this->seeds);
981
        return $this->seeds;
982
    }
983
984
    /**
985
     * Returns a list of seed files found in the provided seed paths.
986
     *
987
     * @return string[]
988
     */
989 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...
990
    {
991
        $config = $this->getConfig();
992
        $paths = $config->getSeedPaths($this->getEnvironmentName(), $this->getDbRef());
993
994
        $files = [];
995
996
        foreach ($paths as $path) {
997
            $files = array_merge(
998
                $files,
999
                Util::glob($path . DIRECTORY_SEPARATOR . '*.php')
1000
            );
1001
        }
1002
        // glob() can return the same file multiple times
1003
        // This will cause the migration to fail with a
1004
        // false assumption of duplicate migrations
1005
        // http://php.net/manual/en/function.glob.php#110340
1006
        $files = array_unique($files);
1007
1008
        return $files;
1009
    }
1010
1011
    /**
1012
     * Sets the config.
1013
     *
1014
     * @param  \Phinx\Config\ConfigInterface $config Configuration Object
1015
     * @return \Phinx\Migration\Manager
1016
     */
1017
    public function setConfig(ConfigInterface $config)
1018
    {
1019
        $this->config = $config;
1020
1021
        return $this;
1022
    }
1023
1024
    /**
1025
     * Gets the config.
1026
     *
1027
     * @return \Phinx\Config\ConfigInterface
1028
     */
1029
    public function getConfig()
1030
    {
1031
        return $this->config;
1032
    }
1033
1034
    /**
1035
     * Toggles the breakpoint for a specific version.
1036
     *
1037
     * @param string $environment
1038
     * @param int $version
1039
     * @return void
1040
     */
1041
    public function toggleBreakpoint($environment, $version)
1042
    {
1043
        $migrations = $this->getMigrations();
1044
        $this->getMigrations();
1045
        $env = $this->getEnvironment($environment);
1046
        $versions = $env->getVersionLog();
1047
1048
        if (empty($versions) || empty($migrations)) {
1049
            return;
1050
        }
1051
1052
        if ($version === null) {
1053
            $lastVersion = end($versions);
1054
            $version = $lastVersion['version'];
1055
        }
1056
1057 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...
1058
            $this->output->writeln(sprintf(
1059
                '<comment>warning</comment> %s is not a valid version',
1060
                $version
1061
            ));
1062
1063
            return;
1064
        }
1065
1066
        $env->getAdapter()->toggleBreakpoint($migrations[$version]);
1067
1068
        $versions = $env->getVersionLog();
1069
1070
        $this->getOutput()->writeln(
1071
            ' Breakpoint ' . ($versions[$version]['breakpoint'] ? 'set' : 'cleared') .
1072
            ' for <info>' . $version . '</info>' .
1073
            ' <comment>' . $migrations[$version]->getName() . '</comment>'
1074
        );
1075
    }
1076
1077
    /**
1078
     * Remove all breakpoints
1079
     *
1080
     * @param string $environment
1081
     * @return void
1082
     */
1083
    public function removeBreakpoints($environment)
1084
    {
1085
        $this->getOutput()->writeln(sprintf(
1086
            ' %d breakpoints cleared.',
1087
            $this->getEnvironment($environment)->getAdapter()->resetAllBreakpoints()
1088
        ));
1089
    }
1090
}
1091