Failed Conditions
Pull Request — master (#968)
by Edi
02:04
created

MigrateCommand::execute()   C

Complexity

Conditions 13
Paths 10

Size

Total Lines 81
Code Lines 45

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 40
CRAP Score 13.2317

Importance

Changes 0
Metric Value
cc 13
eloc 45
c 0
b 0
f 0
nc 10
nop 2
dl 0
loc 81
ccs 40
cts 45
cp 0.8889
crap 13.2317
rs 6.6166

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

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

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