Passed
Pull Request — master (#2007)
by
unknown
02:49
created

Manager::printSeedStatus()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 4
c 0
b 0
f 0
dl 0
loc 6
ccs 4
cts 4
cp 1
rs 10
cc 1
nc 1
nop 3
crap 1
1
<?php
2
3
/**
4
 * MIT License
5
 * For full license information, please view the LICENSE file that was distributed with this source code.
6
 */
7
8
namespace Phinx\Migration;
9
10
use DateTime;
11
use InvalidArgumentException;
12
use Phinx\Config\Config;
13
use Phinx\Config\ConfigInterface;
14
use Phinx\Config\NamespaceAwareInterface;
15
use Phinx\Console\Command\AbstractCommand;
16
use Phinx\Migration\Manager\Environment;
17
use Phinx\Seed\AbstractSeed;
18
use Phinx\Seed\SeedInterface;
19
use Phinx\Util\Util;
20
use Psr\Container\ContainerInterface;
21
use RuntimeException;
22
use Symfony\Component\Console\Input\InputInterface;
23
use Symfony\Component\Console\Output\OutputInterface;
24
25
class Manager
26
{
27
    public const BREAKPOINT_TOGGLE = 1;
28
    public const BREAKPOINT_SET = 2;
29
    public const BREAKPOINT_UNSET = 3;
30
31
    /**
32
     * @var \Phinx\Config\ConfigInterface
33
     */
34
    protected $config;
35
36
    /**
37
     * @var \Symfony\Component\Console\Input\InputInterface
38
     */
39
    protected $input;
40
41
    /**
42
     * @var \Symfony\Component\Console\Output\OutputInterface
43
     */
44
    protected $output;
45
46
    /**
47
     * @var \Phinx\Migration\Manager\Environment[]
48
     */
49
    protected $environments = [];
50
51
    /**
52
     * @var \Phinx\Migration\AbstractMigration[]|null
53
     */
54
    protected $migrations;
55
56
    /**
57
     * @var \Phinx\Seed\AbstractSeed[]|null
58
     */
59
    protected $seeds;
60
61
    /**
62
     * @var \Psr\Container\ContainerInterface
63
     */
64
    protected $container;
65
66
    /**
67
     * @param \Phinx\Config\ConfigInterface $config Configuration Object
68
     * @param \Symfony\Component\Console\Input\InputInterface $input Console Input
69
     * @param \Symfony\Component\Console\Output\OutputInterface $output Console Output
70
     */
71
    public function __construct(ConfigInterface $config, InputInterface $input, OutputInterface $output)
72
    {
73
        $this->setConfig($config);
74
        $this->setInput($input);
75
        $this->setOutput($output);
76
    }
77
78
    /**
79
     * Prints the specified environment's migration status.
80
     *
81
     * @param string $environment environment to print status of
82
     * @param string|null $format format to print status in (either text, json, or null)
83
     * @throws \RuntimeException
84
     * @return array array indicating if there are any missing or down migrations
85
     */
86
    public function printStatus($environment, $format = null)
87
    {
88
        $output = $this->getOutput();
89 432
        $hasDownMigration = false;
90
        $hasMissingMigration = false;
91 432
        $migrations = $this->getMigrations($environment);
92 432
        $migrationCount = 0;
93 432
        $missingCount = 0;
94 432
        $pendingMigrationCount = 0;
95
        $finalMigrations = [];
96
        $verbosity = $output->getVerbosity();
97
        if ($format === 'json') {
98
            $output->setVerbosity(OutputInterface::VERBOSITY_QUIET);
99
        }
100
        if (count($migrations)) {
101
            // rewrite using Symfony Table Helper as we already have this library
102
            // included and it will fix formatting issues (e.g drawing the lines)
103 22
            $output->writeln('');
104
105 22
            switch ($this->getConfig()->getVersionOrder()) {
106 22
                case Config::VERSION_ORDER_CREATION_TIME:
107 22
                    $migrationIdAndStartedHeader = '<info>[Migration ID]</info>  Started            ';
108 22
                    break;
109 22
                case Config::VERSION_ORDER_EXECUTION_TIME:
110 22
                    $migrationIdAndStartedHeader = 'Migration ID    <info>[Started          ]</info>';
111
                    break;
112
                default:
113 21
                    throw new RuntimeException('Invalid version_order configuration option');
114
            }
115 21
116 21
            $output->writeln(" Status  $migrationIdAndStartedHeader  Finished             Migration Name ");
117 19
            $output->writeln('----------------------------------------------------------------------------------');
118 19
119 2
            $env = $this->getEnvironment($environment);
120 1
            $versions = $env->getVersionLog();
121 1
122 1
            $maxNameLength = $versions ? max(array_map(function ($version) {
123 1
                return strlen($version['migration_name']);
124 21
            }, $versions)) : 0;
125
126 20
            $missingVersions = array_diff_key($versions, $migrations);
127 20
            $missingCount = count($missingVersions);
128
129 20
            $hasMissingMigration = !empty($missingVersions);
130 20
131
            // get the migrations sorted in the same way as the versions
132
            /** @var \Phinx\Migration\AbstractMigration[] $sortedMigrations */
133 17
            $sortedMigrations = [];
134 20
135
            foreach ($versions as $versionCreationTime => $version) {
136 20
                if (isset($migrations[$versionCreationTime])) {
137
                    array_push($sortedMigrations, $migrations[$versionCreationTime]);
138 20
                    unset($migrations[$versionCreationTime]);
139
                }
140
            }
141 20
142
            if (empty($sortedMigrations) && !empty($missingVersions)) {
143 20
                // this means we have no up migrations, so we write all the missing versions already so they show up
144 17
                // before any possible down migration
145 13
                foreach ($missingVersions as $missingVersionCreationTime => $missingVersion) {
146 13
                    $this->printMissingVersion($missingVersion, $maxNameLength);
147 13
148 20
                    unset($missingVersions[$missingVersionCreationTime]);
149
                }
150 20
            }
151
152
            // any migration left in the migrations (ie. not unset when sorting the migrations by the version order) is
153 4
            // a migration that is down, so we add them to the end of the sorted migrations list
154 4
            if (!empty($migrations)) {
155
                $sortedMigrations = array_merge($sortedMigrations, $migrations);
156 4
            }
157 4
158 4
            $migrationCount = count($sortedMigrations);
159
            foreach ($sortedMigrations as $migration) {
160
                $version = array_key_exists($migration->getVersion(), $versions) ? $versions[$migration->getVersion()] : false;
161
                if ($version) {
162 20
                    // check if there are missing versions before this version
163 13
                    foreach ($missingVersions as $missingVersionCreationTime => $missingVersion) {
164 13
                        if ($this->getConfig()->isVersionOrderCreationTime()) {
165
                            if ($missingVersion['version'] > $version['version']) {
166 20
                                break;
167 20
                            }
168 20
                        } else {
169
                            if ($missingVersion['start_time'] > $version['start_time']) {
170 13
                                break;
171 6
                            } elseif (
172 6
                                $missingVersion['start_time'] == $version['start_time'] &&
173 4
                                $missingVersion['version'] > $version['version']
174
                            ) {
175 3
                                break;
176
                            }
177
                        }
178
179
                        $this->printMissingVersion($missingVersion, $maxNameLength);
180
181
                        unset($missingVersions[$missingVersionCreationTime]);
182
                    }
183
184 3
                    $status = '     <info>up</info> ';
185
                } else {
186 3
                    $pendingMigrationCount++;
187 13
                    $hasDownMigration = true;
188
                    $status = '   <error>down</error> ';
189 13
                }
190 13
                $maxNameLength = max($maxNameLength, strlen($migration->getName()));
191 13
192 13
                $output->writeln(sprintf(
193
                    '%s %14.0f  %19s  %19s  <comment>%s</comment>',
194 20
                    $status,
195
                    $migration->getVersion(),
196 20
                    ($version ? $version['start_time'] : ''),
197 20
                    ($version ? $version['end_time'] : ''),
198 20
                    $migration->getName()
199 20
                ));
200 20
201 20
                if ($version && $version['breakpoint']) {
202 20
                    $output->writeln('         <error>BREAKPOINT SET</error>');
203 20
                }
204
205 20
                $finalMigrations[] = ['migration_status' => trim(strip_tags($status)), 'migration_id' => sprintf('%14.0f', $migration->getVersion()), 'migration_name' => $migration->getName()];
206 1
                unset($versions[$migration->getVersion()]);
207 1
            }
208
209 20
            // and finally add any possibly-remaining missing migrations
210 20
            foreach ($missingVersions as $missingVersionCreationTime => $missingVersion) {
211 20
                $this->printMissingVersion($missingVersion, $maxNameLength);
212
213
                unset($missingVersions[$missingVersionCreationTime]);
214 20
            }
215 4
        } else {
216
            // there are no migrations
217 4
            $output->writeln('');
218 20
            $output->writeln('There are no available migrations. Try creating one using the <info>create</info> command.');
219 20
        }
220
221 1
        // write an empty line
222 1
        $output->writeln('');
223
224
        if ($format !== null) {
225
            switch ($format) {
226 21
                case AbstractCommand::FORMAT_JSON:
227 21
                    $output->setVerbosity($verbosity);
228
                    $output->writeln(json_encode(
229
                        [
230
                            'pending_count' => $pendingMigrationCount,
231
                            'missing_count' => $missingCount,
232
                            'total_count' => $migrationCount + $missingCount,
233
                            'migrations' => $finalMigrations,
234
                        ]
235
                    ));
236
                    break;
237
                default:
238
                    $output->writeln('<info>Unsupported format: ' . $format . '</info>');
239
            }
240
        }
241
242 21
        return [
243 10
            'hasMissingMigration' => $hasMissingMigration,
244 11
            'hasDownMigration' => $hasDownMigration,
245 6
        ];
246
    }
247 5
248
    /**
249
     * Print Missing Version
250
     *
251
     * @param array $version The missing version to print (in the format returned by Environment.getVersionLog).
252
     * @param int $maxNameLength The maximum migration name length.
253
     * @return void
254
     */
255
    protected function printMissingVersion($version, $maxNameLength)
256
    {
257 10
        $this->getOutput()->writeln(sprintf(
258
            '     <error>up</error>  %14.0f  %19s  %19s  <comment>%s</comment>  <error>** MISSING MIGRATION FILE **</error>',
259 10
            $version['version'],
260 10
            $version['start_time'],
261 10
            $version['end_time'],
262 10
            str_pad($version['migration_name'], $maxNameLength, ' ')
263 10
        ));
264 10
265 10
        if ($version && $version['breakpoint']) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $version of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
266
            $this->getOutput()->writeln('         <error>BREAKPOINT SET</error>');
267 10
        }
268 1
    }
269 1
270 10
    /**
271
     * Migrate to the version of the database on a given date.
272
     *
273
     * @param string $environment Environment
274
     * @param \DateTime $dateTime Date to migrate to
275
     * @param bool $fake flag that if true, we just record running the migration, but not actually do the
276
     *                               migration
277
     * @return void
278
     */
279
    public function migrateToDateTime($environment, DateTime $dateTime, $fake = false)
280 4
    {
281
        $versions = array_keys($this->getMigrations($environment));
282 4
        $dateString = $dateTime->format('YmdHis');
283 4
284
        $outstandingMigrations = array_filter($versions, function ($version) use ($dateString) {
285
            return $version <= $dateString;
286 4
        });
287 4
288
        if (count($outstandingMigrations) > 0) {
289 4
            $migration = max($outstandingMigrations);
290 3
            $this->getOutput()->writeln('Migrating to version ' . $migration);
291 3
            $this->migrate($environment, $migration, $fake);
292 3
        }
293 3
    }
294 4
295
    /**
296
     * Migrate an environment to the specified version.
297
     *
298
     * @param string $environment Environment
299
     * @param int|null $version version to migrate to
300
     * @param bool $fake flag that if true, we just record running the migration, but not actually do the migration
301
     * @return void
302
     */
303 8
    public function migrate($environment, $version = null, $fake = false)
304
    {
305 8
        $migrations = $this->getMigrations($environment);
306 8
        $env = $this->getEnvironment($environment);
307 8
        $versions = $env->getVersions();
308 8
        $current = $env->getCurrentVersion();
309
310 8
        if (empty($versions) && empty($migrations)) {
311
            return;
312
        }
313
314 8
        if ($version === null) {
315 5
            $version = max(array_merge($versions, array_keys($migrations)));
316 5
        } else {
317 3
            if ($version != 0 && !isset($migrations[$version])) {
318
                $this->output->writeln(sprintf(
319
                    '<comment>warning</comment> %s is not a valid version',
320
                    $version
321
                ));
322
323
                return;
324
            }
325
        }
326
327 8
        // are we migrating up or down?
328
        $direction = $version > $current ? MigrationInterface::UP : MigrationInterface::DOWN;
329 8
330
        if ($direction === MigrationInterface::DOWN) {
331
            // run downs first
332
            krsort($migrations);
333
            foreach ($migrations as $migration) {
334
                if ($migration->getVersion() <= $version) {
335
                    break;
336
                }
337
338
                if (in_array($migration->getVersion(), $versions)) {
339
                    $this->executeMigration($environment, $migration, MigrationInterface::DOWN, $fake);
340
                }
341
            }
342
        }
343 8
344 8
        ksort($migrations);
345 8
        foreach ($migrations as $migration) {
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::UP, $fake);
352 8
            }
353 8
        }
354
    }
355
356
    /**
357
     * Execute a migration against the specified environment.
358
     *
359
     * @param string $name Environment Name
360
     * @param \Phinx\Migration\MigrationInterface $migration Migration
361
     * @param string $direction Direction
362
     * @param bool $fake flag that if true, we just record running the migration, but not actually do the migration
363 119
     * @return void
364
     */
365 119
    public function executeMigration($name, MigrationInterface $migration, $direction = MigrationInterface::UP, $fake = false)
366 119
    {
367
        $this->getOutput()->writeln('');
368 119
369 119
        // Skip the migration if it should not be executed
370 119
        if (!$migration->shouldExecute()) {
371
            $this->printMigrationStatus($migration, 'skipped');
372
373 119
            return;
374 119
        }
375 119
376
        $this->printMigrationStatus($migration, ($direction === MigrationInterface::UP ? 'migrating' : 'reverting'));
377 119
378
        // Execute the migration and log the time elapsed.
379 119
        $start = microtime(true);
380 119
        $this->getEnvironment($name)->executeMigration($migration, $direction, $fake);
381 119
        $end = microtime(true);
382 119
383 119
        $this->printMigrationStatus(
384
            $migration,
385
            ($direction === MigrationInterface::UP ? 'migrated' : 'reverted'),
386
            sprintf('%.4fs', $end - $start)
387
        );
388
    }
389
390
    /**
391
     * Execute a seeder against the specified environment.
392 6
     *
393
     * @param string $name Environment Name
394 6
     * @param \Phinx\Seed\SeedInterface $seed Seed
395 6
     * @return void
396
     */
397 6
    public function executeSeed($name, SeedInterface $seed)
398 6
    {
399 6
        $this->getOutput()->writeln('');
400
401
        // Skip the seed if it should not be executed
402 6
        if (!$seed->shouldExecute()) {
403 6
            $this->printSeedStatus($seed, 'skipped');
404 6
405
            return;
406 6
        }
407
408 6
        $this->printSeedStatus($seed, 'seeding');
409 6
410 6
        // Execute the seeder and log the time elapsed.
411 6
        $start = microtime(true);
412 6
        $this->getEnvironment($name)->executeSeed($seed);
413
        $end = microtime(true);
414
415
        $this->printSeedStatus(
416
            $seed,
417
            'seeded',
418
            sprintf('%.4fs', $end - $start)
419
        );
420
    }
421
422
    /**
423 349
     * Print Migration Status
424
     *
425
     * @param \Phinx\Migration\MigrationInterface $migration Migration
426 349
     * @param string $status Status of the migration
427
     * @param string|null $duration Duration the migration took the be executed
428
     * @return void
429 349
     */
430
    protected function printMigrationStatus(MigrationInterface $migration, $status, $duration = null)
431
    {
432 349
        $this->printStatusOutput(
433
            $migration->getVersion() . ' ' . $migration->getName(),
434 349
            $status,
435
            $duration
436
        );
437 349
    }
438 48
439 48
    /**
440 48
     * Print Seed Status
441
     *
442 349
     * @param \Phinx\Seed\SeedInterface $seed Seed
443 349
     * @param string $status Status of the seed
444 349
     * @param string|null $duration Duration the seed took the be executed
445
     * @return void
446
     */
447 47
    protected function printSeedStatus(SeedInterface $seed, $status, $duration = null)
448
    {
449 349
        $this->printStatusOutput(
450
            $seed->getName(),
451 349
            $status,
452 23
            $duration
453 349
        );
454
    }
455 20
456 20
    /**
457 20
     * Print Status in Output
458 20
     *
459
     * @param string $name Name of the migration or seed
460
     * @param string $status Status of the migration or seed
461 20
     * @param string|null $duration Duration the migration or seed took the be executed
462 20
     * @return void
463 20
     */
464
    protected function printStatusOutput($name, $status, $duration = null)
465
    {
466
        $this->getOutput()->writeln(
467 20
            ' ==' .
468
            ' <info>' . $name . ':</info>' .
469
            ' <comment>' . $status . ' ' . $duration . '</comment>'
470 349
        );
471 349
    }
472 53
473 53
    /**
474
     * Rollback an environment to the specified version.
475
     *
476
     * @param string $environment Environment
477 296
     * @param int|string|null $target Target
478
     * @param bool $force Force
479 94
     * @param bool $targetMustMatchVersion Target must match version
480 94
     * @param bool $fake Flag that if true, we just record running the migration, but not actually do the migration
481 94
     * @return void
482
     */
483
    public function rollback($environment, $target = null, $force = false, $targetMustMatchVersion = true, $fake = false)
484 296
    {
485 46
        // note that the migrations are indexed by name (aka creation time) in ascending order
486 46
        $migrations = $this->getMigrations($environment);
487
488
        // note that the version log are also indexed by name with the proper ascending order according to the version order
489
        $executedVersions = $this->getEnvironment($environment)->getVersionLog();
490 250
491
        // get a list of migrations sorted in the opposite way of the executed versions
492 250
        $sortedMigrations = [];
493 250
494 70
        foreach ($executedVersions as $versionCreationTime => &$executedVersion) {
495
            // if we have a date (ie. the target must not match a version) and we are sorting by execution time, we
496
            // convert the version start time so we can compare directly with the target date
497 250
            if (!$this->getConfig()->isVersionOrderCreationTime() && !$targetMustMatchVersion) {
498 250
                $dateTime = DateTime::createFromFormat('Y-m-d H:i:s', $executedVersion['start_time']);
499
                $executedVersion['start_time'] = $dateTime->format('YmdHis');
500 250
            }
501 96
502 96
            if (isset($migrations[$versionCreationTime])) {
503 42
                array_unshift($sortedMigrations, $migrations[$versionCreationTime]);
504
            } else {
505 68
                // this means the version is missing so we unset it so that we don't consider it when rolling back
506
                // migrations (or choosing the last up version as target)
507 222
                unset($executedVersions[$versionCreationTime]);
508 121
            }
509 121
        }
510
511 117
        if ($target === 'all' || $target === '0') {
512 117
            $target = 0;
513 117
        } elseif (!is_numeric($target) && $target !== null) { // try to find a target version based on name
514 250
            // search through the migrations using the name
515
            $migrationNames = array_map(function ($item) {
516 250
                return $item['migration_name'];
517 133
            }, $executedVersions);
518 133
            $found = array_search($target, $migrationNames, true);
519 250
520
            // check on was found
521
            if ($found !== false) {
522
                $target = (string)$found;
523
            } else {
524
                $this->getOutput()->writeln("<error>No migration found with name ($target)</error>");
525
526
                return;
527
            }
528 9
        }
529
530 9
        // Check we have at least 1 migration to revert
531
        $executedVersionCreationTimes = array_keys($executedVersions);
532 9
        if (empty($executedVersionCreationTimes) || $target == end($executedVersionCreationTimes)) {
533
            $this->getOutput()->writeln('<error>No migrations to rollback</error>');
534 3
535 3
            return;
536 3
        }
537 3
538 3
        // If no target was supplied, revert the last migration
539 3
        if ($target === null) {
540
            // Get the migration before the last run migration
541 6
            $prev = count($executedVersionCreationTimes) - 2;
542 3
            $target = $prev >= 0 ? $executedVersionCreationTimes[$prev] : 0;
543 3
        }
544 3
545
        // If the target must match a version, check the target version exists
546
        if ($targetMustMatchVersion && $target !== 0 && !isset($migrations[$target])) {
547 6
            $this->getOutput()->writeln("<error>Target version ($target) not found</error>");
548
549
            return;
550
        }
551
552
        // Rollback all versions until we find the wanted rollback target
553
        $rollbacked = false;
554
555 381
        foreach ($sortedMigrations as $migration) {
556
            if ($targetMustMatchVersion && $migration->getVersion() == $target) {
557 381
                break;
558 381
            }
559
560
            if (in_array($migration->getVersion(), $executedVersionCreationTimes)) {
561
                $executedVersion = $executedVersions[$migration->getVersion()];
562
563
                if (!$targetMustMatchVersion) {
564
                    if (
565
                        ($this->getConfig()->isVersionOrderCreationTime() && $executedVersion['version'] <= $target) ||
566
                        (!$this->getConfig()->isVersionOrderCreationTime() && $executedVersion['start_time'] <= $target)
567
                    ) {
568 382
                        break;
569
                    }
570 382
                }
571 380
572
                if ($executedVersion['breakpoint'] != 0 && !$force) {
573
                    $this->getOutput()->writeln('<error>Breakpoint reached. Further rollbacks inhibited.</error>');
574
                    break;
575 7
                }
576 1
                $this->executeMigration($environment, $migration, MigrationInterface::DOWN, $fake);
577 1
                $rollbacked = true;
578
            }
579 1
        }
580
581
        if (!$rollbacked) {
0 ignored issues
show
introduced by
The condition $rollbacked is always false.
Loading history...
582
            $this->getOutput()->writeln('<error>No migrations to rollback</error>');
583 6
        }
584 6
    }
585
586 6
    /**
587 6
     * Run database seeders against an environment.
588 6
     *
589 6
     * @param string $environment Environment
590
     * @param string|null $seed Seeder
591 6
     * @throws \InvalidArgumentException
592
     * @return void
593
     */
594
    public function seed($environment, $seed = null)
595
    {
596
        $seeds = $this->getSeeds();
597
598
        if ($seed === null) {
599
            // run all seeders
600 400
            foreach ($seeds as $seeder) {
601
                if (array_key_exists($seeder->getName(), $seeds)) {
602 400
                    $this->executeSeed($environment, $seeder);
603 400
                }
604
            }
605
        } else {
606
            // run only one seeder
607
            if (array_key_exists($seed, $seeds)) {
608
                $this->executeSeed($environment, $seeds[$seed]);
609
            } else {
610
                throw new InvalidArgumentException(sprintf('The seed class "%s" does not exist', $seed));
611 393
            }
612
        }
613 393
    }
614
615
    /**
616
     * Sets the environments.
617
     *
618
     * @param \Phinx\Migration\Manager\Environment[] $environments Environments
619
     * @return $this
620
     */
621
    public function setEnvironments($environments = [])
622 400
    {
623
        $this->environments = $environments;
624 400
625 400
        return $this;
626
    }
627
628
    /**
629
     * Gets the manager class for the given environment.
630
     *
631
     * @param string $name Environment Name
632
     * @throws \InvalidArgumentException
633 395
     * @return \Phinx\Migration\Manager\Environment
634
     */
635 395
    public function getEnvironment($name)
636
    {
637
        if (isset($this->environments[$name])) {
638
            return $this->environments[$name];
639
        }
640
641
        // check the environment exists
642
        if (!$this->getConfig()->hasEnvironment($name)) {
643
            throw new InvalidArgumentException(sprintf(
644 379
                'The environment "%s" does not exist',
645
                $name
646 379
            ));
647 379
        }
648
649
        // create an environment instance and cache it
650
        $envOptions = $this->getConfig()->getEnvironment($name);
651
        $envOptions['version_order'] = $this->getConfig()->getVersionOrder();
652
        $envOptions['data_domain'] = $this->getConfig()->getDataDomain();
653
654
        $environment = new Environment($name, $envOptions);
655
        $this->environments[$name] = $environment;
656
        $environment->setInput($this->getInput());
657 388
        $environment->setOutput($this->getOutput());
658
659 388
        return $environment;
660 388
    }
661
662
    /**
663 388
     * Sets the user defined PSR-11 container
664
     *
665 388
     * @param \Psr\Container\ContainerInterface $container Container
666
     * @return void
667 388
     */
668 387
    public function setContainer(ContainerInterface $container)
669 387
    {
670
        $this->container = $container;
671 387
    }
672 3
673
    /**
674
     * Sets the console input.
675 387
     *
676 387
     * @param \Symfony\Component\Console\Input\InputInterface $input Input
677
     * @return $this
678
     */
679 387
    public function setInput(InputInterface $input)
680
    {
681 387
        $this->input = $input;
682 2
683 2
        return $this;
684 2
    }
685 2
686 2
    /**
687
     * Gets the console input.
688
     *
689 387
     * @return \Symfony\Component\Console\Input\InputInterface
690
     */
691
    public function getInput()
692
    {
693 387
        return $this->input;
694 387
    }
695 2
696 2
    /**
697 2
     * Sets the console output.
698
     *
699 2
     * @param \Symfony\Component\Console\Output\OutputInterface $output Output
700
     * @return $this
701
     */
702
    public function setOutput(OutputInterface $output)
703 385
    {
704
        $this->output = $output;
705 385
706 2
        return $this;
707 2
    }
708 2
709
    /**
710 2
     * Gets the console output.
711
     *
712
     * @return \Symfony\Component\Console\Output\OutputInterface
713 383
     */
714 383
    public function getOutput()
715 384
    {
716
        return $this->output;
717 379
    }
718 379
719 379
    /**
720
     * Sets the database migrations.
721 379
     *
722
     * @param \Phinx\Migration\AbstractMigration[] $migrations Migrations
723
     * @return $this
724
     */
725
    public function setMigrations(array $migrations)
726
    {
727
        $this->migrations = $migrations;
728
729 388
        return $this;
730
    }
731 388
732 388
    /**
733 388
     * Gets an array of the database migrations, indexed by migration name (aka creation time) and sorted in ascending
734
     * order
735 388
     *
736 388
     * @param string $environment Environment
737 388
     * @throws \InvalidArgumentException
738 388
     * @return \Phinx\Migration\AbstractMigration[]
739 388
     */
740 388
    public function getMigrations($environment)
741
    {
742 388
        if ($this->migrations === null) {
743
            $phpFiles = $this->getMigrationFiles();
744
745
            if ($this->getOutput()->getVerbosity() >= OutputInterface::VERBOSITY_DEBUG) {
746
                $this->getOutput()->writeln('Migration file');
747
                $this->getOutput()->writeln(
748
                    array_map(
749
                        function ($phpFile) {
750
                            return "    <info>{$phpFile}</info>";
751 11
                        },
752
                        $phpFiles
753 11
                    )
754 11
                );
755
            }
756
757
            // filter the files to only get the ones that match our naming scheme
758
            $fileNames = [];
759
            /** @var \Phinx\Migration\AbstractMigration[] $versions */
760
            $versions = [];
761
762
            foreach ($phpFiles as $filePath) {
763 11
                if (Util::isValidMigrationFileName(basename($filePath))) {
764
                    if ($this->getOutput()->getVerbosity() >= OutputInterface::VERBOSITY_DEBUG) {
765 11
                        $this->getOutput()->writeln("Valid migration file <info>{$filePath}</info>.");
766 11
                    }
767
768
                    $version = Util::getVersionFromFileName(basename($filePath));
769 11
770
                    if (isset($versions[$version])) {
771 11
                        throw new InvalidArgumentException(sprintf('Duplicate migration - "%s" has the same version as "%s"', $filePath, $versions[$version]->getVersion()));
772
                    }
773 11
774 11
                    $config = $this->getConfig();
775 11
                    $namespace = $config instanceof NamespaceAwareInterface ? $config->getMigrationNamespaceByPath(dirname($filePath)) : null;
776 11
777
                    // convert the filename to a class name
778
                    $class = ($namespace === null ? '' : $namespace . '\\') . Util::mapFileNameToClassName(basename($filePath));
779 11
780 11
                    if (isset($fileNames[$class])) {
781
                        throw new InvalidArgumentException(sprintf(
782
                            'Migration "%s" has the same name as "%s"',
783
                            basename($filePath),
784 11
                            $fileNames[$class]
785 11
                        ));
786
                    }
787
788
                    $fileNames[$class] = basename($filePath);
789
790
                    if ($this->getOutput()->getVerbosity() >= OutputInterface::VERBOSITY_DEBUG) {
791
                        $this->getOutput()->writeln("Loading class <info>$class</info> from <info>$filePath</info>.");
792
                    }
793
794 11
                    // load the migration file
795
                    $orig_display_errors_setting = ini_get('display_errors');
796 11
                    ini_set('display_errors', 'On');
797
                    /** @noinspection PhpIncludeInspection */
798
                    require_once $filePath;
799
                    ini_set('display_errors', $orig_display_errors_setting);
800
                    if (!class_exists($class)) {
801
                        throw new InvalidArgumentException(sprintf(
802
                            'Could not find class "%s" in file "%s"',
803
                            $class,
804 11
                            $filePath
805 11
                        ));
806 11
                    }
807
808 11
                    if ($this->getOutput()->getVerbosity() >= OutputInterface::VERBOSITY_DEBUG) {
809 11
                        $this->getOutput()->writeln("Running <info>$class</info>.");
810 11
                    }
811
812 11
                    // instantiate it
813
                    $migration = new $class($environment, $version, $this->getInput(), $this->getOutput());
814
815
                    if (!($migration instanceof AbstractMigration)) {
816
                        throw new InvalidArgumentException(sprintf(
817
                            'The class "%s" in file "%s" must extend \Phinx\Migration\AbstractMigration',
818
                            $class,
819
                            $filePath
820 11
                        ));
821
                    }
822 11
823 11
                    $versions[$version] = $migration;
824 11
                } else {
825
                    if ($this->getOutput()->getVerbosity() >= OutputInterface::VERBOSITY_DEBUG) {
826 11
                        $this->getOutput()->writeln("Invalid migration file <error>{$filePath}</error>.");
827 11
                    }
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
    protected function getMigrationFiles()
844 400
    {
845 400
        return Util::getFiles($this->getConfig()->getMigrationPaths());
846
    }
847
848
    /**
849
     * Sets the database seeders.
850
     *
851
     * @param \Phinx\Seed\AbstractSeed[] $seeds Seeders
852
     * @return $this
853 399
     */
854
    public function setSeeds(array $seeds)
855 399
    {
856
        $this->seeds = $seeds;
857
858
        return $this;
859
    }
860
861
    /**
862
     * Get seed dependencies instances from seed dependency array
863
     *
864
     * @param \Phinx\Seed\AbstractSeed $seed Seed
865 2
     * @return \Phinx\Seed\AbstractSeed[]
866
     */
867 2
    protected function getSeedDependenciesInstances(AbstractSeed $seed)
868 2
    {
869 2
        $dependenciesInstances = [];
870 2
        $dependencies = $seed->getDependencies();
871
        if (!empty($dependencies)) {
872 2
            foreach ($dependencies as $dependency) {
873
                foreach ($this->seeds as $seed) {
874
                    if (get_class($seed) === $dependency) {
875
                        $dependenciesInstances[get_class($seed)] = $seed;
876 2
                    }
877 1
                }
878 1
            }
879 1
        }
880
881 2
        return $dependenciesInstances;
882 1
    }
883 1
884
    /**
885 1
     * Order seeds by dependencies
886 1
     *
887
     * @param \Phinx\Seed\AbstractSeed[] $seeds Seeds
888
     * @return \Phinx\Seed\AbstractSeed[]
889 1
     */
890
    protected function orderSeedsByDependencies(array $seeds)
891 1
    {
892
        $orderedSeeds = [];
893 1
        foreach ($seeds as $seed) {
894 1
            $key = get_class($seed);
895 1
            $dependencies = $this->getSeedDependenciesInstances($seed);
896 1
            if (!empty($dependencies)) {
897 1
                $orderedSeeds[$key] = $seed;
898 1
                $orderedSeeds = array_merge($this->orderSeedsByDependencies($dependencies), $orderedSeeds);
899
            } else {
900
                $orderedSeeds[$key] = $seed;
901
            }
902
        }
903
904
        return $orderedSeeds;
905
    }
906 1
907
    /**
908 1
     * Gets an array of database seeders.
909 1
     *
910 1
     * @throws \InvalidArgumentException
911 1
     * @return \Phinx\Seed\AbstractSeed[]
912 1
     */
913
    public function getSeeds()
914
    {
915
        if ($this->seeds === null) {
916
            $phpFiles = $this->getSeedFiles();
917
918
            // filter the files to only get the ones that match our naming scheme
919
            $fileNames = [];
920
            /** @var \Phinx\Seed\AbstractSeed[] $seeds */
921
            $seeds = [];
922
923
            foreach ($phpFiles as $filePath) {
924
                if (Util::isValidSeedFileName(basename($filePath))) {
925
                    $config = $this->getConfig();
926
                    $namespace = $config instanceof NamespaceAwareInterface ? $config->getSeedNamespaceByPath(dirname($filePath)) : null;
927
928
                    // convert the filename to a class name
929
                    $class = ($namespace === null ? '' : $namespace . '\\') . pathinfo($filePath, PATHINFO_FILENAME);
0 ignored issues
show
Bug introduced by
Are you sure pathinfo($filePath, Phin...tion\PATHINFO_FILENAME) of type array|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

929
                    $class = ($namespace === null ? '' : $namespace . '\\') . /** @scrutinizer ignore-type */ pathinfo($filePath, PATHINFO_FILENAME);
Loading history...
930
                    $fileNames[$class] = basename($filePath);
931
932
                    // load the seed file
933
                    /** @noinspection PhpIncludeInspection */
934
                    require_once $filePath;
935
                    if (!class_exists($class)) {
936
                        throw new InvalidArgumentException(sprintf(
937
                            'Could not find class "%s" in file "%s"',
938
                            $class,
939
                            $filePath
940
                        ));
941
                    }
942
943
                    // instantiate it
944
                    /** @var \Phinx\Seed\AbstractSeed $seed */
945
                    if ($this->container !== null) {
946
                        $seed = $this->container->get($class);
947
                    } else {
948
                        $seed = new $class();
949
                    }
950
                    $input = $this->getInput();
951
                    if ($input !== null) {
952
                        $seed->setInput($input);
953
                    }
954
                    $output = $this->getOutput();
955
                    if ($output !== null) {
956
                        $seed->setOutput($output);
957
                    }
958
959
                    if (!($seed instanceof AbstractSeed)) {
960
                        throw new InvalidArgumentException(sprintf(
961
                            'The class "%s" in file "%s" must extend \Phinx\Seed\AbstractSeed',
962
                            $class,
963
                            $filePath
964
                        ));
965
                    }
966
967
                    $seeds[$class] = $seed;
968
                }
969
            }
970
971
            ksort($seeds);
972
            $this->setSeeds($seeds);
973
        }
974
975
        $this->seeds = $this->orderSeedsByDependencies($this->seeds);
976
977
        return $this->seeds;
978
    }
979
980
    /**
981
     * Returns a list of seed files found in the provided seed paths.
982
     *
983
     * @return string[]
984
     */
985
    protected function getSeedFiles()
986
    {
987
        return Util::getFiles($this->getConfig()->getSeedPaths());
988
    }
989
990
    /**
991
     * Sets the config.
992
     *
993
     * @param \Phinx\Config\ConfigInterface $config Configuration Object
994
     * @return $this
995
     */
996
    public function setConfig(ConfigInterface $config)
997
    {
998
        $this->config = $config;
999
1000
        return $this;
1001
    }
1002
1003
    /**
1004
     * Gets the config.
1005
     *
1006
     * @return \Phinx\Config\ConfigInterface
1007
     */
1008
    public function getConfig()
1009
    {
1010
        return $this->config;
1011
    }
1012
1013
    /**
1014
     * Toggles the breakpoint for a specific version.
1015
     *
1016
     * @param string $environment Environment name
1017
     * @param int|null $version Version
1018
     * @return void
1019
     */
1020
    public function toggleBreakpoint($environment, $version)
1021
    {
1022
        $this->markBreakpoint($environment, $version, self::BREAKPOINT_TOGGLE);
1023
    }
1024
1025
    /**
1026
     * Updates the breakpoint for a specific version.
1027
     *
1028
     * @param string $environment The required environment
1029
     * @param int|null $version The version of the target migration
1030
     * @param int $mark The state of the breakpoint as defined by self::BREAKPOINT_xxxx constants.
1031
     * @return void
1032
     */
1033
    protected function markBreakpoint($environment, $version, $mark)
1034
    {
1035
        $migrations = $this->getMigrations($environment);
1036
        $this->getMigrations($environment);
1037
        $env = $this->getEnvironment($environment);
1038
        $versions = $env->getVersionLog();
1039
1040
        if (empty($versions) || empty($migrations)) {
1041
            return;
1042
        }
1043
1044
        if ($version === null) {
1045
            $lastVersion = end($versions);
1046
            $version = $lastVersion['version'];
1047
        }
1048
1049
        if ($version != 0 && (!isset($versions[$version]) || !isset($migrations[$version]))) {
1050
            $this->output->writeln(sprintf(
1051
                '<comment>warning</comment> %s is not a valid version',
1052
                $version
1053
            ));
1054
1055
            return;
1056
        }
1057
1058
        switch ($mark) {
1059
            case self::BREAKPOINT_TOGGLE:
1060
                $env->getAdapter()->toggleBreakpoint($migrations[$version]);
1061
                break;
1062
            case self::BREAKPOINT_SET:
1063
                if ($versions[$version]['breakpoint'] == 0) {
1064
                    $env->getAdapter()->setBreakpoint($migrations[$version]);
1065
                }
1066
                break;
1067
            case self::BREAKPOINT_UNSET:
1068
                if ($versions[$version]['breakpoint'] == 1) {
1069
                    $env->getAdapter()->unsetBreakpoint($migrations[$version]);
1070
                }
1071
                break;
1072
        }
1073
1074
        $versions = $env->getVersionLog();
1075
1076
        $this->getOutput()->writeln(
1077
            ' Breakpoint ' . ($versions[$version]['breakpoint'] ? 'set' : 'cleared') .
1078
            ' for <info>' . $version . '</info>' .
1079
            ' <comment>' . $migrations[$version]->getName() . '</comment>'
1080
        );
1081
    }
1082
1083
    /**
1084
     * Remove all breakpoints
1085
     *
1086
     * @param string $environment The required environment
1087
     * @return void
1088
     */
1089
    public function removeBreakpoints($environment)
1090
    {
1091
        $this->getOutput()->writeln(sprintf(
1092
            ' %d breakpoints cleared.',
1093
            $this->getEnvironment($environment)->getAdapter()->resetAllBreakpoints()
1094
        ));
1095
    }
1096
1097
    /**
1098
     * Set the breakpoint for a specific version.
1099
     *
1100
     * @param string $environment The required environment
1101
     * @param int|null $version The version of the target migration
1102
     * @return void
1103
     */
1104
    public function setBreakpoint($environment, $version)
1105
    {
1106
        $this->markBreakpoint($environment, $version, self::BREAKPOINT_SET);
1107
    }
1108
1109
    /**
1110
     * Unset the breakpoint for a specific version.
1111
     *
1112
     * @param string $environment The required environment
1113
     * @param int|null $version The version of the target migration
1114
     * @return void
1115
     */
1116
    public function unsetBreakpoint($environment, $version)
1117
    {
1118
        $this->markBreakpoint($environment, $version, self::BREAKPOINT_UNSET);
1119
    }
1120
}
1121