Completed
Push — master ( 757f06...15dbca )
by mark
02:08 queued 12s
created

Manager::markBreakpoint()   C

Complexity

Conditions 13
Paths 15

Size

Total Lines 49

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 182

Importance

Changes 0
Metric Value
dl 0
loc 49
ccs 0
cts 0
cp 0
rs 6.6166
c 0
b 0
f 0
cc 13
nc 15
nop 3
crap 182

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
     *
84
     * @throws \RuntimeException
85
     *
86
     * @return array array indicating if there are any missing or down migrations
87
     */
88
    public function printStatus($environment, $format = null)
89 432
    {
90
        $output = $this->getOutput();
91 432
        $hasDownMigration = false;
92 432
        $hasMissingMigration = false;
93 432
        $migrations = $this->getMigrations($environment);
94 432
        $migrationCount = 0;
95
        $missingCount = 0;
96
        $pendingMigrationCount = 0;
97
        $finalMigrations = [];
98
        $verbosity = $output->getVerbosity();
99
        if ($format === 'json') {
100
            $output->setVerbosity(OutputInterface::VERBOSITY_QUIET);
101
        }
102
        if (count($migrations)) {
103 22
            // rewrite using Symfony Table Helper as we already have this library
104
            // included and it will fix formatting issues (e.g drawing the lines)
105 22
            $output->writeln('');
106 22
107 22
            switch ($this->getConfig()->getVersionOrder()) {
108 22
                case Config::VERSION_ORDER_CREATION_TIME:
109 22
                    $migrationIdAndStartedHeader = "<info>[Migration ID]</info>  Started            ";
110 22
                    break;
111
                case Config::VERSION_ORDER_EXECUTION_TIME:
112
                    $migrationIdAndStartedHeader = "Migration ID    <info>[Started          ]</info>";
113 21
                    break;
114
                default:
115 21
                    throw new RuntimeException('Invalid version_order configuration option');
116 21
            }
117 19
118 19
            $output->writeln(" Status  $migrationIdAndStartedHeader  Finished             Migration Name ");
119 2
            $output->writeln('----------------------------------------------------------------------------------');
120 1
121 1
            $env = $this->getEnvironment($environment);
122 1
            $versions = $env->getVersionLog();
123 1
124 21
            $maxNameLength = $versions ? max(array_map(function ($version) {
125
                return strlen($version['migration_name']);
126 20
            }, $versions)) : 0;
127 20
128
            $missingVersions = array_diff_key($versions, $migrations);
129 20
            $missingCount = count($missingVersions);
130 20
131
            $hasMissingMigration = !empty($missingVersions);
132
133 17
            // get the migrations sorted in the same way as the versions
134 20
            /** @var \Phinx\Migration\AbstractMigration[] $sortedMigrations */
135
            $sortedMigrations = [];
136 20
137
            foreach ($versions as $versionCreationTime => $version) {
138 20
                if (isset($migrations[$versionCreationTime])) {
139
                    array_push($sortedMigrations, $migrations[$versionCreationTime]);
140
                    unset($migrations[$versionCreationTime]);
141 20
                }
142
            }
143 20
144 17
            if (empty($sortedMigrations) && !empty($missingVersions)) {
145 13
                // this means we have no up migrations, so we write all the missing versions already so they show up
146 13
                // before any possible down migration
147 13
                foreach ($missingVersions as $missingVersionCreationTime => $missingVersion) {
148 20
                    $this->printMissingVersion($missingVersion, $maxNameLength);
149
150 20
                    unset($missingVersions[$missingVersionCreationTime]);
151
                }
152
            }
153 4
154 4
            // any migration left in the migrations (ie. not unset when sorting the migrations by the version order) is
155
            // a migration that is down, so we add them to the end of the sorted migrations list
156 4
            if (!empty($migrations)) {
157 4
                $sortedMigrations = array_merge($sortedMigrations, $migrations);
158 4
            }
159
160
            $migrationCount = count($sortedMigrations);
161
            foreach ($sortedMigrations as $migration) {
162 20
                $version = array_key_exists($migration->getVersion(), $versions) ? $versions[$migration->getVersion()] : false;
163 13
                if ($version) {
164 13
                    // check if there are missing versions before this version
165
                    foreach ($missingVersions as $missingVersionCreationTime => $missingVersion) {
166 20
                        if ($this->getConfig()->isVersionOrderCreationTime()) {
167 20
                            if ($missingVersion['version'] > $version['version']) {
168 20
                                break;
169
                            }
170 13
                        } else {
171 6
                            if ($missingVersion['start_time'] > $version['start_time']) {
172 6
                                break;
173 4
                            } elseif (
174
                                $missingVersion['start_time'] == $version['start_time'] &&
175 3
                                $missingVersion['version'] > $version['version']
176
                            ) {
177
                                break;
178
                            }
179
                        }
180
181
                        $this->printMissingVersion($missingVersion, $maxNameLength);
182
183
                        unset($missingVersions[$missingVersionCreationTime]);
184 3
                    }
185
186 3
                    $status = '     <info>up</info> ';
187 13
                } else {
188
                    $pendingMigrationCount++;
189 13
                    $hasDownMigration = true;
190 13
                    $status = '   <error>down</error> ';
191 13
                }
192 13
                $maxNameLength = max($maxNameLength, strlen($migration->getName()));
193
194 20
                $output->writeln(sprintf(
195
                    '%s %14.0f  %19s  %19s  <comment>%s</comment>',
196 20
                    $status,
197 20
                    $migration->getVersion(),
198 20
                    ($version ? $version['start_time'] : ''),
199 20
                    ($version ? $version['end_time'] : ''),
200 20
                    $migration->getName()
201 20
                ));
202 20
203 20
                if ($version && $version['breakpoint']) {
204
                    $output->writeln('         <error>BREAKPOINT SET</error>');
205 20
                }
206 1
207 1
                $finalMigrations[] = ['migration_status' => trim(strip_tags($status)), 'migration_id' => sprintf('%14.0f', $migration->getVersion()), 'migration_name' => $migration->getName()];
208
                unset($versions[$migration->getVersion()]);
209 20
            }
210 20
211 20
            // and finally add any possibly-remaining missing migrations
212
            foreach ($missingVersions as $missingVersionCreationTime => $missingVersion) {
213
                $this->printMissingVersion($missingVersion, $maxNameLength);
214 20
215 4
                unset($missingVersions[$missingVersionCreationTime]);
216
            }
217 4
        } else {
218 20
            // there are no migrations
219 20
            $output->writeln('');
220
            $output->writeln('There are no available migrations. Try creating one using the <info>create</info> command.');
221 1
        }
222 1
223
        // write an empty line
224
        $output->writeln('');
225
226 21
        if ($format !== null) {
227 21
            switch ($format) {
228
                case AbstractCommand::FORMAT_JSON:
229
                    $output->setVerbosity($verbosity);
230
                    $output->writeln(json_encode(
231
                        [
232
                            'pending_count' => $pendingMigrationCount,
233
                            'missing_count' => $missingCount,
234
                            'total_count' => $migrationCount + $missingCount,
235
                            'migrations' => $finalMigrations,
236
                        ]
237
                    ));
238
                    break;
239
                default:
240
                    $output->writeln('<info>Unsupported format: ' . $format . '</info>');
241
            }
242 21
        }
243 10
244 11
        return [
245 6
            'hasMissingMigration' => $hasMissingMigration,
246
            'hasDownMigration' => $hasDownMigration,
247 5
        ];
248
    }
249
250
    /**
251
     * Print Missing Version
252
     *
253
     * @param array $version The missing version to print (in the format returned by Environment.getVersionLog).
254
     * @param int $maxNameLength The maximum migration name length.
255
     *
256
     * @return void
257 10
     */
258
    protected function printMissingVersion($version, $maxNameLength)
259 10
    {
260 10
        $this->getOutput()->writeln(sprintf(
261 10
            '     <error>up</error>  %14.0f  %19s  %19s  <comment>%s</comment>  <error>** MISSING **</error>',
262 10
            $version['version'],
263 10
            $version['start_time'],
264 10
            $version['end_time'],
265 10
            str_pad($version['migration_name'], $maxNameLength, ' ')
266
        ));
267 10
268 1
        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...
269 1
            $this->getOutput()->writeln('         <error>BREAKPOINT SET</error>');
270 10
        }
271
    }
272
273
    /**
274
     * Migrate to the version of the database on a given date.
275
     *
276
     * @param string $environment Environment
277
     * @param \DateTime $dateTime Date to migrate to
278
     * @param bool $fake flag that if true, we just record running the migration, but not actually do the
279
     *                               migration
280 4
     *
281
     * @return void
282 4
     */
283 4
    public function migrateToDateTime($environment, DateTime $dateTime, $fake = false)
284
    {
285
        $versions = array_keys($this->getMigrations($environment));
286 4
        $dateString = $dateTime->format('YmdHis');
287 4
288
        $outstandingMigrations = array_filter($versions, function ($version) use ($dateString) {
289 4
            return $version <= $dateString;
290 3
        });
291 3
292 3
        if (count($outstandingMigrations) > 0) {
293 3
            $migration = max($outstandingMigrations);
294 4
            $this->getOutput()->writeln('Migrating to version ' . $migration);
295
            $this->migrate($environment, $migration, $fake);
296
        }
297
    }
298
299
    /**
300
     * Migrate an environment to the specified version.
301
     *
302
     * @param string $environment Environment
303 8
     * @param int|null $version version to migrate to
304
     * @param bool $fake flag that if true, we just record running the migration, but not actually do the migration
305 8
     *
306 8
     * @return void
307 8
     */
308 8
    public function migrate($environment, $version = null, $fake = false)
309
    {
310 8
        $migrations = $this->getMigrations($environment);
311
        $env = $this->getEnvironment($environment);
312
        $versions = $env->getVersions();
313
        $current = $env->getCurrentVersion();
314 8
315 5
        if (empty($versions) && empty($migrations)) {
316 5
            return;
317 3
        }
318
319
        if ($version === null) {
320
            $version = max(array_merge($versions, array_keys($migrations)));
321
        } else {
322
            if ($version != 0 && !isset($migrations[$version])) {
323
                $this->output->writeln(sprintf(
324
                    '<comment>warning</comment> %s is not a valid version',
325
                    $version
326
                ));
327 8
328
                return;
329 8
            }
330
        }
331
332
        // are we migrating up or down?
333
        $direction = $version > $current ? MigrationInterface::UP : MigrationInterface::DOWN;
334
335
        if ($direction === MigrationInterface::DOWN) {
336
            // run downs first
337
            krsort($migrations);
338
            foreach ($migrations as $migration) {
339
                if ($migration->getVersion() <= $version) {
340
                    break;
341
                }
342
343 8
                if (in_array($migration->getVersion(), $versions)) {
344 8
                    $this->executeMigration($environment, $migration, MigrationInterface::DOWN, $fake);
345 8
                }
346 2
            }
347
        }
348
349 8
        ksort($migrations);
350 5
        foreach ($migrations as $migration) {
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::UP, $fake);
357
            }
358
        }
359
    }
360
361
    /**
362
     * Execute a migration against the specified environment.
363 119
     *
364
     * @param string $name Environment Name
365 119
     * @param \Phinx\Migration\MigrationInterface $migration Migration
366 119
     * @param string $direction Direction
367
     * @param bool $fake flag that if true, we just record running the migration, but not actually do the migration
368 119
     *
369 119
     * @return void
370 119
     */
371
    public function executeMigration($name, MigrationInterface $migration, $direction = MigrationInterface::UP, $fake = false)
372
    {
373 119
        $this->getOutput()->writeln('');
374 119
        $this->getOutput()->writeln(
375 119
            ' ==' .
376
            ' <info>' . $migration->getVersion() . ' ' . $migration->getName() . ':</info>' .
377 119
            ' <comment>' . ($direction === MigrationInterface::UP ? 'migrating' : 'reverting') . '</comment>'
378
        );
379 119
        $migration->preFlightCheck($direction);
380 119
381 119
        // Execute the migration and log the time elapsed.
382 119
        $start = microtime(true);
383 119
        $this->getEnvironment($name)->executeMigration($migration, $direction, $fake);
384
        $end = microtime(true);
385
386
        $migration->postFlightCheck($direction);
387
        $this->getOutput()->writeln(
388
            ' ==' .
389
            ' <info>' . $migration->getVersion() . ' ' . $migration->getName() . ':</info>' .
390
            ' <comment>' . ($direction === MigrationInterface::UP ? 'migrated' : 'reverted') .
391
            ' ' . sprintf('%.4fs', $end - $start) . '</comment>'
392 6
        );
393
    }
394 6
395 6
    /**
396
     * Execute a seeder against the specified environment.
397 6
     *
398 6
     * @param string $name Environment Name
399 6
     * @param \Phinx\Seed\SeedInterface $seed Seed
400
     *
401
     * @return void
402 6
     */
403 6
    public function executeSeed($name, SeedInterface $seed)
404 6
    {
405
        $this->getOutput()->writeln('');
406 6
        $this->getOutput()->writeln(
407
            ' ==' .
408 6
            ' <info>' . $seed->getName() . ':</info>' .
409 6
            ' <comment>seeding</comment>'
410 6
        );
411 6
412 6
        // Execute the seeder and log the time elapsed.
413
        $start = microtime(true);
414
        $this->getEnvironment($name)->executeSeed($seed);
415
        $end = microtime(true);
416
417
        $this->getOutput()->writeln(
418
            ' ==' .
419
            ' <info>' . $seed->getName() . ':</info>' .
420
            ' <comment>seeded' .
421
            ' ' . sprintf('%.4fs', $end - $start) . '</comment>'
422
        );
423 349
    }
424
425
    /**
426 349
     * Rollback an environment to the specified version.
427
     *
428
     * @param string $environment Environment
429 349
     * @param int|string|null $target Target
430
     * @param bool $force Force
431
     * @param bool $targetMustMatchVersion Target must match version
432 349
     * @param bool $fake Flag that if true, we just record running the migration, but not actually do the migration
433
     *
434 349
     * @return void
435
     */
436
    public function rollback($environment, $target = null, $force = false, $targetMustMatchVersion = true, $fake = false)
437 349
    {
438 48
        // note that the migrations are indexed by name (aka creation time) in ascending order
439 48
        $migrations = $this->getMigrations($environment);
440 48
441
        // note that the version log are also indexed by name with the proper ascending order according to the version order
442 349
        $executedVersions = $this->getEnvironment($environment)->getVersionLog();
443 349
444 349
        // get a list of migrations sorted in the opposite way of the executed versions
445
        $sortedMigrations = [];
446
447 47
        foreach ($executedVersions as $versionCreationTime => &$executedVersion) {
448
            // if we have a date (ie. the target must not match a version) and we are sorting by execution time, we
449 349
            // convert the version start time so we can compare directly with the target date
450
            if (!$this->getConfig()->isVersionOrderCreationTime() && !$targetMustMatchVersion) {
451 349
                $dateTime = DateTime::createFromFormat('Y-m-d H:i:s', $executedVersion['start_time']);
452 23
                $executedVersion['start_time'] = $dateTime->format('YmdHis');
453 349
            }
454
455 20
            if (isset($migrations[$versionCreationTime])) {
456 20
                array_unshift($sortedMigrations, $migrations[$versionCreationTime]);
457 20
            } else {
458 20
                // this means the version is missing so we unset it so that we don't consider it when rolling back
459
                // migrations (or choosing the last up version as target)
460
                unset($executedVersions[$versionCreationTime]);
461 20
            }
462 20
        }
463 20
464
        if ($target === 'all' || $target === '0') {
465
            $target = 0;
466
        } elseif (!is_numeric($target) && $target !== null) { // try to find a target version based on name
467 20
            // search through the migrations using the name
468
            $migrationNames = array_map(function ($item) {
469
                return $item['migration_name'];
470 349
            }, $executedVersions);
471 349
            $found = array_search($target, $migrationNames, true);
472 53
473 53
            // check on was found
474
            if ($found !== false) {
475
                $target = (string)$found;
476
            } else {
477 296
                $this->getOutput()->writeln("<error>No migration found with name ($target)</error>");
478
479 94
                return;
480 94
            }
481 94
        }
482
483
        // Check we have at least 1 migration to revert
484 296
        $executedVersionCreationTimes = array_keys($executedVersions);
485 46
        if (empty($executedVersionCreationTimes) || $target == end($executedVersionCreationTimes)) {
486 46
            $this->getOutput()->writeln('<error>No migrations to rollback</error>');
487
488
            return;
489
        }
490 250
491
        // If no target was supplied, revert the last migration
492 250
        if ($target === null) {
493 250
            // Get the migration before the last run migration
494 70
            $prev = count($executedVersionCreationTimes) - 2;
495
            $target = $prev >= 0 ? $executedVersionCreationTimes[$prev] : 0;
496
        }
497 250
498 250
        // If the target must match a version, check the target version exists
499
        if ($targetMustMatchVersion && $target !== 0 && !isset($migrations[$target])) {
500 250
            $this->getOutput()->writeln("<error>Target version ($target) not found</error>");
501 96
502 96
            return;
503 42
        }
504
505 68
        // Rollback all versions until we find the wanted rollback target
506
        $rollbacked = false;
507 222
508 121
        foreach ($sortedMigrations as $migration) {
509 121
            if ($targetMustMatchVersion && $migration->getVersion() == $target) {
510
                break;
511 117
            }
512 117
513 117
            if (in_array($migration->getVersion(), $executedVersionCreationTimes)) {
514 250
                $executedVersion = $executedVersions[$migration->getVersion()];
515
516 250
                if (!$targetMustMatchVersion) {
517 133
                    if (
518 133
                        ($this->getConfig()->isVersionOrderCreationTime() && $executedVersion['version'] <= $target) ||
519 250
                        (!$this->getConfig()->isVersionOrderCreationTime() && $executedVersion['start_time'] <= $target)
520
                    ) {
521
                        break;
522
                    }
523
                }
524
525
                if ($executedVersion['breakpoint'] != 0 && !$force) {
526
                    $this->getOutput()->writeln('<error>Breakpoint reached. Further rollbacks inhibited.</error>');
527
                    break;
528 9
                }
529
                $this->executeMigration($environment, $migration, MigrationInterface::DOWN, $fake);
530 9
                $rollbacked = true;
531
            }
532 9
        }
533
534 3
        if (!$rollbacked) {
535 3
            $this->getOutput()->writeln('<error>No migrations to rollback</error>');
536 3
        }
537 3
    }
538 3
539 3
    /**
540
     * Run database seeders against an environment.
541 6
     *
542 3
     * @param string $environment Environment
543 3
     * @param string|null $seed Seeder
544 3
     *
545
     * @throws \InvalidArgumentException
546
     *
547 6
     * @return void
548
     */
549
    public function seed($environment, $seed = null)
550
    {
551
        $seeds = $this->getSeeds();
552
553
        if ($seed === null) {
554
            // run all seeders
555 381
            foreach ($seeds as $seeder) {
556
                if (array_key_exists($seeder->getName(), $seeds)) {
557 381
                    $this->executeSeed($environment, $seeder);
558 381
                }
559
            }
560
        } else {
561
            // run only one seeder
562
            if (array_key_exists($seed, $seeds)) {
563
                $this->executeSeed($environment, $seeds[$seed]);
564
            } else {
565
                throw new InvalidArgumentException(sprintf('The seed class "%s" does not exist', $seed));
566
            }
567
        }
568 382
    }
569
570 382
    /**
571 380
     * Sets the environments.
572
     *
573
     * @param \Phinx\Migration\Manager\Environment[] $environments Environments
574
     *
575 7
     * @return $this
576 1
     */
577 1
    public function setEnvironments($environments = [])
578
    {
579 1
        $this->environments = $environments;
580
581
        return $this;
582
    }
583 6
584 6
    /**
585
     * Gets the manager class for the given environment.
586 6
     *
587 6
     * @param string $name Environment Name
588 6
     *
589 6
     * @throws \InvalidArgumentException
590
     *
591 6
     * @return \Phinx\Migration\Manager\Environment
592
     */
593
    public function getEnvironment($name)
594
    {
595
        if (isset($this->environments[$name])) {
596
            return $this->environments[$name];
597
        }
598
599
        // check the environment exists
600 400
        if (!$this->getConfig()->hasEnvironment($name)) {
601
            throw new InvalidArgumentException(sprintf(
602 400
                'The environment "%s" does not exist',
603 400
                $name
604
            ));
605
        }
606
607
        // create an environment instance and cache it
608
        $envOptions = $this->getConfig()->getEnvironment($name);
609
        $envOptions['version_order'] = $this->getConfig()->getVersionOrder();
610
        $envOptions['data_domain'] = $this->getConfig()->getDataDomain();
611 393
612
        $environment = new Environment($name, $envOptions);
613 393
        $this->environments[$name] = $environment;
614
        $environment->setInput($this->getInput());
615
        $environment->setOutput($this->getOutput());
616
617
        return $environment;
618
    }
619
620
    /**
621
     * Sets the user defined PSR-11 container
622 400
     *
623
     * @param \Psr\Container\ContainerInterface $container Container
624 400
     */
625 400
    public function setContainer(ContainerInterface $container)
626
    {
627
        $this->container = $container;
628
    }
629
630
    /**
631
     * Sets the console input.
632
     *
633 395
     * @param \Symfony\Component\Console\Input\InputInterface $input Input
634
     *
635 395
     * @return $this
636
     */
637
    public function setInput(InputInterface $input)
638
    {
639
        $this->input = $input;
640
641
        return $this;
642
    }
643
644 379
    /**
645
     * Gets the console input.
646 379
     *
647 379
     * @return \Symfony\Component\Console\Input\InputInterface
648
     */
649
    public function getInput()
650
    {
651
        return $this->input;
652
    }
653
654
    /**
655
     * Sets the console output.
656
     *
657 388
     * @param \Symfony\Component\Console\Output\OutputInterface $output Output
658
     *
659 388
     * @return $this
660 388
     */
661
    public function setOutput(OutputInterface $output)
662
    {
663 388
        $this->output = $output;
664
665 388
        return $this;
666
    }
667 388
668 387
    /**
669 387
     * Gets the console output.
670
     *
671 387
     * @return \Symfony\Component\Console\Output\OutputInterface
672 3
     */
673
    public function getOutput()
674
    {
675 387
        return $this->output;
676 387
    }
677
678
    /**
679 387
     * Sets the database migrations.
680
     *
681 387
     * @param \Phinx\Migration\AbstractMigration[] $migrations Migrations
682 2
     *
683 2
     * @return $this
684 2
     */
685 2
    public function setMigrations(array $migrations)
686 2
    {
687
        $this->migrations = $migrations;
688
689 387
        return $this;
690
    }
691
692
    /**
693 387
     * Gets an array of the database migrations, indexed by migration name (aka creation time) and sorted in ascending
694 387
     * order
695 2
     *
696 2
     * @param string $environment Environment
697 2
     *
698
     * @throws \InvalidArgumentException
699 2
     *
700
     * @return \Phinx\Migration\AbstractMigration[]
701
     */
702
    public function getMigrations($environment)
703 385
    {
704
        if ($this->migrations === null) {
705 385
            $phpFiles = $this->getMigrationFiles();
706 2
707 2
            if ($this->getOutput()->getVerbosity() >= OutputInterface::VERBOSITY_DEBUG) {
708 2
                $this->getOutput()->writeln('Migration file');
709
                $this->getOutput()->writeln(
710 2
                    array_map(
0 ignored issues
show
Documentation introduced by
array_map(function ($php...</info>"; }, $phpFiles) is of type array, but the function expects a string|object<Symfony\Co...onsole\Output\iterable>.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
711
                        function ($phpFile) {
712
                            return "    <info>{$phpFile}</info>";
713 383
                        },
714 383
                        $phpFiles
715 384
                    )
716
                );
717 379
            }
718 379
719 379
            // filter the files to only get the ones that match our naming scheme
720
            $fileNames = [];
721 379
            /** @var \Phinx\Migration\AbstractMigration[] $versions */
722
            $versions = [];
723
724
            foreach ($phpFiles as $filePath) {
725
                if (Util::isValidMigrationFileName(basename($filePath))) {
726
                    if ($this->getOutput()->getVerbosity() >= OutputInterface::VERBOSITY_DEBUG) {
727
                        $this->getOutput()->writeln("Valid migration file <info>{$filePath}</info>.");
728
                    }
729 388
730
                    $version = Util::getVersionFromFileName(basename($filePath));
731 388
732 388
                    if (isset($versions[$version])) {
733 388
                        throw new InvalidArgumentException(sprintf('Duplicate migration - "%s" has the same version as "%s"', $filePath, $versions[$version]->getVersion()));
734
                    }
735 388
736 388
                    $config = $this->getConfig();
737 388
                    $namespace = $config instanceof NamespaceAwareInterface ? $config->getMigrationNamespaceByPath(dirname($filePath)) : null;
738 388
739 388
                    // convert the filename to a class name
740 388
                    $class = ($namespace === null ? '' : $namespace . '\\') . Util::mapFileNameToClassName(basename($filePath));
741
742 388
                    if (isset($fileNames[$class])) {
743
                        throw new InvalidArgumentException(sprintf(
744
                            'Migration "%s" has the same name as "%s"',
745
                            basename($filePath),
746
                            $fileNames[$class]
747
                        ));
748
                    }
749
750
                    $fileNames[$class] = basename($filePath);
751 11
752
                    if ($this->getOutput()->getVerbosity() >= OutputInterface::VERBOSITY_DEBUG) {
753 11
                        $this->getOutput()->writeln("Loading class <info>$class</info> from <info>$filePath</info>.");
754 11
                    }
755
756
                    // load the migration file
757
                    $orig_display_errors_setting = ini_get('display_errors');
758
                    ini_set('display_errors', 'On');
759
                    /** @noinspection PhpIncludeInspection */
760
                    require_once $filePath;
761
                    ini_set('display_errors', $orig_display_errors_setting);
762
                    if (!class_exists($class)) {
763 11
                        throw new InvalidArgumentException(sprintf(
764
                            'Could not find class "%s" in file "%s"',
765 11
                            $class,
766 11
                            $filePath
767
                        ));
768
                    }
769 11
770
                    if ($this->getOutput()->getVerbosity() >= OutputInterface::VERBOSITY_DEBUG) {
771 11
                        $this->getOutput()->writeln("Running <info>$class</info>.");
772
                    }
773 11
774 11
                    // instantiate it
775 11
                    $migration = new $class($environment, $version, $this->getInput(), $this->getOutput());
776 11
777
                    if (!($migration instanceof AbstractMigration)) {
778
                        throw new InvalidArgumentException(sprintf(
779 11
                            'The class "%s" in file "%s" must extend \Phinx\Migration\AbstractMigration',
780 11
                            $class,
781
                            $filePath
782
                        ));
783
                    }
784 11
785 11
                    $versions[$version] = $migration;
786
                } else {
787
                    if ($this->getOutput()->getVerbosity() >= OutputInterface::VERBOSITY_DEBUG) {
788
                        $this->getOutput()->writeln("Invalid migration file <error>{$filePath}</error>.");
789
                    }
790
                }
791
            }
792
793
            ksort($versions);
794 11
            $this->setMigrations($versions);
795
        }
796 11
797
        return $this->migrations;
798
    }
799
800
    /**
801
     * Returns a list of migration files found in the provided migration paths.
802
     *
803
     * @return string[]
804 11
     */
805 11
    protected function getMigrationFiles()
806 11
    {
807
        return Util::getFiles($this->getConfig()->getMigrationPaths());
808 11
    }
809 11
810 11
    /**
811
     * Sets the database seeders.
812 11
     *
813
     * @param \Phinx\Seed\AbstractSeed[] $seeds Seeders
814
     *
815
     * @return $this
816
     */
817
    public function setSeeds(array $seeds)
818
    {
819
        $this->seeds = $seeds;
820 11
821
        return $this;
822 11
    }
823 11
824 11
    /**
825
     * Get seed dependencies instances from seed dependency array
826 11
     *
827 11
     * @param \Phinx\Seed\AbstractSeed $seed Seed
828 11
     *
829 11
     * @return \Phinx\Seed\AbstractSeed[]
830 11
     */
831 11
    protected function getSeedDependenciesInstances(AbstractSeed $seed)
832
    {
833 11
        $dependenciesInstances = [];
834
        $dependencies = $seed->getDependencies();
835
        if (!empty($dependencies)) {
836
            foreach ($dependencies as $dependency) {
837
                foreach ($this->seeds as $seed) {
0 ignored issues
show
Bug introduced by
The expression $this->seeds of type array<integer,object<Phi...eed\AbstractSeed>>|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
838
                    if (get_class($seed) === $dependency) {
839
                        $dependenciesInstances[get_class($seed)] = $seed;
840
                    }
841
                }
842 400
            }
843
        }
844 400
845 400
        return $dependenciesInstances;
846
    }
847
848
    /**
849
     * Order seeds by dependencies
850
     *
851
     * @param \Phinx\Seed\AbstractSeed[] $seeds Seeds
852
     *
853 399
     * @return \Phinx\Seed\AbstractSeed[]
854
     */
855 399
    protected function orderSeedsByDependencies(array $seeds)
856
    {
857
        $orderedSeeds = [];
858
        foreach ($seeds as $seed) {
859
            $key = get_class($seed);
860
            $dependencies = $this->getSeedDependenciesInstances($seed);
861
            if (!empty($dependencies)) {
862
                $orderedSeeds[$key] = $seed;
863
                $orderedSeeds = array_merge($this->orderSeedsByDependencies($dependencies), $orderedSeeds);
864
            } else {
865 2
                $orderedSeeds[$key] = $seed;
866
            }
867 2
        }
868 2
869 2
        return $orderedSeeds;
870 2
    }
871
872 2
    /**
873
     * Gets an array of database seeders.
874
     *
875
     * @throws \InvalidArgumentException
876 2
     *
877 1
     * @return \Phinx\Seed\AbstractSeed[]
878 1
     */
879 1
    public function getSeeds()
880
    {
881 2
        if ($this->seeds === null) {
882 1
            $phpFiles = $this->getSeedFiles();
883 1
884
            // filter the files to only get the ones that match our naming scheme
885 1
            $fileNames = [];
886 1
            /** @var \Phinx\Seed\AbstractSeed[] $seeds */
887
            $seeds = [];
888
889 1
            foreach ($phpFiles as $filePath) {
890
                if (Util::isValidSeedFileName(basename($filePath))) {
891 1
                    $config = $this->getConfig();
892
                    $namespace = $config instanceof NamespaceAwareInterface ? $config->getSeedNamespaceByPath(dirname($filePath)) : null;
893 1
894 1
                    // convert the filename to a class name
895 1
                    $class = ($namespace === null ? '' : $namespace . '\\') . pathinfo($filePath, PATHINFO_FILENAME);
896 1
                    $fileNames[$class] = basename($filePath);
897 1
898 1
                    // load the seed file
899
                    /** @noinspection PhpIncludeInspection */
900
                    require_once $filePath;
901
                    if (!class_exists($class)) {
902
                        throw new InvalidArgumentException(sprintf(
903
                            'Could not find class "%s" in file "%s"',
904
                            $class,
905
                            $filePath
906 1
                        ));
907
                    }
908 1
909 1
                    // instantiate it
910 1
                    /** @var \Phinx\Seed\AbstractSeed $seed */
911 1
                    if ($this->container !== null) {
912 1
                        $seed = $this->container->get($class);
913
                    } else {
914
                        $seed = new $class();
915
                    }
916
                    $input = $this->getInput();
917
                    if ($input !== null) {
918
                        $seed->setInput($input);
919
                    }
920
                    $output = $this->getOutput();
921
                    if ($output !== null) {
922
                        $seed->setOutput($output);
923
                    }
924
925
                    if (!($seed instanceof AbstractSeed)) {
926
                        throw new InvalidArgumentException(sprintf(
927
                            'The class "%s" in file "%s" must extend \Phinx\Seed\AbstractSeed',
928
                            $class,
929
                            $filePath
930
                        ));
931
                    }
932
933
                    $seeds[$class] = $seed;
934
                }
935
            }
936
937
            ksort($seeds);
938
            $this->setSeeds($seeds);
939
        }
940
941
        $this->seeds = $this->orderSeedsByDependencies($this->seeds);
0 ignored issues
show
Bug introduced by
It seems like $this->seeds can also be of type null; however, Phinx\Migration\Manager:...erSeedsByDependencies() does only seem to accept array<integer,object<Phinx\Seed\AbstractSeed>>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
942
943
        return $this->seeds;
944
    }
945
946
    /**
947
     * Returns a list of seed files found in the provided seed paths.
948
     *
949
     * @return string[]
950
     */
951
    protected function getSeedFiles()
952
    {
953
        return Util::getFiles($this->getConfig()->getSeedPaths());
954
    }
955
956
    /**
957
     * Sets the config.
958
     *
959
     * @param \Phinx\Config\ConfigInterface $config Configuration Object
960
     *
961
     * @return $this
962
     */
963
    public function setConfig(ConfigInterface $config)
964
    {
965
        $this->config = $config;
966
967
        return $this;
968
    }
969
970
    /**
971
     * Gets the config.
972
     *
973
     * @return \Phinx\Config\ConfigInterface
974
     */
975
    public function getConfig()
976
    {
977
        return $this->config;
978
    }
979
980
    /**
981
     * Toggles the breakpoint for a specific version.
982
     *
983
     * @param string $environment Environment name
984
     * @param int|null $version Version
985
     *
986
     * @return void
987
     */
988
    public function toggleBreakpoint($environment, $version)
989
    {
990
        $this->markBreakpoint($environment, $version, self::BREAKPOINT_TOGGLE);
991
    }
992
993
    /**
994
     * Updates the breakpoint for a specific version.
995
     *
996
     * @param string $environment The required environment
997
     * @param int|null $version The version of the target migration
998
     * @param int $mark The state of the breakpoint as defined by self::BREAKPOINT_xxxx constants.
999
     *
1000
     * @return void
1001
     */
1002
    protected function markBreakpoint($environment, $version, $mark)
1003
    {
1004
        $migrations = $this->getMigrations($environment);
1005
        $this->getMigrations($environment);
1006
        $env = $this->getEnvironment($environment);
1007
        $versions = $env->getVersionLog();
1008
1009
        if (empty($versions) || empty($migrations)) {
1010
            return;
1011
        }
1012
1013
        if ($version === null) {
1014
            $lastVersion = end($versions);
1015
            $version = $lastVersion['version'];
1016
        }
1017
1018
        if ($version != 0 && (!isset($versions[$version]) || !isset($migrations[$version]))) {
1019
            $this->output->writeln(sprintf(
1020
                '<comment>warning</comment> %s is not a valid version',
1021
                $version
1022
            ));
1023
1024
            return;
1025
        }
1026
1027
        switch ($mark) {
1028
            case self::BREAKPOINT_TOGGLE:
1029
                $env->getAdapter()->toggleBreakpoint($migrations[$version]);
1030
                break;
1031
            case self::BREAKPOINT_SET:
1032
                if ($versions[$version]['breakpoint'] == 0) {
1033
                    $env->getAdapter()->setBreakpoint($migrations[$version]);
1034
                }
1035
                break;
1036
            case self::BREAKPOINT_UNSET:
1037
                if ($versions[$version]['breakpoint'] == 1) {
1038
                    $env->getAdapter()->unsetBreakpoint($migrations[$version]);
1039
                }
1040
                break;
1041
        }
1042
1043
        $versions = $env->getVersionLog();
1044
1045
        $this->getOutput()->writeln(
1046
            ' Breakpoint ' . ($versions[$version]['breakpoint'] ? 'set' : 'cleared') .
1047
            ' for <info>' . $version . '</info>' .
1048
            ' <comment>' . $migrations[$version]->getName() . '</comment>'
1049
        );
1050
    }
1051
1052
    /**
1053
     * Remove all breakpoints
1054
     *
1055
     * @param string $environment The required environment
1056
     *
1057
     * @return void
1058
     */
1059
    public function removeBreakpoints($environment)
1060
    {
1061
        $this->getOutput()->writeln(sprintf(
1062
            ' %d breakpoints cleared.',
1063
            $this->getEnvironment($environment)->getAdapter()->resetAllBreakpoints()
1064
        ));
1065
    }
1066
1067
    /**
1068
     * Set the breakpoint for a specific version.
1069
     *
1070
     * @param string $environment The required environment
1071
     * @param int|null $version The version of the target migration
1072
     *
1073
     * @return void
1074
     */
1075
    public function setBreakpoint($environment, $version)
1076
    {
1077
        $this->markBreakpoint($environment, $version, self::BREAKPOINT_SET);
1078
    }
1079
1080
    /**
1081
     * Unset the breakpoint for a specific version.
1082
     *
1083
     * @param string $environment The required environment
1084
     * @param int|null $version The version of the target migration
1085
     *
1086
     * @return void
1087
     */
1088
    public function unsetBreakpoint($environment, $version)
1089
    {
1090
        $this->markBreakpoint($environment, $version, self::BREAKPOINT_UNSET);
1091
    }
1092
}
1093