Issues (36)

src/Commands/CycleORM/DatabaseMigrateCommand.php (1 issue)

Labels
Severity
1
<?php declare(strict_types=1);
2
3
/*
4
 * This file is part of Biurad opensource projects.
5
 *
6
 * @copyright 2019 Biurad Group (https://biurad.com/)
7
 * @license   https://opensource.org/licenses/BSD-3-Clause License
8
 *
9
 * For the full copyright and license information, please view the LICENSE
10
 * file that was distributed with this source code.
11
 */
12
13
namespace Flange\Commands\CycleORM;
14
15
use Cycle\Migrations\Migrator;
16
use Cycle\Migrations\State;
17
use Symfony\Component\Console\Command\Command;
18
use Symfony\Component\Console\Helper\Table;
19
use Symfony\Component\Console\Input\ArrayInput;
20
use Symfony\Component\Console\Input\InputInterface;
21
use Symfony\Component\Console\Input\InputOption;
22
use Symfony\Component\Console\Output\OutputInterface;
23
use Symfony\Component\Console\Style\SymfonyStyle;
24
25
/**
26
 * Perform one or all outstanding migrations.
27
 *
28
 * @author Divine Niiquaye Ibok <[email protected]>
29
 */
30
class DatabaseMigrateCommand extends Command
31
{
32
    protected static $defaultName = 'cycle:database:migrate';
33
34
    public function __construct(private Migrator $migrator)
35
    {
36
        parent::__construct();
37
    }
38
39
    /**
40
     * {@inheritdoc}
41
     */
42
    protected function configure(): void
43
    {
44
        $this
45
            ->setDefinition([
46
                new InputOption('force', 's', InputOption::VALUE_NONE, 'Skip safe environment check'),
47
                new InputOption('one', 'o', InputOption::VALUE_NONE, 'Execute only one (first) migration'),
48
                new InputOption('init', 'i', InputOption::VALUE_NONE, 'Init Database migrations (create migrations table)'),
49
                new InputOption('status', null, InputOption::VALUE_NONE, 'Get list of all available migrations and their statuses'),
50
                new InputOption('rollback', null, InputOption::VALUE_NONE, 'Rollback multiple migrations (default) or one setting -o'),
51
                new InputOption('replay', null, InputOption::VALUE_NONE, 'Replay (down, up) one or multiple migrations'),
52
            ])
53
            ->setDescription('Perform one or all outstanding migrations')
54
        ;
55
    }
56
57
    /**
58
     * {@inheritdoc}
59
     */
60
    protected function execute(InputInterface $input, OutputInterface $output): int
61
    {
62
        $io = new SymfonyStyle($input, $output);
63
64
        if (!$this->migrator->isConfigured()) {
65
            $this->migrator->configure();
66
            $output->writeln('');
67
            $output->writeln('<info>Migrations table were successfully created</info>');
68
        }
69
70
        if ($input->getOption('init')) {
71
            goto exitCode;
72
        }
73
74
        if (!$this->verifyConfigured($output) || !$this->verifyEnvironment($input, $io)) {
75
            // Making sure migration is configured.
76
            return 1;
77
        }
78
79
        if ($input->getOption('status')) {
80
            if (empty($this->migrator->getMigrations())) {
81
                $output->writeln('<comment>No migrations were found.</comment>');
82
83
                return 1;
84
            }
85
86
            $table = new Table($output);
87
            $table = $table->setHeaders(['Migration', 'Created at', 'Executed at']);
88
89
            foreach ($this->migrator->getMigrations() as $migration) {
90
                $state = $migration->getState();
91
92
                $table->addRow([
93
                    $state->getName(),
94
                    $state->getTimeCreated()->format('Y-m-d H:i:s'),
95
                    State::STATUS_PENDING === $state->getStatus() ? '<fg=red>not executed yet</fg=red>' : '<info>'.$state->getTimeExecuted()->format('Y-m-d H:i:s').'</info>',
96
                ]);
97
            }
98
            $table->render();
99
100
            goto exitCode;
101
        }
102
103
        $found = false;
104
        $count = $input->getOption('one') ? 1 : \PHP_INT_MAX;
105
106
        if ($input->getOption('replay')) {
107
            $input->setOption('rollback', $replay = true);
108
        }
109
110
        while ($count > 0 && ($migration = $this->migrator->{$input->getOption('rollback') ? 'rollback' : 'run'}())) {
111
            $found = true;
112
            --$count;
113
114
            $io->newLine();
115
            $output->write(\sprintf(
116
                "<info>Migration <comment>%s</comment> was successfully %s.</info>\n",
117
                $migration->getState()->getName(),
118
                $input->getOption('rollback') ? 'rolled back' : 'executed'
119
            ));
120
        }
121
122
        if (!$found) {
123
            $io->error('No outstanding migrations were found');
124
125
            return 1;
126
        }
127
128
        if (isset($replay)) {
129
            $output->writeln('');
130
131
            if ($io->confirm('Do you want to execute <info>migrations</info> immediately?', false)) {
132
                $output->writeln('Executing outstanding migration(s)...');
133
134
                return $this->getApplication()->find($this->getName())->run(new ArrayInput(['--force' => true]), $output);
0 ignored issues
show
It seems like $this->getName() can also be of type null; however, parameter $name of Symfony\Component\Console\Application::find() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

134
                return $this->getApplication()->find(/** @scrutinizer ignore-type */ $this->getName())->run(new ArrayInput(['--force' => true]), $output);
Loading history...
135
            }
136
        }
137
138
        exitCode:
139
        return self::SUCCESS;
140
    }
141
142
    protected function verifyConfigured(OutputInterface $output): bool
143
    {
144
        if (!$this->migrator->isConfigured()) {
145
            $output->writeln('');
146
            $output->writeln("<fg=red>Migrations are not configured yet, run '<info>migrations:init</info>' first.</fg=red>");
147
148
            return false;
149
        }
150
151
        return true;
152
    }
153
154
    /**
155
     * Check if current environment is safe to run migration.
156
     */
157
    protected function verifyEnvironment(InputInterface $input, SymfonyStyle $io): bool
158
    {
159
        if ($input->getOption('force') || $this->migrator->getConfig()->isSafe()) {
160
            // Safe to run
161
            return true;
162
        }
163
164
        $io->newLine();
165
        $io->writeln('<fg=red>Confirmation is required to run migrations!</fg=red>');
166
167
        if (!$io->confirm('Would you like to continue?', false)) {
168
            $io->writeln('<comment>Cancelling operation...</comment>');
169
170
            return false;
171
        }
172
173
        return true;
174
    }
175
}
176