Passed
Pull Request — master (#683)
by Jonathan
02:21
created

MigrateCommand   A

Complexity

Total Complexity 21

Size/Duplication

Total Lines 231
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
dl 0
loc 231
ccs 114
cts 114
cp 1
rs 10
c 0
b 0
f 0
wmc 21

6 Methods

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