Failed Conditions
Pull Request — master (#979)
by Asmir
02:03
created

checkExecutedUnavailableMigrations()   A

Complexity

Conditions 5
Paths 3

Size

Total Lines 30
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 5.005

Importance

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

142
            $version = $this->getDependencyFactory()->getVersionAliasResolver()->resolveVersionAlias(/** @scrutinizer ignore-type */ $versionAlias);
Loading history...
143 2
        } catch (UnknownMigrationVersion|NoMigrationsFoundWithCriteria $e) {
144 2
            $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

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