Completed
Pull Request — master (#839)
by Valentin
03:41
created

VersionCommand   A

Complexity

Total Complexity 30

Size/Duplication

Total Lines 215
Duplicated Lines 0 %

Test Coverage

Coverage 92.38%

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 129
c 3
b 0
f 0
dl 0
loc 215
ccs 97
cts 105
cp 0.9238
rs 10
wmc 30

4 Methods

Rating   Name   Duplication   Size   Complexity  
A execute() 0 23 5
C markVersions() 0 37 12
C mark() 0 62 12
B configure() 0 67 1
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 Doctrine\Migrations\Tools\Console\Exception\InvalidOptionUsage;
9
use Doctrine\Migrations\Tools\Console\Exception\VersionAlreadyExists;
10
use Doctrine\Migrations\Tools\Console\Exception\VersionDoesNotExist;
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 sprintf;
16
17
/**
18
 * The VersionCommand class is responsible for manually adding and deleting migration versions from the tracking table.
19
 */
20
class VersionCommand extends AbstractCommand
21
{
22
    /** @var string */
23
    protected static $defaultName = 'migrations:version';
24
25
    /** @var bool */
26
    private $markMigrated;
27
28 23
    protected function configure() : void
29
    {
30
        $this
31 23
            ->setAliases(['version'])
32 23
            ->setDescription('Manually add and delete migration versions from the version table.')
33 23
            ->addArgument(
34 23
                'version',
35 23
                InputArgument::OPTIONAL,
36 23
                'The version to add or delete.',
37 23
                null
38
            )
39 23
            ->addOption(
40 23
                'add',
41 23
                null,
42 23
                InputOption::VALUE_NONE,
43 23
                'Add the specified version.'
44
            )
45 23
            ->addOption(
46 23
                'delete',
47 23
                null,
48 23
                InputOption::VALUE_NONE,
49 23
                'Delete the specified version.'
50
            )
51 23
            ->addOption(
52 23
                'all',
53 23
                null,
54 23
                InputOption::VALUE_NONE,
55 23
                'Apply to all the versions.'
56
            )
57 23
            ->addOption(
58 23
                'range-from',
59 23
                null,
60 23
                InputOption::VALUE_OPTIONAL,
61 23
                'Apply from specified version.'
62
            )
63 23
            ->addOption(
64 23
                'range-to',
65 23
                null,
66 23
                InputOption::VALUE_OPTIONAL,
67 23
                'Apply to specified version.'
68
            )
69 23
            ->setHelp(<<<EOT
70 23
The <info>%command.name%</info> command allows you to manually add, delete or synchronize migration versions from the version table:
71
72
    <info>%command.full_name% YYYYMMDDHHMMSS --add</info>
73
74
If you want to delete a version you can use the <comment>--delete</comment> option:
75
76
    <info>%command.full_name% YYYYMMDDHHMMSS --delete</info>
77
78
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:
79
80
    <info>%command.full_name% --add --all</info>
81
    <info>%command.full_name% --delete --all</info>
82
83
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:
84
85
    <info>%command.full_name% --add --range-from=YYYYMMDDHHMMSS --range-to=YYYYMMDDHHMMSS</info>
86
    <info>%command.full_name% --delete --range-from=YYYYMMDDHHMMSS --range-to=YYYYMMDDHHMMSS</info>
87
88
You can also execute this command without a warning message which you need to interact with:
89
90
    <info>%command.full_name% --no-interaction</info>
91
EOT
92
            );
93
94 23
        parent::configure();
95 23
    }
96
97
    /**
98
     * @throws InvalidOptionUsage
99
     */
100 13
    public function execute(InputInterface $input, OutputInterface $output) : ?int
101
    {
102 13
        if ($input->getOption('add') === false && $input->getOption('delete') === false) {
103
            throw InvalidOptionUsage::new('You must specify whether you want to --add or --delete the specified version.');
104
        }
105
106 13
        $this->markMigrated = (bool) $input->getOption('add');
107
108 13
        if ($input->isInteractive()) {
109
            $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)';
110
111
            $confirmation = $this->askConfirmation($question, $input, $output);
112
113
            if ($confirmation) {
114
                $this->markVersions($input, $output);
115
            } else {
116
                $output->writeln('<error>Migration cancelled!</error>');
117
            }
118
        } else {
119 13
            $this->markVersions($input, $output);
120
        }
121
122 7
        return 0;
123
    }
124
125
    /**
126
     * @throws InvalidOptionUsage
127
     */
128 13
    private function markVersions(InputInterface $input, OutputInterface $output) : void
129
    {
130 13
        $affectedVersion = $input->getArgument('version');
131 13
        $allOption       = $input->getOption('all');
132 13
        $rangeFromOption = $input->getOption('range-from');
133 13
        $rangeToOption   = $input->getOption('range-to');
134
135 13
        if ($allOption === true && ($rangeFromOption !== null || $rangeToOption !== null)) {
136 2
            throw InvalidOptionUsage::new(
137 2
                'Options --all and --range-to/--range-from both used. You should use only one of them.'
138
            );
139
        }
140
141 11
        if ($rangeFromOption !== null xor $rangeToOption !== null) {
142 2
            throw InvalidOptionUsage::new(
143 2
                'Options --range-to and --range-from should be used together.'
144
            );
145
        }
146
147 9
        if ($allOption === true) {
148 2
            $availableVersions = $this->migrationRepository->getAvailableVersions();
149
150 2
            foreach ($availableVersions as $version) {
151 2
                $this->mark($input, $output, $version, true);
152
            }
153 7
        } elseif ($rangeFromOption !== null && $rangeToOption !== null) {
154 2
            $availableVersions = $this->migrationRepository->getAvailableVersions();
155
156 2
            foreach ($availableVersions as $version) {
157 2
                if ($version < $rangeFromOption || $version > $rangeToOption) {
158 2
                    continue;
159
                }
160
161 2
                $this->mark($input, $output, $version, true);
162
            }
163
        } else {
164 5
            $this->mark($input, $output, $affectedVersion);
165
        }
166 7
    }
167
168
    /**
169
     * @throws VersionAlreadyExists
170
     * @throws VersionDoesNotExist
171
     * @throws UnknownMigrationVersion
172
     */
173 9
    private function mark(InputInterface $input, OutputInterface $output, string $version, bool $all = false) : void
174
    {
175 9
        if (! $this->migrationRepository->hasVersion($version)) {
176 1
            if ((bool) $input->getOption('delete') === false) {
177
                throw UnknownMigrationVersion::new($version);
178
            }
179
180
            $question =
181
                'WARNING! You are about to delete a migration version from the version table that has no corresponding migration file.' .
182 1
                'Do you want to delete this migration from the migrations table? (y/n)';
183
184 1
            $confirmation = $this->askConfirmation($question, $input, $output);
185
186 1
            if ($confirmation) {
187 1
                $this->migrationRepository->removeMigrationVersionFromDatabase($version);
188
189 1
                $output->writeln(sprintf(
190 1
                    '<info>%s</info> deleted from the version table.',
191 1
                    $version
192
                ));
193
194 1
                return;
195
            }
196
        }
197
198 8
        $version = $this->migrationRepository->getVersion($version);
199
200 8
        $marked = false;
201
202 8
        if ($this->markMigrated && $this->migrationRepository->hasVersionMigrated($version)) {
203 1
            if (! $all) {
204 1
                throw VersionAlreadyExists::new($version);
205
            }
206
207
            $marked = true;
208
        }
209
210 7
        if (! $this->markMigrated && ! $this->migrationRepository->hasVersionMigrated($version)) {
211 3
            if (! $all) {
212 1
                throw VersionDoesNotExist::new($version);
213
            }
214
215 2
            $marked = true;
216
        }
217
218 6
        if ($marked === true) {
219 2
            return;
220
        }
221
222 6
        if ($this->markMigrated) {
223 3
            $version->markMigrated();
224
225 3
            $output->writeln(sprintf(
226 3
                '<info>%s</info> added to the version table.',
227 3
                $version->getVersion()
228
            ));
229
        } else {
230 3
            $version->markNotMigrated();
231
232 3
            $output->writeln(sprintf(
233 3
                '<info>%s</info> deleted from the version table.',
234 3
                $version->getVersion()
235
            ));
236
        }
237 6
    }
238
}
239