Completed
Push — master ( 0e495c...26cd23 )
by Asmir
13s queued 11s
created

MigrateCommand::execute()   C

Complexity

Conditions 13
Paths 10

Size

Total Lines 89

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 46
CRAP Score 13.0387

Importance

Changes 0
Metric Value
dl 0
loc 89
ccs 46
cts 49
cp 0.9388
rs 5.5333
c 0
b 0
f 0
cc 13
nc 10
nop 2
crap 13.0387

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\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 in_array;
19
use function is_string;
20
use function is_writable;
21
use function sprintf;
22
use function strpos;
23
24
/**
25
 * The MigrateCommand class is responsible for executing a migration from the current version to another
26
 * version up or down. It will calculate all the migration versions that need to be executed and execute them.
27
 */
28
final class MigrateCommand extends DoctrineCommand
29
{
30
    /** @var string */
31
    protected static $defaultName = 'migrations:migrate';
32
33 24
    protected function configure() : void
34
    {
35
        $this
36 24
            ->setAliases(['migrate'])
37 24
            ->setDescription(
38 24
                'Execute a migration to a specified version or the latest available version.'
39
            )
40 24
            ->addArgument(
41 24
                'version',
42 24
                InputArgument::OPTIONAL,
43 24
                'The version FQCN or alias (first, prev, next, latest) to migrate to.',
44 24
                'latest'
45
            )
46 24
            ->addOption(
47 24
                'write-sql',
48 24
                null,
49 24
                InputOption::VALUE_OPTIONAL,
50 24
                'The path to output the migration SQL file. Defaults to current working directory.',
51 24
                false
52
            )
53 24
            ->addOption(
54 24
                'dry-run',
55 24
                null,
56 24
                InputOption::VALUE_NONE,
57 24
                'Execute the migration as a dry run.'
58
            )
59 24
            ->addOption(
60 24
                'query-time',
61 24
                null,
62 24
                InputOption::VALUE_NONE,
63 24
                'Time all the queries individually.'
64
            )
65 24
            ->addOption(
66 24
                'allow-no-migration',
67 24
                null,
68 24
                InputOption::VALUE_NONE,
69 24
                'Do not throw an exception if no migration is available.'
70
            )
71 24
            ->addOption(
72 24
                'all-or-nothing',
73 24
                null,
74 24
                InputOption::VALUE_OPTIONAL,
75 24
                'Wrap the entire migration in a transaction.',
76 24
                false
77
            )
78 24
            ->setHelp(<<<EOT
79 24
The <info>%command.name%</info> command executes a migration to a specified version or the latest available version:
80
81
    <info>%command.full_name%</info>
82
83
You can show more information about the process by increasing the verbosity level. To see the
84
executed queries, set the level to debug with <comment>-vv</comment>:
85
86
    <info>%command.full_name% -vv</info>
87
88
You can optionally manually specify the version you wish to migrate to:
89
90
    <info>%command.full_name% FQCN</info>
91
92
You can specify the version you wish to migrate to using an alias:
93
94
    <info>%command.full_name% prev</info>
95
    <info>These alias are defined : first, latest, prev, current and next</info>
96
97
You can specify the version you wish to migrate to using an number against the current version:
98
99
    <info>%command.full_name% current+3</info>
100
101
You can also execute the migration as a <comment>--dry-run</comment>:
102
103
    <info>%command.full_name% FQCN --dry-run</info>
104
105
You can output the prepared SQL statements to a file with <comment>--write-sql</comment>:
106
107
    <info>%command.full_name% FQCN --write-sql</info>
108
109
Or you can also execute the migration without a warning message which you need to interact with:
110
111
    <info>%command.full_name% --no-interaction</info>
112
113
You can also time all the different queries if you wanna know which one is taking so long:
114
115
    <info>%command.full_name% --query-time</info>
116 24
117 24
Use the --all-or-nothing option to wrap the entire migration in a transaction.
118
119 24
EOT
120
            );
121 24
122 24
        parent::configure();
123
    }
124 24
125 24
    protected function execute(InputInterface $input, OutputInterface $output) : int
126 3
    {
127
        $migratorConfigurationFactory = $this->getDependencyFactory()->getConsoleInputMigratorConfigurationFactory();
128 3
        $migratorConfiguration        = $migratorConfigurationFactory->getMigratorConfiguration($input);
129
130
        $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?';
131 21
        if (! $migratorConfiguration->isDryRun() && ! $this->canExecute($question, $input)) {
132
            $this->io->error('Migration cancelled!');
133 21
134 21
            return 3;
135
        }
136 21
137 21
        $this->getDependencyFactory()->getMetadataStorage()->ensureInitialized();
138
139
        $allowNoMigration = $input->getOption('allow-no-migration');
140
        $versionAlias     = $input->getArgument('version');
141
142
        $path = $input->getOption('write-sql') ?? getcwd();
143
        if (is_string($path) && ! is_writable($path)) {
144 21
            $this->io->error(sprintf('The path "%s" not writeable!', $path));
145 5
146 5
            return 1;
147
        }
148
149 16
        $migrationRepository = $this->getDependencyFactory()->getMigrationRepository();
150 16
        if (count($migrationRepository->getMigrations()) === 0) {
151 16
            $message = sprintf(
152
                'The version "%s" couldn\'t be reached, there are no registered migrations.',
153 16
                $versionAlias
154 1
            );
155
156
            if ($allowNoMigration) {
157 15
                $this->io->warning($message);
158
159 15
                return 0;
160 5
            }
161
162
            $this->io->error($message);
163 10
164 10
            return 1;
165
        }
166 10
167 10
        try {
168
            $version = $this->getDependencyFactory()->getVersionAliasResolver()->resolveVersionAlias($versionAlias);
0 ignored issues
show
Bug introduced by
It seems like $versionAlias defined by $input->getArgument('version') on line 140 can also be of type array<integer,string> or null; however, Doctrine\Migrations\Vers...::resolveVersionAlias() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
169
        } catch (UnknownMigrationVersion $e) {
170
            $this->io->error(sprintf(
171 10
                'Unknown version: %s',
172 10
                OutputFormatter::escape($versionAlias)
0 ignored issues
show
Bug introduced by
It seems like $versionAlias defined by $input->getArgument('version') on line 140 can also be of type array<integer,string> or null; however, Symfony\Component\Consol...tputFormatter::escape() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
173
            ));
174 10
175 4
            return 1;
176 4
        } catch (NoMigrationsToExecute|NoMigrationsFoundWithCriteria $e) {
177
            return $this->exitForAlias($versionAlias);
0 ignored issues
show
Bug introduced by
It seems like $versionAlias defined by $input->getArgument('version') on line 140 can also be of type array<integer,string> or null; however, Doctrine\Migrations\Tool...Command::exitForAlias() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
178
        }
179 10
180
        $planCalculator                = $this->getDependencyFactory()->getMigrationPlanCalculator();
181 10
        $statusCalculator              = $this->getDependencyFactory()->getMigrationStatusCalculator();
182
        $executedUnavailableMigrations = $statusCalculator->getExecutedUnavailableMigrations();
183
184 16
        if ($this->checkExecutedUnavailableMigrations($executedUnavailableMigrations, $input) === false) {
185
            return 3;
186
        }
187
188 16
        $plan = $planCalculator->getPlanUntilVersion($version);
189 1
190 1
        if (count($plan) === 0) {
191 1
            return $this->exitForAlias($versionAlias);
0 ignored issues
show
Bug introduced by
It seems like $versionAlias defined by $input->getArgument('version') on line 140 can also be of type array<integer,string> or null; however, Doctrine\Migrations\Tool...Command::exitForAlias() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
192
        }
193
194 1
        $this->getDependencyFactory()->getLogger()->notice(
195 1
            'Migrating' . ($migratorConfiguration->isDryRun() ? ' (dry-run)' : '') . ' {direction} to {to}',
196 1
            [
197 1
                'direction' => $plan->getDirection(),
198
                'to' => (string) $version,
199 1
            ]
200 1
        );
201
202
        $migrator = $this->getDependencyFactory()->getMigrator();
203
        $sql      = $migrator->migrate($plan, $migratorConfiguration);
204 1
205
        if (is_string($path)) {
206 1
            $writer = $this->getDependencyFactory()->getQueryWriter();
207 1
            $writer->write($path, $plan->getDirection(), $sql);
208
        }
209 1
210
        $this->io->newLine();
211
212
        return 0;
213 15
    }
214
215
    private function checkExecutedUnavailableMigrations(
216 10
        ExecutedMigrationsList $executedUnavailableMigrations,
217
        InputInterface $input
218 10
    ) : bool {
219 8
        if (count($executedUnavailableMigrations) !== 0) {
220
            $this->io->warning(sprintf(
221
                'You have %s previously executed migrations in the database that are not registered migrations.',
222 8
                count($executedUnavailableMigrations)
223 4
            ));
224 4
225 4
            foreach ($executedUnavailableMigrations->getItems() as $executedUnavailableMigration) {
226 4
                $this->io->text(sprintf(
227
                    '<comment>>></comment> %s (<comment>%s</comment>)',
228
                    $executedUnavailableMigration->getExecutedAt() !== null
229 4
                        ? $executedUnavailableMigration->getExecutedAt()->format('Y-m-d H:i:s')
230 4
                        : null,
231 4
                    $executedUnavailableMigration->getVersion()
232 4
                ));
233
            }
234
235
            $question = 'Are you sure you wish to continue?';
236 8
237 4
            if (! $this->canExecute($question, $input)) {
238
                $this->io->error('Migration cancelled!');
239 4
240
                return false;
241
            }
242 4
        }
243
244 4
        return true;
245
    }
246
247 2
    private function exitForAlias(string $versionAlias) : int
248 2
    {
249 2
        $version = $this->getDependencyFactory()->getVersionAliasResolver()->resolveVersionAlias('current');
250
251
        // Allow meaningful message when latest version already reached.
252 2
        if (in_array($versionAlias, ['current', 'latest', 'first'], true)) {
253
            $message = sprintf(
254
                'Already at the %s version ("%s")',
255
                $versionAlias,
256
                (string) $version
257
            );
258
259
            $this->io->success($message);
260
        } elseif (in_array($versionAlias, ['next', 'prev'], true) || strpos($versionAlias, 'current') === 0) {
261
            $message = sprintf(
262
                'The version "%s" couldn\'t be reached, you are at version "%s"',
263
                $versionAlias,
264
                (string) $version
265
            );
266
267
            $this->io->error($message);
268
        } else {
269
            $message = sprintf(
270
                'You are already at version "%s"',
271
                (string) $version
272
            );
273
274
            $this->io->success($message);
275
        }
276
277
        return 0;
278
    }
279
}
280