Passed
Pull Request — master (#878)
by Asmir
02:42
created

MigrateCommand::execute()   C

Complexity

Conditions 12
Paths 10

Size

Total Lines 70
Code Lines 41

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 39
CRAP Score 12.0167

Importance

Changes 0
Metric Value
cc 12
eloc 41
c 0
b 0
f 0
nc 10
nop 2
dl 0
loc 70
ccs 39
cts 41
cp 0.9512
crap 12.0167
rs 6.9666

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\ExecutedMigrationsSet;
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
class MigrateCommand extends DoctrineCommand
27
{
28
    /** @var string */
29
    protected static $defaultName = 'migrations:migrate';
30
31 12
    protected function configure() : void
32
    {
33
        $this
34 12
            ->setAliases(['migrate'])
35 12
            ->setDescription(
36 12
                'Execute a migration to a specified version or the latest available version.'
37
            )
38 12
            ->addArgument(
39 12
                'version',
40 12
                InputArgument::OPTIONAL,
41 12
                'The version FQCN or alias (first, prev, next, latest) to migrate to.',
42 12
                'latest'
43
            )
44 12
            ->addOption(
45 12
                'write-sql',
46 12
                null,
47 12
                InputOption::VALUE_OPTIONAL,
48 12
                'The path to output the migration SQL file instead of executing it. Defaults to current working directory.',
49 12
                false
50
            )
51 12
            ->addOption(
52 12
                'dry-run',
53 12
                null,
54 12
                InputOption::VALUE_NONE,
55 12
                'Execute the migration as a dry run.'
56
            )
57 12
            ->addOption(
58 12
                'query-time',
59 12
                null,
60 12
                InputOption::VALUE_NONE,
61 12
                'Time all the queries individually.'
62
            )
63 12
            ->addOption(
64 12
                'allow-no-migration',
65 12
                null,
66 12
                InputOption::VALUE_NONE,
67 12
                'Do not throw an exception if no migration is available.'
68
            )
69 12
            ->addOption(
70 12
                'all-or-nothing',
71 12
                null,
72 12
                InputOption::VALUE_OPTIONAL,
73 12
                'Wrap the entire migration in a transaction.',
74 12
                false
75
            )
76 12
            ->setHelp(<<<EOT
77 12
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 12
        parent::configure();
115 12
    }
116
117 12
    public function execute(InputInterface $input, OutputInterface $output) : ?int
118
    {
119 12
        $this->outputHeader($output);
120 12
        $allowNoMigration = $input->getOption('allow-no-migration');
121 12
        $versionAlias     = $input->getArgument('version');
122 12
        $path             = $input->getOption('write-sql');
123
124
        try {
125 12
            $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

125
            $version = $this->getDependencyFactory()->getVersionAliasResolver()->resolveVersionAlias(/** @scrutinizer ignore-type */ $versionAlias);
Loading history...
126 2
        } catch (UnknownMigrationVersion|NoMigrationsFoundWithCriteria $e) {
127 2
            $this->getVersionNameFromAlias($versionAlias, $output);
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

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