Failed Conditions
Pull Request — master (#941)
by Vincent
02:59
created

DiffCommand::checkExecutedUnavailableMigrations()   A

Complexity

Conditions 5
Paths 3

Size

Total Lines 31
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 16
c 1
b 0
f 0
nc 3
nop 3
dl 0
loc 31
ccs 0
cts 17
cp 0
crap 30
rs 9.4222
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\Migrations\Tools\Console\Command;
6
7
use Doctrine\Migrations\Generator\Exception\NoChangesDetected;
8
use Doctrine\Migrations\Metadata\AvailableMigrationsList;
9
use Doctrine\Migrations\Metadata\ExecutedMigrationsSet;
10
use Doctrine\Migrations\Tools\Console\Exception\InvalidOptionUsage;
11
use OutOfBoundsException;
12
use Symfony\Component\Console\Input\InputInterface;
13
use Symfony\Component\Console\Input\InputOption;
14
use Symfony\Component\Console\Output\OutputInterface;
15
use function addslashes;
16
use function assert;
17
use function class_exists;
18
use function filter_var;
19
use function is_string;
20
use function key;
21
use function sprintf;
22
use const FILTER_VALIDATE_BOOLEAN;
23
24
/**
25
 * The DiffCommand class is responsible for generating a migration by comparing your current database schema to
26
 * your mapping information.
27
 */
28
class DiffCommand extends DoctrineCommand
29
{
30
    /** @var string */
31
    protected static $defaultName = 'migrations:diff';
32
33
    protected function configure() : void
34
    {
35
        parent::configure();
36
37
        $this
38
            ->setAliases(['diff'])
39
            ->setDescription('Generate a migration by comparing your current database to your mapping information.')
40
            ->setHelp(<<<EOT
41
The <info>%command.name%</info> command generates a migration by comparing your current database to your mapping information:
42
43
    <info>%command.full_name%</info>
44
45
You can optionally specify a <comment>--editor-cmd</comment> option to open the generated file in your favorite editor:
46
47
    <info>%command.full_name% --editor-cmd=mate</info>
48
EOT
49
            )
50
            ->addOption(
51
                'namespace',
52
                null,
53
                InputOption::VALUE_REQUIRED,
54
                'The namespace to use for the migration (must be in the list of configured namespaces)'
55
            )
56
            ->addOption(
57
                'editor-cmd',
58
                null,
59
                InputOption::VALUE_REQUIRED,
60
                'Open file with this command upon creation.'
61
            )
62
            ->addOption(
63
                'filter-expression',
64
                null,
65
                InputOption::VALUE_REQUIRED,
66
                'Tables which are filtered by Regular Expression.'
67
            )
68
            ->addOption(
69
                'formatted',
70
                null,
71
                InputOption::VALUE_NONE,
72
                'Format the generated SQL.'
73
            )
74
            ->addOption(
75
                'line-length',
76
                null,
77
                InputOption::VALUE_REQUIRED,
78
                'Max line length of unformatted lines.',
79
                120
80
            )
81
            ->addOption(
82
                'check-database-platform',
83
                null,
84
                InputOption::VALUE_OPTIONAL,
85
                'Check Database Platform to the generated code.',
86
                false
87
            )
88
            ->addOption(
89
                'allow-empty-diff',
90
                null,
91
                InputOption::VALUE_NONE,
92
                'Do not throw an exception when no changes are detected.'
93
            );
94
    }
95
96
    /**
97
     * @throws InvalidOptionUsage
98
     */
99
    public function execute(
100
        InputInterface $input,
101
        OutputInterface $output
102
    ) : ?int {
103
        $filterExpression = (string) $input->getOption('filter-expression') ?: null;
104
        $formatted        = filter_var($input->getOption('formatted'), FILTER_VALIDATE_BOOLEAN);
105
        $lineLength       = (int) $input->getOption('line-length');
106
        $allowEmptyDiff   = $input->getOption('allow-empty-diff');
107
        $checkDbPlatform  = filter_var($input->getOption('check-database-platform'), FILTER_VALIDATE_BOOLEAN);
108
        $namespace        = $input->getOption('namespace') ?: null;
109
        if ($formatted) {
110
            if (! class_exists('SqlFormatter')) {
111
                throw InvalidOptionUsage::new(
112
                    'The "--formatted" option can only be used if the sql formatter is installed. Please run "composer require jdorn/sql-formatter".'
113
                );
114
            }
115
        }
116
117
        $configuration = $this->getDependencyFactory()->getConfiguration();
118
119
        $dirs = $configuration->getMigrationDirectories();
120
        if ($namespace === null) {
121
            $namespace = key($dirs);
122
        } elseif (! isset($dirs[$namespace])) {
123
            throw new OutOfBoundsException(sprintf('Path not defined for the namespace %s', $namespace));
124
        }
125
126
        assert(is_string($namespace));
127
128
        $planCalculator                = $this->getDependencyFactory()->getMigrationPlanCalculator();
129
        $executedUnavailableMigrations = $planCalculator->getExecutedUnavailableMigrations();
0 ignored issues
show
Bug introduced by
The method getExecutedUnavailableMigrations() does not exist on Doctrine\Migrations\Vers...MigrationPlanCalculator. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

129
        /** @scrutinizer ignore-call */ 
130
        $executedUnavailableMigrations = $planCalculator->getExecutedUnavailableMigrations();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
130
        $newMigrations                 = $planCalculator->getNewMigrations();
0 ignored issues
show
Bug introduced by
The method getNewMigrations() does not exist on Doctrine\Migrations\Vers...MigrationPlanCalculator. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

130
        /** @scrutinizer ignore-call */ 
131
        $newMigrations                 = $planCalculator->getNewMigrations();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
131
132
        if ($this->checkNewMigrations($newMigrations, $input, $output) === false) {
133
            return 3;
134
        }
135
136
        if ($this->checkExecutedUnavailableMigrations($executedUnavailableMigrations, $input, $output) === false) {
137
            return 3;
138
        }
139
140
        $fqcn = $this->getDependencyFactory()->getClassNameGenerator()->generateClassName($namespace);
141
142
        $diffGenerator = $this->getDependencyFactory()->getDiffGenerator();
143
144
        try {
145
            $path = $diffGenerator->generate(
146
                $fqcn,
147
                $filterExpression,
148
                $formatted,
149
                $lineLength,
150
                $checkDbPlatform
151
            );
152
        } catch (NoChangesDetected $exception) {
153
            if ($allowEmptyDiff) {
154
                $output->writeln($exception->getMessage());
155
156
                return 0;
157
            }
158
159
            throw $exception;
160
        }
161
162
        $editorCommand = $input->getOption('editor-cmd');
163
164
        if ($editorCommand !== null) {
165
            assert(is_string($editorCommand));
166
            $this->procOpen($editorCommand, $path);
167
        }
168
169
        $output->writeln([
170
            sprintf('Generated new migration class to "<info>%s</info>"', $path),
171
            '',
172
            sprintf(
173
                'To run just this migration for testing purposes, you can use <info>migrations:execute --up \'%s\'</info>',
174
                addslashes($fqcn)
175
            ),
176
            '',
177
            sprintf(
178
                'To revert the migration you can use <info>migrations:execute --down \'%s\'</info>',
179
                addslashes($fqcn)
180
            ),
181
        ]);
182
183
        return 0;
184
    }
185
186
    private function checkNewMigrations(
187
        AvailableMigrationsList $newMigrations,
188
        InputInterface $input,
189
        OutputInterface $output
190
    ) : bool {
191
        if (\count($newMigrations) !== 0) {
0 ignored issues
show
introduced by
Function \count() should not be referenced via a fully qualified name, but via a use statement.
Loading history...
192
            $output->writeln(sprintf(
193
                '<error>WARNING! You have %s available migrations to execute.</error>',
194
                \count($newMigrations)
0 ignored issues
show
introduced by
Function \count() should not be referenced via a fully qualified name, but via a use statement.
Loading history...
195
            ));
196
197
            $question = 'Are you sure you wish to continue? (y/n)';
198
199
            if (! $this->canExecute($question, $input, $output)) {
200
                $output->writeln('<error>Migration cancelled!</error>');
201
202
                return false;
203
            }
204
        }
205
206
        return true;
207
    }
208
209
    private function checkExecutedUnavailableMigrations(
210
        ExecutedMigrationsSet $executedUnavailableMigrations,
211
        InputInterface $input,
212
        OutputInterface $output
213
    ) : bool {
214
        if (\count($executedUnavailableMigrations) !== 0) {
0 ignored issues
show
introduced by
Function \count() should not be referenced via a fully qualified name, but via a use statement.
Loading history...
215
            $output->writeln(sprintf(
216
                '<error>WARNING! You have %s previously executed migrations in the database that are not registered migrations.</error>',
217
                \count($executedUnavailableMigrations)
0 ignored issues
show
introduced by
Function \count() should not be referenced via a fully qualified name, but via a use statement.
Loading history...
218
            ));
219
220
            foreach ($executedUnavailableMigrations->getItems() as $executedUnavailableMigration) {
221
                $output->writeln(sprintf(
222
                    '    <comment>>></comment> %s (<comment>%s</comment>)',
223
                    $executedUnavailableMigration->getExecutedAt() !== null
224
                        ? $executedUnavailableMigration->getExecutedAt()->format('Y-m-d H:i:s')
225
                        : null,
226
                    $executedUnavailableMigration->getVersion()
227
                ));
228
            }
229
230
            $question = 'Are you sure you wish to continue? (y/n)';
231
232
            if (! $this->canExecute($question, $input, $output)) {
233
                $output->writeln('<error>Migration cancelled!</error>');
234
235
                return false;
236
            }
237
        }
238
239
        return true;
240
    }
241
}
242