MigrationExecutor::executeUp()   C
last analyzed

Complexity

Conditions 7
Paths 8

Size

Total Lines 28
Code Lines 20

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 28
rs 6.7272
cc 7
eloc 20
nc 8
nop 2
1
<?php
2
3
namespace RDV\Bundle\MigrationBundle\Migration;
4
5
use Doctrine\DBAL\Platforms\AbstractPlatform;
6
use Doctrine\DBAL\Platforms\MySqlPlatform;
7
use Doctrine\DBAL\Schema\Column;
8
use Doctrine\DBAL\Schema\ColumnDiff;
9
use Doctrine\DBAL\Schema\Comparator;
10
use Doctrine\DBAL\Schema\Index;
11
use Doctrine\DBAL\Schema\Schema;
12
use Doctrine\DBAL\Schema\SchemaConfig;
13
use Doctrine\DBAL\Schema\SchemaDiff;
14
use Doctrine\DBAL\Schema\Sequence;
15
use Doctrine\DBAL\Schema\Table;
16
use Doctrine\DBAL\Schema\TableDiff;
17
use Psr\Log\LoggerInterface;
18
use RDV\Bundle\MigrationBundle\Exception\InvalidNameException;
19
20
class MigrationExecutor
21
{
22
    /**
23
     * @var MigrationQueryExecutor
24
     */
25
    protected $queryExecutor;
26
27
    /**
28
     * @var LoggerInterface
29
     */
30
    protected $logger;
31
32
    /**
33
     * @var MigrationExtensionManager
34
     */
35
    protected $extensionManager;
36
37
    /**
38
     * @param MigrationQueryExecutor $queryExecutor
39
     */
40
    public function __construct(MigrationQueryExecutor $queryExecutor)
41
    {
42
        $this->queryExecutor = $queryExecutor;
43
    }
44
45
    /**
46
     * Sets a logger
47
     *
48
     * @param LoggerInterface $logger
49
     */
50
    public function setLogger(LoggerInterface $logger)
51
    {
52
        $this->logger = $logger;
53
    }
54
55
    /**
56
     * Gets a query executor object this migration executor works with
57
     *
58
     * @return MigrationQueryExecutor
59
     */
60
    public function getQueryExecutor()
61
    {
62
        return $this->queryExecutor;
63
    }
64
65
    /**
66
     * Sets extension manager
67
     *
68
     * @param MigrationExtensionManager $extensionManager
69
     */
70
    public function setExtensionManager(MigrationExtensionManager $extensionManager)
71
    {
72
        $this->extensionManager = $extensionManager;
73
        $this->extensionManager->setDatabasePlatform(
74
            $this->queryExecutor->getConnection()->getDatabasePlatform()
75
        );
76
    }
77
78
    /**
79
     * Executes UP method for the given migrations
80
     *
81
     * @param MigrationState[] $migrations
82
     * @param bool             $dryRun
83
     *
84
     * @throws \RuntimeException if at lease one migration failed
85
     */
86
    public function executeUp(array $migrations, $dryRun = false)
87
    {
88
        $platform    = $this->queryExecutor->getConnection()->getDatabasePlatform();
89
        $sm          = $this->queryExecutor->getConnection()->getSchemaManager();
90
        $schema      = $this->createSchemaObject(
91
            $sm->listTables(),
92
            $platform->supportsSequences() ? $sm->listSequences() : [],
93
            $sm->createSchemaConfig()
94
        );
95
        $failedMigrations = false;
96
        foreach ($migrations as $item) {
97
            $migration = $item->getMigration();
98
            if (!empty($failedMigrations) && !$migration instanceof FailIndependentMigration) {
99
                $this->logger->notice(sprintf('> %s - skipped', get_class($migration)));
100
                continue;
101
            }
102
103
            if ($this->executeUpMigration($schema, $platform, $migration, $dryRun)) {
104
                $item->setSuccessful();
105
            } else {
106
                $item->setFailed();
107
                $failedMigrations[] = get_class($migration);
108
            }
109
        }
110
        if (!empty($failedMigrations)) {
111
            throw new \RuntimeException(sprintf('Failed migrations: %s.', implode(', ', $failedMigrations)));
112
        }
113
    }
114
115
    /**
116
     * @param Schema           $schema
117
     * @param AbstractPlatform $platform
118
     * @param Migration        $migration
119
     * @param bool             $dryRun
120
     *
121
     * @return bool
122
     */
123
    public function executeUpMigration(
124
        Schema &$schema,
125
        AbstractPlatform $platform,
126
        Migration $migration,
127
        $dryRun = false
128
    ) {
129
        $result = true;
130
131
        $this->logger->notice(sprintf('> %s', get_class($migration)));
132
        $toSchema = clone $schema;
133
        $this->setExtensions($migration);
134
        try {
135
            $queryBag = new QueryBag();
136
            $migration->up($toSchema, $queryBag);
137
138
            $comparator = new Comparator();
139
            $schemaDiff = $comparator->compare($schema, $toSchema);
140
141
            $this->checkTables($schemaDiff, $migration);
142
            $this->checkIndexes($schemaDiff, $migration);
143
144
            $queries = array_merge(
145
                $queryBag->getPreQueries(),
146
                $schemaDiff->toSql($platform),
147
                $queryBag->getPostQueries()
148
            );
149
150
            $schema = $toSchema;
151
152
            foreach ($queries as $query) {
153
                $this->queryExecutor->execute($query, $dryRun);
154
            }
155
        } catch (\Exception $ex) {
156
            $result = false;
157
            $this->logger->error(sprintf('  ERROR: %s', $ex->getMessage()));
158
        }
159
160
        return $result;
161
    }
162
163
    /**
164
     * Creates a database schema object
165
     *
166
     * @param Table[]           $tables
167
     * @param Sequence[]        $sequences
168
     * @param SchemaConfig|null $schemaConfig
169
     *
170
     * @return Schema
171
     */
172
    protected function createSchemaObject(array $tables = [], array $sequences = [], $schemaConfig = null)
173
    {
174
        return new Schema($tables, $sequences, $schemaConfig);
175
    }
176
177
    /**
178
     * Sets extensions for the given migration
179
     *
180
     * @param Migration $migration
181
     */
182
    protected function setExtensions(Migration $migration)
183
    {
184
        if ($this->extensionManager) {
185
            $this->extensionManager->applyExtensions($migration);
186
        }
187
    }
188
189
    /**
190
     * Validates the given tables from SchemaDiff
191
     *
192
     * @param SchemaDiff $schemaDiff
193
     * @param Migration  $migration
194
     *
195
     * @throws InvalidNameException if invalid table or column name is detected
196
     */
197 View Code Duplication
    protected function checkTables(SchemaDiff $schemaDiff, Migration $migration)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
198
    {
199
        foreach ($schemaDiff->newTables as $table) {
200
            $this->checkTableName($table->getName(), $migration);
201
            $this->checkColumnNames($table->getName(), $table->getColumns(), $migration);
202
        }
203
204
        foreach ($schemaDiff->changedTables as $tableName => $diff) {
205
            $this->checkColumnNames(
206
                $tableName,
207
                array_values($diff->addedColumns),
208
                $migration
209
            );
210
        }
211
    }
212
213
    /**
214
     * Validates the given columns
215
     *
216
     * @param string    $tableName
217
     * @param Column[]  $columns
218
     * @param Migration $migration
219
     *
220
     * @throws InvalidNameException if invalid column name is detected
221
     */
222
    protected function checkColumnNames($tableName, $columns, Migration $migration)
223
    {
224
        foreach ($columns as $column) {
225
            $this->checkColumnName($tableName, $column->getName(), $migration);
226
        }
227
    }
228
229
    /**
230
     * Validates table name
231
     *
232
     * @param string    $tableName
233
     * @param Migration $migration
234
     *
235
     * @throws InvalidNameException if table name is invalid
236
     */
237
    protected function checkTableName($tableName, Migration $migration)
238
    {
239
    }
240
241
    /**
242
     * Validates column name
243
     *
244
     * @param string    $tableName
245
     * @param string    $columnName
246
     * @param Migration $migration
247
     *
248
     * @throws InvalidNameException if column name is invalid
249
     */
250
    protected function checkColumnName($tableName, $columnName, Migration $migration)
251
    {
252
    }
253
254
    /**
255
     * @param SchemaDiff $schemaDiff
256
     * @param Migration  $migration
257
     */
258 View Code Duplication
    protected function checkIndexes(SchemaDiff $schemaDiff, Migration $migration)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
259
    {
260
        foreach ($schemaDiff->newTables as $table) {
261
            foreach ($table->getIndexes() as $index) {
262
                $this->checkIndex($table, $index, $migration);
263
            }
264
        }
265
266
        foreach ($schemaDiff->changedTables as $tableDiff) {
267
            foreach (array_values($tableDiff->addedIndexes) as $index) {
268
                $this->checkIndex(
269
                    $this->getTableFromDiff($tableDiff),
270
                    $index,
271
                    $migration
272
                );
273
            }
274
        }
275
    }
276
277
    /**
278
     * @param Table     $table
279
     * @param Index     $index
280
     * @param Migration $migration
281
     *
282
     * @throws InvalidNameException
283
     */
284
    protected function checkIndex(Table $table, Index $index, Migration $migration)
285
    {
286
        $columns = $index->getColumns();
287
        foreach ($columns as $columnName) {
288
            if ($table->getColumn($columnName)->getLength() > MySqlPlatform::LENGTH_LIMIT_TINYTEXT) {
289
                throw new InvalidNameException(
290
                    sprintf(
291
                        'Could not create index for column with length more than %s. ' .
292
                        'Please correct "%s" column length "%s" in table in "%s" migration',
293
                        MySqlPlatform::LENGTH_LIMIT_TINYTEXT,
294
                        $columnName,
295
                        $table->getName(),
296
                        get_class($migration)
297
                    )
298
                );
299
            }
300
        }
301
    }
302
303
    /**
304
     * @param TableDiff $diff
305
     *
306
     * @return Table
307
     */
308
    protected function getTableFromDiff(TableDiff $diff)
309
    {
310
        $changedColumns = array_map(
311
            function (ColumnDiff $columnDiff) {
312
                return $columnDiff->column;
313
            },
314
            $diff->changedColumns
315
        );
316
317
        $table = new Table(
318
            $diff->fromTable->getName(),
319
            array_merge($diff->fromTable->getColumns(), $diff->addedColumns, $changedColumns)
320
        );
321
322
        return $table;
323
    }
324
}
325