Completed
Pull Request — master (#665)
by Jonathan
02:09
created

MigrateCommand   A

Complexity

Total Complexity 17

Size/Duplication

Total Lines 210
Duplicated Lines 0 %

Test Coverage

Coverage 98.06%

Importance

Changes 0
Metric Value
dl 0
loc 210
ccs 101
cts 103
cp 0.9806
rs 10
c 0
b 0
f 0
wmc 17

5 Methods

Rating   Name   Duplication   Size   Complexity  
B execute() 0 59 6
A createMigration() 0 3 1
A configure() 0 74 1
B checkExecutedUnavailableMigrations() 0 30 4
B getVersionNameFromAlias() 0 34 5
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\Migrations\Tools\Console\Command;
6
7
use Doctrine\Migrations\Migration;
8
use Symfony\Component\Console\Formatter\OutputFormatter;
9
use Symfony\Component\Console\Input\InputArgument;
10
use Symfony\Component\Console\Input\InputInterface;
11
use Symfony\Component\Console\Input\InputOption;
12
use Symfony\Component\Console\Output\OutputInterface;
13
use function count;
14
use function getcwd;
15
use function sprintf;
16
use function substr;
17
18
class MigrateCommand extends AbstractCommand
19
{
20 16
    protected function configure() : void
21
    {
22
        $this
23 16
            ->setName('migrations:migrate')
24 16
            ->setDescription(
25 16
                'Execute a migration to a specified version or the latest available version.'
26
            )
27 16
            ->addArgument(
28 16
                'version',
29 16
                InputArgument::OPTIONAL,
30 16
                'The version number (YYYYMMDDHHMMSS) or alias (first, prev, next, latest) to migrate to.',
31 16
                'latest'
32
            )
33 16
            ->addOption(
34 16
                'write-sql',
35 16
                null,
36 16
                InputOption::VALUE_OPTIONAL,
37 16
                'The path to output the migration SQL file instead of executing it. Default to current working directory.'
38
            )
39 16
            ->addOption(
40 16
                'dry-run',
41 16
                null,
42 16
                InputOption::VALUE_NONE,
43 16
                'Execute the migration as a dry run.'
44
            )
45 16
            ->addOption(
46 16
                'query-time',
47 16
                null,
48 16
                InputOption::VALUE_NONE,
49 16
                'Time all the queries individually.'
50
            )
51 16
            ->addOption(
52 16
                'allow-no-migration',
53 16
                null,
54 16
                InputOption::VALUE_NONE,
55 16
                'Don\'t throw an exception if no migration is available (CI).'
56
            )
57 16
            ->setHelp(<<<EOT
58 16
The <info>%command.name%</info> command executes a migration to a specified version or the latest available version:
59
60
    <info>%command.full_name%</info>
61
62
You can optionally manually specify the version you wish to migrate to:
63
64
    <info>%command.full_name% YYYYMMDDHHMMSS</info>
65
66
You can specify the version you wish to migrate to using an alias:
67
68
    <info>%command.full_name% prev</info>
69
    <info>These alias are defined : first, latest, prev, current and next</info>
70
71
You can specify the version you wish to migrate to using an number against the current version:
72
73
    <info>%command.full_name% current+3</info>
74
75
You can also execute the migration as a <comment>--dry-run</comment>:
76
77
    <info>%command.full_name% YYYYMMDDHHMMSS --dry-run</info>
78
79
You can output the would be executed SQL statements to a file with <comment>--write-sql</comment>:
80
81
    <info>%command.full_name% YYYYMMDDHHMMSS --write-sql</info>
82
83
Or you can also execute the migration without a warning message which you need to interact with:
84
85
    <info>%command.full_name% --no-interaction</info>
86
87
You can also time all the different queries if you wanna know which one is taking so long:
88
89
    <info>%command.full_name% --query-time</info>
90
EOT
91
        );
92
93 16
        parent::configure();
94 16
    }
95
96 9
    public function execute(InputInterface $input, OutputInterface $output) : int
97
    {
98 9
        $migration = $this->createMigration();
99
100 9
        $this->outputHeader($output);
101
102 9
        $version          = (string) $input->getArgument('version');
103 9
        $path             = $input->getOption('write-sql');
104 9
        $allowNoMigration = (bool) $input->getOption('allow-no-migration');
105 9
        $timeAllqueries   = (bool) $input->getOption('query-time');
106 9
        $dryRun           = (bool) $input->getOption('dry-run');
107
108 9
        $this->configuration->setIsDryRun($dryRun);
109
110 9
        $version = $this->getVersionNameFromAlias(
111 9
            $version,
112 9
            $output
113
        );
114
115 9
        if ($version === '') {
116 5
            return 1;
117
        }
118
119 4
        if ($this->checkExecutedUnavailableMigrations($input, $output) === 1) {
120 1
            return 1;
121
        }
122
123 3
        if ($path !== null) {
124 1
            $path = $path === true ? getcwd() : $path;
125 1
            $migration->writeSqlFile($path, $version);
126
127 1
            return 0;
128
        }
129
130 2
        $cancelled = false;
131
132 2
        $migration->setNoMigrationException($allowNoMigration);
133
134 2
        $result = $migration->migrate(
0 ignored issues
show
Unused Code introduced by
The assignment to $result is dead and can be removed.
Loading history...
135 2
            $version,
136 2
            $dryRun,
137 2
            $timeAllqueries,
138
            function () use ($input, $output, &$cancelled) {
139 1
                $question = 'WARNING! You are about to execute a database migration that could result in schema changes and data loss. Are you sure you wish to continue? (y/n)';
140
141 1
                $canContinue = $this->canExecute($question, $input, $output);
142 1
                $cancelled   = ! $canContinue;
143
144 1
                return $canContinue;
145 2
            }
146
        );
147
148 2
        if ($cancelled) {
149
            $output->writeln('<error>Migration cancelled!</error>');
150
151
            return 1;
152
        }
153
154 2
        return 0;
155
    }
156
157 9
    protected function createMigration() : Migration
158
    {
159 9
        return $this->dependencyFactory->getMigration();
160
    }
161
162 4
    private function checkExecutedUnavailableMigrations(
163
        InputInterface $input,
164
        OutputInterface $output
165
    ) : int {
166 4
        $executedUnavailableMigrations = $this->migrationRepository->getExecutedUnavailableMigrations();
167
168 4
        if (! empty($executedUnavailableMigrations)) {
169 3
            $output->writeln(sprintf(
170 3
                '<error>WARNING! You have %s previously executed migrations in the database that are not registered migrations.</error>',
171 3
                count($executedUnavailableMigrations)
172
            ));
173
174 3
            foreach ($executedUnavailableMigrations as $executedUnavailableMigration) {
175 3
                $output->writeln(sprintf(
176 3
                    '    <comment>>></comment> %s (<comment>%s</comment>)',
177 3
                    $this->configuration->getDateTime($executedUnavailableMigration),
178 3
                    $executedUnavailableMigration
179
                ));
180
            }
181
182 3
            $question = 'Are you sure you wish to continue? (y/n)';
183
184 3
            if (! $this->canExecute($question, $input, $output)) {
185 1
                $output->writeln('<error>Migration cancelled!</error>');
186
187 1
                return 1;
188
            }
189
        }
190
191 3
        return 0;
192
    }
193
194 9
    private function getVersionNameFromAlias(
195
        string $versionAlias,
196
        OutputInterface $output
197
    ) : string {
198 9
        $version = $this->configuration->resolveVersionAlias($versionAlias);
199
200 9
        if ($version === null) {
201 4
            if ($versionAlias === 'prev') {
202 1
                $output->writeln('<error>Already at first version.</error>');
203
204 1
                return '';
205
            }
206
207 3
            if ($versionAlias === 'next') {
208 1
                $output->writeln('<error>Already at latest version.</error>');
209
210 1
                return '';
211
            }
212
213 2
            if (substr($versionAlias, 0, 7) === 'current') {
214 1
                $output->writeln('<error>The delta couldn\'t be reached.</error>');
215
216 1
                return '';
217
            }
218
219 1
            $output->writeln(sprintf(
220 1
                '<error>Unknown version: %s</error>',
221 1
                OutputFormatter::escape($versionAlias)
222
            ));
223
224 1
            return '';
225
        }
226
227 5
        return $version;
228
    }
229
}
230