Failed Conditions
Pull Request — master (#632)
by Michael
02:44
created

DiffCommand::buildCodeFromSql()   C

Complexity

Conditions 7
Paths 11

Size

Total Lines 44
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 7.2576

Importance

Changes 0
Metric Value
cc 7
eloc 22
nc 11
nop 4
dl 0
loc 44
ccs 19
cts 23
cp 0.8261
crap 7.2576
rs 6.7272
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\Configuration\Configuration;
8
use Doctrine\Migrations\Provider\OrmSchemaProvider;
9
use Doctrine\Migrations\Provider\SchemaProviderInterface;
10
use InvalidArgumentException;
11
use SqlFormatter;
12
use Symfony\Component\Console\Input\InputInterface;
13
use Symfony\Component\Console\Input\InputOption;
14
use Symfony\Component\Console\Output\OutputInterface;
15
use function array_unshift;
16
use function class_exists;
17
use function file_get_contents;
18
use function implode;
19
use function preg_match;
20
use function sprintf;
21
use function stripos;
22
use function strlen;
23
use function strpos;
24
use function substr;
25
use function var_export;
26
27
class DiffCommand extends GenerateCommand
28
{
29
    /** @var null|SchemaProviderInterface */
30
    protected $schemaProvider;
31
32 8
    public function __construct(?SchemaProviderInterface $schemaProvider = null)
33
    {
34 8
        $this->schemaProvider = $schemaProvider;
35
36 8
        parent::__construct();
37 8
    }
38
39 8
    protected function configure() : void
40
    {
41 8
        parent::configure();
42
43
        $this
44 8
            ->setName('migrations:diff')
45 8
            ->setDescription('Generate a migration by comparing your current database to your mapping information.')
46 8
            ->setHelp(<<<EOT
47 8
The <info>%command.name%</info> command generates a migration by comparing your current database to your mapping information:
48
49
    <info>%command.full_name%</info>
50
51
You can optionally specify a <comment>--editor-cmd</comment> option to open the generated file in your favorite editor:
52
53
    <info>%command.full_name% --editor-cmd=mate</info>
54
EOT
55
            )
56 8
            ->addOption(
57 8
                'filter-expression',
58 8
                null,
59 8
                InputOption::VALUE_OPTIONAL,
60 8
                'Tables which are filtered by Regular Expression.'
61
            )
62 8
            ->addOption(
63 8
                'formatted',
64 8
                null,
65 8
                InputOption::VALUE_NONE,
66 8
                'Format the generated SQL.'
67
            )
68 8
            ->addOption(
69 8
                'line-length',
70 8
                null,
71 8
                InputOption::VALUE_OPTIONAL,
72 8
                'Max line length of unformatted lines.',
73 8
                120
74
            )
75
        ;
76 8
    }
77
78 8
    public function execute(
79
        InputInterface $input,
80
        OutputInterface $output
81
    ) : void {
82 8
        $configuration = $this->getMigrationConfiguration($input, $output);
83
84 8
        $this->loadCustomTemplate($configuration, $output);
85
86 8
        $conn = $configuration->getConnection();
87
88 8
        $platform = $conn->getDatabasePlatform();
89
90 8
        $filterExpr = $input->getOption('filter-expression');
91
92 8
        if ($filterExpr) {
93
            $conn->getConfiguration()
94
                ->setFilterSchemaAssetsExpression($filterExpr);
95
        }
96
97 8
        $fromSchema = $conn->getSchemaManager()->createSchema();
98
99 8
        $toSchema = $this->getSchemaProvider()->createSchema();
100
101 8
        $filterExpr = $conn->getConfiguration()->getFilterSchemaAssetsExpression();
102
103
        // Not using value from options, because filters can be set from config.yml
104 8
        if ($filterExpr !== null) {
105 2
            foreach ($toSchema->getTables() as $table) {
106 2
                $tableName = $table->getName();
107 2
                if (preg_match($filterExpr, $this->resolveTableName($tableName))) {
108 2
                    continue;
109
                }
110
111 1
                $toSchema->dropTable($tableName);
112
            }
113
        }
114
115 8
        $up = $this->buildCodeFromSql(
116 8
            $configuration,
117 8
            $fromSchema->getMigrateToSql($toSchema, $platform),
118 8
            $input->getOption('formatted'),
119 8
            $input->getOption('line-length')
120
        );
121
122 8
        $down = $this->buildCodeFromSql(
123 8
            $configuration,
124 8
            $fromSchema->getMigrateFromSql($toSchema, $platform),
125 8
            $input->getOption('formatted'),
126 8
            $input->getOption('line-length')
127
        );
128
129 8
        if (! $up && ! $down) {
130
            $output->writeln('No changes detected in your mapping information.');
131
132
            return;
133
        }
134
135 8
        $version = $configuration->generateVersionNumber();
136 8
        $path    = $this->generateMigration($configuration, $input, $version, $up, $down);
137
138 8
        $output->writeln(
139 8
            sprintf(
140 8
                'Generated new migration class to "<info>%s</info>" from schema differences.',
141 8
                $path
142
            )
143
        );
144
145 8
        $output->writeln(
146 8
            file_get_contents($path),
147 8
            OutputInterface::VERBOSITY_VERBOSE
148
        );
149 8
    }
150
151
    /** @param string[] $sql */
152 8
    private function buildCodeFromSql(
153
        Configuration $configuration,
154
        array $sql,
155
        bool $formatted = false,
156
        int $lineLength = 120
157
    ) : string {
158 8
        $currentPlatform = $configuration->getConnection()->getDatabasePlatform()->getName();
159 8
        $code            = [];
160
161 8
        foreach ($sql as $query) {
162 8
            if (stripos($query, $configuration->getMigrationsTableName()) !== false) {
163
                continue;
164
            }
165
166 8
            if ($formatted) {
167 1
                if (! class_exists('SqlFormatter')) {
168
                    throw new InvalidArgumentException(
169
                        'The "--formatted" option can only be used if the sql formatter is installed. Please run "composer require jdorn/sql-formatter".'
170
                    );
171
                }
172
173 1
                $maxLength = $lineLength - 18 - 8; // max - php code length - indentation
174
175 1
                if (strlen($query) > $maxLength) {
176 1
                    $query = SqlFormatter::format($query, false);
177
                }
178
            }
179
180 8
            $code[] = sprintf('$this->addSql(%s);', var_export($query, true));
181
        }
182
183 8
        if (! empty($code)) {
184 8
            array_unshift(
185
                $code,
186 8
                sprintf(
187 8
                    '$this->abortIf($this->connection->getDatabasePlatform()->getName() !== %s, %s);',
188 8
                    var_export($currentPlatform, true),
189 8
                    var_export(sprintf("Migration can only be executed safely on '%s'.", $currentPlatform), true)
190
                ),
191 8
                ''
192
            );
193
        }
194
195 8
        return implode("\n", $code);
196
    }
197
198 8
    private function getSchemaProvider() : SchemaProviderInterface
199
    {
200 8
        if (! $this->schemaProvider) {
201 1
            $this->schemaProvider = new OrmSchemaProvider(
202 1
                $this->getHelper('entityManager')->getEntityManager()
203
            );
204
        }
205
206 8
        return $this->schemaProvider;
207
    }
208
209
    /**
210
     * Resolve a table name from its fully qualified name. The `$name` argument
211
     * comes from Doctrine\DBAL\Schema\Table#getName which can sometimes return
212
     * a namespaced name with the form `{namespace}.{tableName}`. This extracts
213
     * the table name from that.
214
     */
215 2
    private function resolveTableName(string $name) : string
216
    {
217 2
        $pos = strpos($name, '.');
218
219 2
        return $pos === false ? $name : substr($name, $pos + 1);
220
    }
221
}
222