VersionCommand   A
last analyzed

Complexity

Total Complexity 35

Size/Duplication

Total Lines 239
Duplicated Lines 42.26 %

Coupling/Cohesion

Components 2
Dependencies 13

Test Coverage

Coverage 93.39%

Importance

Changes 0
Metric Value
wmc 35
lcom 2
cbo 13
dl 101
loc 239
ccs 113
cts 121
cp 0.9339
rs 9.6
c 0
b 0
f 0

4 Methods

Rating   Name   Duplication   Size   Complexity  
B configure() 68 68 1
A execute() 0 24 5
C markVersions() 0 52 16
C mark() 33 72 13

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\Migrations\Tools\Console\Command;
6
7
use Doctrine\Migrations\Exception\MigrationClassNotFound;
8
use Doctrine\Migrations\Exception\UnknownMigrationVersion;
9
use Doctrine\Migrations\Metadata\ExecutedMigrationsList;
10
use Doctrine\Migrations\Tools\Console\Exception\InvalidOptionUsage;
11
use Doctrine\Migrations\Tools\Console\Exception\VersionAlreadyExists;
12
use Doctrine\Migrations\Tools\Console\Exception\VersionDoesNotExist;
13
use Doctrine\Migrations\Version\Direction;
14
use Doctrine\Migrations\Version\ExecutionResult;
15
use Doctrine\Migrations\Version\Version;
16
use Symfony\Component\Console\Input\InputArgument;
17
use Symfony\Component\Console\Input\InputInterface;
18
use Symfony\Component\Console\Input\InputOption;
19
use Symfony\Component\Console\Output\OutputInterface;
20
use function sprintf;
21
22
/**
23
 * The VersionCommand class is responsible for manually adding and deleting migration versions from the tracking table.
24
 */
25
final class VersionCommand extends DoctrineCommand
26
{
27
    /** @var string */
28
    protected static $defaultName = 'migrations:version';
29
30
    /** @var bool */
31
    private $markMigrated;
32
33 13 View Code Duplication
    protected function configure() : void
34
    {
35
        $this
36 13
            ->setAliases(['version'])
37 13
            ->setDescription('Manually add and delete migration versions from the version table.')
38 13
            ->addArgument(
39 13
                'version',
40 13
                InputArgument::OPTIONAL,
41 13
                'The version to add or delete.',
42 13
                null
43
            )
44 13
            ->addOption(
45 13
                'add',
46 13
                null,
47 13
                InputOption::VALUE_NONE,
48 13
                'Add the specified version.'
49
            )
50 13
            ->addOption(
51 13
                'delete',
52 13
                null,
53 13
                InputOption::VALUE_NONE,
54 13
                'Delete the specified version.'
55
            )
56 13
            ->addOption(
57 13
                'all',
58 13
                null,
59 13
                InputOption::VALUE_NONE,
60 13
                'Apply to all the versions.'
61
            )
62 13
            ->addOption(
63 13
                'range-from',
64 13
                null,
65 13
                InputOption::VALUE_OPTIONAL,
66 13
                'Apply from specified version.'
67
            )
68 13
            ->addOption(
69 13
                'range-to',
70 13
                null,
71 13
                InputOption::VALUE_OPTIONAL,
72 13
                'Apply to specified version.'
73
            )
74 13
            ->setHelp(<<<EOT
75 13
The <info>%command.name%</info> command allows you to manually add, delete or synchronize migration versions from the version table:
76
77
    <info>%command.full_name% MIGRATION-FQCN --add</info>
78
79
If you want to delete a version you can use the <comment>--delete</comment> option:
80
81
    <info>%command.full_name% MIGRATION-FQCN --delete</info>
82
83
If you want to synchronize by adding or deleting all migration versions available in the version table you can use the <comment>--all</comment> option:
84
85
    <info>%command.full_name% --add --all</info>
86
    <info>%command.full_name% --delete --all</info>
87
88
If you want to synchronize by adding or deleting some range of migration versions available in the version table you can use the <comment>--range-from/--range-to</comment> option:
89
90
    <info>%command.full_name% --add --range-from=MIGRATION-FQCN --range-to=MIGRATION-FQCN</info>
91
    <info>%command.full_name% --delete --range-from=MIGRATION-FQCN --range-to=MIGRATION-FQCN</info>
92
93
You can also execute this command without a warning message which you need to interact with:
94
95
    <info>%command.full_name% --no-interaction</info>
96
EOT
97
            );
98
99 13
        parent::configure();
100 13
    }
101
102
    /**
103
     * @throws InvalidOptionUsage
104
     */
105 13
    protected function execute(InputInterface $input, OutputInterface $output) : int
106
    {
107 13
        if ($input->getOption('add') === false && $input->getOption('delete') === false) {
108
            throw InvalidOptionUsage::new('You must specify whether you want to --add or --delete the specified version.');
109
        }
110
111 13
        $this->markMigrated = $input->getOption('add');
112
113 13
        if ($input->isInteractive()) {
114
            $question = 'WARNING! You are about to add, delete or synchronize migration versions from the version table that could result in data lost. Are you sure you wish to continue?';
115
116
            $confirmation = $this->io->confirm($question);
117
118
            if ($confirmation) {
119
                $this->markVersions($input, $output);
120
            } else {
121
                $this->io->error('Migration cancelled!');
122
            }
123
        } else {
124 13
            $this->markVersions($input, $output);
125
        }
126
127 7
        return 0;
128
    }
129
130
    /**
131
     * @throws InvalidOptionUsage
132
     */
133 13
    private function markVersions(InputInterface $input, OutputInterface $output) : void
134
    {
135 13
        $affectedVersion = $input->getArgument('version');
136 13
        $allOption       = $input->getOption('all');
137 13
        $rangeFromOption = $input->getOption('range-from');
138 13
        $rangeToOption   = $input->getOption('range-to');
139
140 13
        if ($allOption === true && ($rangeFromOption !== null || $rangeToOption !== null)) {
141 2
            throw InvalidOptionUsage::new(
142 2
                'Options --all and --range-to/--range-from both used. You should use only one of them.'
143
            );
144
        }
145
146 11
        if ($rangeFromOption !== null xor $rangeToOption !== null) {
147 2
            throw InvalidOptionUsage::new(
148 2
                'Options --range-to and --range-from should be used together.'
149
            );
150
        }
151
152 9
        $executedMigrations = $this->getDependencyFactory()->getMetadataStorage()->getExecutedMigrations();
153 9
        $availableVersions  = $this->getDependencyFactory()->getMigrationPlanCalculator()->getMigrations();
154 9
        if ($allOption === true) {
155 2
            if ($input->getOption('delete') === true) {
156 1
                foreach ($executedMigrations->getItems() as $availableMigration) {
157 1
                    $this->mark($input, $output, $availableMigration->getVersion(), false, $executedMigrations);
158
                }
159
            }
160
161 2
            foreach ($availableVersions->getItems() as $availableMigration) {
162 2
                $this->mark($input, $output, $availableMigration->getVersion(), true, $executedMigrations);
163
            }
164 7
        } elseif ($affectedVersion !== null) {
165 5
            $this->mark($input, $output, new Version($affectedVersion), false, $executedMigrations);
0 ignored issues
show
Bug introduced by
It seems like $affectedVersion defined by $input->getArgument('version') on line 135 can also be of type array<integer,string>; however, Doctrine\Migrations\Version\Version::__construct() 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...
166 2
        } elseif ($rangeFromOption !== null && $rangeToOption !== null) {
167 2
            $migrate = false;
168 2
            foreach ($availableVersions->getItems() as $availableMigration) {
169 2
                if ((string) $availableMigration->getVersion() === $rangeFromOption) {
170 2
                    $migrate = true;
171
                }
172
173 2
                if ($migrate) {
174 2
                    $this->mark($input, $output, $availableMigration->getVersion(), true, $executedMigrations);
175
                }
176
177 2
                if ((string) $availableMigration->getVersion() === $rangeToOption) {
178 2
                    break;
179
                }
180
            }
181
        } else {
182
            throw InvalidOptionUsage::new('You must specify the version or use the --all argument.');
183
        }
184 7
    }
185
186
    /**
187
     * @throws VersionAlreadyExists
188
     * @throws VersionDoesNotExist
189
     * @throws UnknownMigrationVersion
190
     */
191 9
    private function mark(InputInterface $input, OutputInterface $output, Version $version, bool $all, ExecutedMigrationsList $executedMigrations) : void
0 ignored issues
show
Unused Code introduced by
The parameter $output is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
192
    {
193
        try {
194 9
            $availableMigration = $this->getDependencyFactory()->getMigrationRepository()->getMigration($version);
195 2
        } catch (MigrationClassNotFound $e) {
196 2
            $availableMigration = null;
197
        }
198
199 9
        $storage = $this->getDependencyFactory()->getMetadataStorage();
200 9
        if ($availableMigration === null) {
201 2
            if ($input->getOption('delete') === false) {
202
                throw UnknownMigrationVersion::new((string) $version);
203
            }
204
205
            $question =
206
                'WARNING! You are about to delete a migration version from the version table that has no corresponding migration file.' .
207 2
                'Do you want to delete this migration from the migrations table?';
208
209 2
            $confirmation = $this->io->confirm($question);
210
211 2 View Code Duplication
            if ($confirmation) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
212 2
                $migrationResult = new ExecutionResult($version, Direction::DOWN);
213 2
                $storage->complete($migrationResult);
214 2
                $this->io->text(sprintf(
215 2
                    "<info>%s</info> deleted from the version table.\n",
216 2
                    (string) $version
217
                ));
218
219 2
                return;
220
            }
221
        }
222
223 8
        $marked = false;
224
225 8 View Code Duplication
        if ($this->markMigrated && $executedMigrations->hasMigration($version)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
226 2
            if (! $all) {
227 1
                throw VersionAlreadyExists::new($version);
228
            }
229
230 1
            $marked = true;
231
        }
232
233 7 View Code Duplication
        if (! $this->markMigrated && ! $executedMigrations->hasMigration($version)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
234 2
            if (! $all) {
235 1
                throw VersionDoesNotExist::new($version);
236
            }
237
238 1
            $marked = true;
239
        }
240
241 6
        if ($marked === true) {
242 2
            return;
243
        }
244
245 6
        if ($this->markMigrated) {
246 3
            $migrationResult = new ExecutionResult($version, Direction::UP);
247 3
            $storage->complete($migrationResult);
248
249 3
            $this->io->text(sprintf(
250 3
                "<info>%s</info> added to the version table.\n",
251 3
                (string) $version
252
            ));
253 View Code Duplication
        } else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
254 3
            $migrationResult = new ExecutionResult($version, Direction::DOWN);
255 3
            $storage->complete($migrationResult);
256
257 3
            $this->io->text(sprintf(
258 3
                "<info>%s</info> deleted from the version table.\n",
259 3
                (string) $version
260
            ));
261
        }
262 6
    }
263
}
264