Failed Conditions
Pull Request — master (#978)
by Asmir
03:19
created

MigrateCommand::getVersionNameFromAlias()   B

Complexity

Conditions 7
Paths 5

Size

Total Lines 29
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 7.7656

Importance

Changes 0
Metric Value
cc 7
eloc 15
nc 5
nop 2
dl 0
loc 29
ccs 12
cts 16
cp 0.75
crap 7.7656
rs 8.8333
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\Migrations\Tools\Console\Command;
6
7
use Doctrine\Migrations\Exception\NoMigrationsFoundWithCriteria;
0 ignored issues
show
introduced by
Type Doctrine\Migrations\Exception\NoMigrationsFoundWithCriteria is not used in this file.
Loading history...
8
use Doctrine\Migrations\Exception\NoMigrationsToExecute;
9
use Doctrine\Migrations\Exception\UnknownMigrationVersion;
10
use Doctrine\Migrations\Metadata\ExecutedMigrationsList;
11
use Symfony\Component\Console\Formatter\OutputFormatter;
12
use Symfony\Component\Console\Input\InputArgument;
13
use Symfony\Component\Console\Input\InputInterface;
14
use Symfony\Component\Console\Input\InputOption;
15
use Symfony\Component\Console\Output\OutputInterface;
16
use function count;
17
use function getcwd;
18
use function is_string;
19
use function is_writable;
20
use function sprintf;
21
use function substr;
22
23
/**
24
 * The MigrateCommand class is responsible for executing a migration from the current version to another
25
 * version up or down. It will calculate all the migration versions that need to be executed and execute them.
26
 */
27
final class MigrateCommand extends DoctrineCommand
28
{
29
    /** @var string */
30
    protected static $defaultName = 'migrations:migrate';
31
32 14
    protected function configure() : void
33
    {
34
        $this
35 14
            ->setAliases(['migrate'])
36 14
            ->setDescription(
37 14
                'Execute a migration to a specified version or the latest available version.'
38
            )
39 14
            ->addArgument(
40 14
                'version',
41 14
                InputArgument::OPTIONAL,
42 14
                'The version FQCN or alias (first, prev, next, latest) to migrate to.',
43 14
                'latest'
44
            )
45 14
            ->addOption(
46 14
                'write-sql',
47 14
                null,
48 14
                InputOption::VALUE_OPTIONAL,
49 14
                'The path to output the migration SQL file instead of executing it. Defaults to current working directory.',
50 14
                false
51
            )
52 14
            ->addOption(
53 14
                'dry-run',
54 14
                null,
55 14
                InputOption::VALUE_NONE,
56 14
                'Execute the migration as a dry run.'
57
            )
58 14
            ->addOption(
59 14
                'query-time',
60 14
                null,
61 14
                InputOption::VALUE_NONE,
62 14
                'Time all the queries individually.'
63
            )
64 14
            ->addOption(
65 14
                'allow-no-migration',
66 14
                null,
67 14
                InputOption::VALUE_NONE,
68 14
                'Do not throw an exception if no migration is available.'
69
            )
70 14
            ->addOption(
71 14
                'all-or-nothing',
72 14
                null,
73 14
                InputOption::VALUE_OPTIONAL,
74 14
                'Wrap the entire migration in a transaction.',
75 14
                false
76
            )
77 14
            ->setHelp(<<<EOT
78 14
The <info>%command.name%</info> command executes a migration to a specified version or the latest available version:
79
80
    <info>%command.full_name%</info>
81
82
You can optionally manually specify the version you wish to migrate to:
83
84
    <info>%command.full_name% FQCN</info>
85
86
You can specify the version you wish to migrate to using an alias:
87
88
    <info>%command.full_name% prev</info>
89
    <info>These alias are defined : first, latest, prev, current and next</info>
90
91
You can specify the version you wish to migrate to using an number against the current version:
92
93
    <info>%command.full_name% current+3</info>
94
95
You can also execute the migration as a <comment>--dry-run</comment>:
96
97
    <info>%command.full_name% FQCN --dry-run</info>
98
99
You can output the would be executed SQL statements to a file with <comment>--write-sql</comment>:
100
101
    <info>%command.full_name% FQCN --write-sql</info>
102
103
Or you can also execute the migration without a warning message which you need to interact with:
104
105
    <info>%command.full_name% --no-interaction</info>
106
107
You can also time all the different queries if you wanna know which one is taking so long:
108
109
    <info>%command.full_name% --query-time</info>
110
111
Use the --all-or-nothing option to wrap the entire migration in a transaction.
112
EOT
113
            );
114
115 14
        parent::configure();
116 14
    }
117
118 14
    protected function execute(InputInterface $input, OutputInterface $output) : int
119
    {
120 14
        $migratorConfigurationFactory = $this->getDependencyFactory()->getConsoleInputMigratorConfigurationFactory();
121 14
        $migratorConfiguration        = $migratorConfigurationFactory->getMigratorConfiguration($input);
122
123 14
        $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?';
124 14
        if (! $migratorConfiguration->isDryRun() && ! $this->canExecute($question, $input)) {
125 3
            $this->io->error('Migration cancelled!');
126
127 3
            return 3;
128
        }
129
130 11
        $this->getDependencyFactory()->getMetadataStorage()->ensureInitialized();
131
132 11
        $allowNoMigration = $input->getOption('allow-no-migration');
133 11
        $versionAlias     = $input->getArgument('version');
134 11
        $path             = $input->getOption('write-sql');
135
136
        try {
137 11
            $version = $this->getDependencyFactory()->getVersionAliasResolver()->resolveVersionAlias($versionAlias);
0 ignored issues
show
Bug introduced by
It seems like $versionAlias can also be of type null and string[]; however, parameter $alias of Doctrine\Migrations\Vers...::resolveVersionAlias() 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

137
            $version = $this->getDependencyFactory()->getVersionAliasResolver()->resolveVersionAlias(/** @scrutinizer ignore-type */ $versionAlias);
Loading history...
138 1
        } catch (UnknownMigrationVersion|NoMigrationsToExecute $e) {
139 1
            $this->getVersionNameFromAlias($versionAlias);
0 ignored issues
show
Bug introduced by
It seems like $versionAlias can also be of type null and string[]; however, parameter $versionAlias of Doctrine\Migrations\Tool...tVersionNameFromAlias() 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

139
            $this->getVersionNameFromAlias(/** @scrutinizer ignore-type */ $versionAlias);
Loading history...
140
141 1
            return 1;
142
        }
143
144 10
        $planCalculator                = $this->getDependencyFactory()->getMigrationPlanCalculator();
145 10
        $statusCalculator              = $this->getDependencyFactory()->getMigrationStatusCalculator();
146 10
        $executedUnavailableMigrations = $statusCalculator->getExecutedUnavailableMigrations();
147
148 10
        if ($this->checkExecutedUnavailableMigrations($executedUnavailableMigrations, $input) === false) {
149 1
            return 3;
150
        }
151
152 9
        $plan = $planCalculator->getPlanUntilVersion($version);
153
154 9
        if (count($plan) === 0) {
155 3
            $this->getVersionNameFromAlias($versionAlias, $allowNoMigration);
156
157 3
            return $allowNoMigration ? 0 : 1;
158
        }
159
160 6
        $migrator = $this->getDependencyFactory()->getMigrator();
161 6
        if ($migratorConfiguration->isDryRun()) {
162 2
            $sql = $migrator->migrate($plan, $migratorConfiguration);
163
164 2
            $path = is_string($path) ? $path : getcwd();
165
166 2
            if (! is_string($path) || ! is_writable($path)) {
0 ignored issues
show
introduced by
The condition is_string($path) is always true.
Loading history...
167
                $this->io->error('Path not writeable!');
168
169
                return 1;
170
            }
171
172 2
            $writer = $this->getDependencyFactory()->getQueryWriter();
173 2
            $writer->write($path, $plan->getDirection(), $sql);
174
175 2
            return 0;
176
        }
177
178 4
        $this->getDependencyFactory()->getLogger()->notice(
179 4
            'Migrating' . ($migratorConfiguration->isDryRun() ? ' (dry-run)' : '') . ' {direction} to {to}',
180
            [
181 4
                'direction' => $plan->getDirection(),
182 4
                'to' => (string) $version,
183
            ]
184
        );
185
186 4
        $migrator->migrate($plan, $migratorConfiguration);
187
188 4
        $this->io->newLine();
189
190 4
        return 0;
191
    }
192
193 10
    private function checkExecutedUnavailableMigrations(
194
        ExecutedMigrationsList $executedUnavailableMigrations,
195
        InputInterface $input
196
    ) : bool {
197 10
        if (count($executedUnavailableMigrations) !== 0) {
198 1
            $this->io->warning(sprintf(
199 1
                'You have %s previously executed migrations in the database that are not registered migrations.',
200 1
                count($executedUnavailableMigrations)
201
            ));
202
203 1
            foreach ($executedUnavailableMigrations->getItems() as $executedUnavailableMigration) {
204 1
                $this->io->text(sprintf(
205 1
                    '<comment>>></comment> %s (<comment>%s</comment>)',
206 1
                    $executedUnavailableMigration->getExecutedAt() !== null
207
                        ? $executedUnavailableMigration->getExecutedAt()->format('Y-m-d H:i:s')
208 1
                        : null,
209 1
                    $executedUnavailableMigration->getVersion()
210
                ));
211
            }
212
213 1
            $question = 'Are you sure you wish to continue?';
214
215 1
            if (! $this->canExecute($question, $input)) {
216 1
                $this->io->error('Migration cancelled!');
217
218 1
                return false;
219
            }
220
        }
221
222 9
        return true;
223
    }
224
225 4
    private function getVersionNameFromAlias(string $versionAlias, bool $allowNoMigration = false) : void
226
    {
227 4
        if ($versionAlias === 'first') {
228 1
            $this->io->error('Already at first version.');
229
230 1
            return;
231
        }
232
233 3
        if ($allowNoMigration && $versionAlias === 'latest') {
234 1
            $this->io->text('Already at latest version.');
235
236 1
            return;
237
        }
238
239 2
        if ($versionAlias === 'next' || $versionAlias === 'latest') {
240
            $this->io->error('Already at latest version.');
241
242
            return;
243
        }
244
245 2
        if (substr($versionAlias, 0, 7) === 'current') {
246
            $this->io->error('The delta couldn\'t be reached.');
247
248
            return;
249
        }
250
251 2
        $this->io->error(sprintf(
252 2
            'Unknown version: %s',
253 2
            OutputFormatter::escape($versionAlias)
254
        ));
255 2
    }
256
}
257