Completed
Push — master ( 2d3818...4cdd36 )
by Jonathan
11s
created

VersionCommand::execute()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 25
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 8.9038

Importance

Changes 0
Metric Value
cc 5
eloc 14
nc 4
nop 2
dl 0
loc 25
ccs 6
cts 13
cp 0.4615
crap 8.9038
rs 8.439
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\UnknownMigrationVersion;
8
use InvalidArgumentException;
9
use Symfony\Component\Console\Input\InputArgument;
10
use Symfony\Component\Console\Input\InputInterface;
11
use Symfony\Component\Console\Input\InputOption;
12
use Symfony\Component\Console\Output\OutputInterface;
13
use function sprintf;
14
15
/**
16
 * The VersionCommand class is responsible for manually adding and deleting migration versions from the tracking table.
17
 */
18
class VersionCommand extends AbstractCommand
19
{
20
    /** @var bool */
21
    private $markMigrated;
22
23 22
    protected function configure() : void
24
    {
25
        $this
26 22
            ->setName('migrations:version')
27 22
            ->setAliases(['version'])
28 22
            ->setDescription('Manually add and delete migration versions from the version table.')
29 22
            ->addArgument(
30 22
                'version',
31 22
                InputArgument::OPTIONAL,
32 22
                'The version to add or delete.',
33 22
                null
34
            )
35 22
            ->addOption(
36 22
                'add',
37 22
                null,
38 22
                InputOption::VALUE_NONE,
39 22
                'Add the specified version.'
40
            )
41 22
            ->addOption(
42 22
                'delete',
43 22
                null,
44 22
                InputOption::VALUE_NONE,
45 22
                'Delete the specified version.'
46
            )
47 22
            ->addOption(
48 22
                'all',
49 22
                null,
50 22
                InputOption::VALUE_NONE,
51 22
                'Apply to all the versions.'
52
            )
53 22
            ->addOption(
54 22
                'range-from',
55 22
                null,
56 22
                InputOption::VALUE_OPTIONAL,
57 22
                'Apply from specified version.'
58
            )
59 22
            ->addOption(
60 22
                'range-to',
61 22
                null,
62 22
                InputOption::VALUE_OPTIONAL,
63 22
                'Apply to specified version.'
64
            )
65 22
            ->setHelp(<<<EOT
66 22
The <info>%command.name%</info> command allows you to manually add, delete or synchronize migration versions from the version table:
67
68
    <info>%command.full_name% YYYYMMDDHHMMSS --add</info>
69
70
If you want to delete a version you can use the <comment>--delete</comment> option:
71
72
    <info>%command.full_name% YYYYMMDDHHMMSS --delete</info>
73
74
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:
75
76
    <info>%command.full_name% --add --all</info>
77
    <info>%command.full_name% --delete --all</info>
78
79
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:
80
81
    <info>%command.full_name% --add --range-from=YYYYMMDDHHMMSS --range-to=YYYYMMDDHHMMSS</info>
82
    <info>%command.full_name% --delete --range-from=YYYYMMDDHHMMSS --range-to=YYYYMMDDHHMMSS</info>
83
84
You can also execute this command without a warning message which you need to interact with:
85
86
    <info>%command.full_name% --no-interaction</info>
87
EOT
88
            );
89
90 22
        parent::configure();
91 22
    }
92
93 12
    public function execute(InputInterface $input, OutputInterface $output) : ?int
94
    {
95 12
        if ($input->getOption('add') === false && $input->getOption('delete') === false) {
96
            throw new InvalidArgumentException(
97
                'You must specify whether you want to --add or --delete the specified version.'
98
            );
99
        }
100
101 12
        $this->markMigrated = (bool) $input->getOption('add');
102
103 12
        if ($input->isInteractive()) {
104
            $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? (y/n)';
105
106
            $confirmation = $this->askConfirmation($question, $input, $output);
107
108
            if ($confirmation) {
109
                $this->markVersions($input, $output);
110
            } else {
111
                $output->writeln('<error>Migration cancelled!</error>');
112
            }
113
        } else {
114 12
            $this->markVersions($input, $output);
115
        }
116
117 6
        return 0;
118
    }
119
120
    /**
121
     * @throws InvalidArgumentException
122
     * @throws UnknownMigrationVersion
123
     */
124 12
    private function markVersions(InputInterface $input, OutputInterface $output) : void
125
    {
126 12
        $affectedVersion = $input->getArgument('version');
127 12
        $allOption       = $input->getOption('all');
128 12
        $rangeFromOption = $input->getOption('range-from');
129 12
        $rangeToOption   = $input->getOption('range-to');
130
131 12
        if ($allOption === true && ($rangeFromOption !== null || $rangeToOption !== null)) {
132 2
            throw new InvalidArgumentException(
133 2
                'Options --all and --range-to/--range-from both used. You should use only one of them.'
134
            );
135
        }
136
137 10
        if ($rangeFromOption !== null ^ $rangeToOption !== null) {
0 ignored issues
show
Comprehensibility introduced by
Consider adding parentheses for clarity. Current Interpretation: ($rangeFromOption !== nu...$rangeToOption !== null, Probably Intended Meaning: $rangeFromOption !== (nu...rangeToOption !== null)

When comparing the result of a bit operation, we suggest to add explicit parenthesis and not to rely on PHP?s built-in operator precedence to ensure the code behaves as intended and to make it more readable.

Let?s take a look at these examples:

// Returns always int(0).
return 0 === $foo & 4;
return (0 === $foo) & 4;

// More likely intended return: true/false
return 0 === ($foo & 4);
Loading history...
138 2
            throw new InvalidArgumentException(
139 2
                'Options --range-to and --range-from should be used together.'
140
            );
141
        }
142
143 8
        if ($allOption === true) {
144 2
            $availableVersions = $this->migrationRepository->getAvailableVersions();
145
146 2
            foreach ($availableVersions as $version) {
147 2
                $this->mark($output, $version, true);
148
            }
149 6
        } elseif ($rangeFromOption !== null && $rangeToOption !== null) {
150 2
            $availableVersions = $this->migrationRepository->getAvailableVersions();
151
152 2
            foreach ($availableVersions as $version) {
153 2
                if ($version < $rangeFromOption || $version > $rangeToOption) {
154 2
                    continue;
155
                }
156
157 2
                $this->mark($output, $version, true);
158
            }
159
        } else {
160 4
            $this->mark($output, $affectedVersion);
161
        }
162 6
    }
163
164
    /**
165
     * @throws InvalidArgumentException
166
     * @throws UnknownMigrationVersion
167
     */
168 8
    private function mark(OutputInterface $output, string $version, bool $all = false) : void
169
    {
170 8
        if (! $this->migrationRepository->hasVersion($version)) {
171
            throw UnknownMigrationVersion::new($version);
172
        }
173
174 8
        $version = $this->migrationRepository->getVersion($version);
175
176 8
        $marked = false;
177
178 8
        if ($this->markMigrated && $this->migrationRepository->hasVersionMigrated($version)) {
179 1
            if (! $all) {
180 1
                throw new InvalidArgumentException(
181 1
                    sprintf('The version "%s" already exists in the version table.', $version)
182
                );
183
            }
184
185
            $marked = true;
186
        }
187
188 7
        if (! $this->markMigrated && ! $this->migrationRepository->hasVersionMigrated($version)) {
189 3
            if (! $all) {
190 1
                throw new InvalidArgumentException(
191 1
                    sprintf('The version "%s" does not exist in the version table.', $version)
192
                );
193
            }
194
195 2
            $marked = true;
196
        }
197
198 6
        if ($marked === true) {
199 2
            return;
200
        }
201
202 6
        if ($this->markMigrated) {
203 3
            $version->markMigrated();
204
205 3
            $output->writeln(sprintf(
206 3
                '<info>%s</info> added to the version table.',
207 3
                $version->getVersion()
208
            ));
209
        } else {
210 3
            $version->markNotMigrated();
211
212 3
            $output->writeln(sprintf(
213 3
                '<info>%s</info> deleted from the version table.',
214 3
                $version->getVersion()
215
            ));
216
        }
217 6
    }
218
}
219