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

Version::execute()   F

Complexity

Conditions 13
Paths 1312

Size

Total Lines 105
Code Lines 59

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 55
CRAP Score 13

Importance

Changes 0
Metric Value
cc 13
eloc 59
nc 1312
nop 3
dl 0
loc 105
ccs 55
cts 55
cp 1
crap 13
rs 2
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\Migrations;
6
7
use Doctrine\DBAL\Connection;
8
use Doctrine\DBAL\Types\Type;
9
use Doctrine\Migrations\Configuration\Configuration;
10
use Doctrine\Migrations\Event\MigrationsVersionEventArgs;
11
use Doctrine\Migrations\Provider\LazySchemaDiffProvider;
12
use Doctrine\Migrations\Provider\SchemaDiffProvider;
13
use Doctrine\Migrations\Provider\SchemaDiffProviderInterface;
14
use Throwable;
15
use function array_map;
16
use function count;
17
use function implode;
18
use function is_array;
19
use function is_bool;
20
use function is_int;
21
use function is_string;
22
use function microtime;
23
use function round;
24
use function rtrim;
25
use function sprintf;
26
use function ucfirst;
27
28
class Version
29
{
30
    public const STATE_NONE = 0;
31
    public const STATE_PRE  = 1;
32
    public const STATE_EXEC = 2;
33
    public const STATE_POST = 3;
34
35
    public const DIRECTION_UP   = 'up';
36
    public const DIRECTION_DOWN = 'down';
37
38
    /** @var Configuration */
39
    private $configuration;
40
41
    /** @var OutputWriter */
42
    private $outputWriter;
43
44
    /** @var string */
45
    private $version;
46
47
    /** @var AbstractMigration */
48
    private $migration;
49
50
    /** @var Connection */
51
    private $connection;
52
53
    /** @var string */
54
    private $class;
55
56
    /** @var string[] */
57
    private $sql = [];
58
59
    /** @var mixed[] */
60
    private $params = [];
61
62
    /** @var mixed[] */
63
    private $types = [];
64
65
    /** @var float */
66
    private $time;
67
68
    /** @var int */
69
    private $state = self::STATE_NONE;
70
71
    /** @var SchemaDiffProviderInterface */
72
    private $schemaProvider;
73
74 122
    public function __construct(
75
        Configuration $configuration,
76
        string $version,
77
        string $class,
78
        ?SchemaDiffProviderInterface $schemaProvider = null
79
    ) {
80 122
        $this->configuration = $configuration;
81 122
        $this->outputWriter  = $configuration->getOutputWriter();
82 122
        $this->class         = $class;
83 122
        $this->connection    = $configuration->getConnection();
84 122
        $this->migration     = new $class($this);
85 122
        $this->version       = $version;
86
87 122
        if ($schemaProvider !== null) {
88 1
            $this->schemaProvider = $schemaProvider;
89
        }
90
91 122
        if ($schemaProvider !== null) {
92 1
            return;
93
        }
94
95 121
        $schemaProvider = new SchemaDiffProvider(
96 121
            $this->connection->getSchemaManager(),
97 121
            $this->connection->getDatabasePlatform()
98
        );
99
100 121
        $this->schemaProvider = LazySchemaDiffProvider::fromDefaultProxyFactoryConfiguration(
101 121
            $schemaProvider
102
        );
103 121
    }
104
105 79
    public function getVersion() : string
106
    {
107 79
        return $this->version;
108
    }
109
110 118
    public function getConfiguration() : Configuration
111
    {
112 118
        return $this->configuration;
113
    }
114
115 15
    public function isMigrated() : bool
116
    {
117 15
        return $this->configuration->hasVersionMigrated($this);
118
    }
119
120 43
    public function markMigrated() : void
121
    {
122 43
        $this->markVersion(self::DIRECTION_UP);
123 43
    }
124
125 9
    public function markNotMigrated() : void
126
    {
127 9
        $this->markVersion(self::DIRECTION_DOWN);
128 9
    }
129
130
    /**
131
     * @param string[]|string $sql
132
     * @param mixed[]         $params
133
     * @param mixed[]         $types
134
     */
135 49
    public function addSql($sql, array $params = [], array $types = []) : void
136
    {
137 49
        if (is_array($sql)) {
138 48
            foreach ($sql as $key => $query) {
139 14
                $this->sql[] = $query;
140
141 14
                if (empty($params[$key])) {
142 14
                    continue;
143
                }
144
145 1
                $queryTypes = $types[$key] ?? [];
146 48
                $this->addQueryParams($params[$key], $queryTypes);
147
            }
148
        } else {
149 30
            $this->sql[] = $sql;
150
151 30
            if (! empty($params)) {
152 18
                $this->addQueryParams($params, $types);
153
            }
154
        }
155 49
    }
156
157 9
    public function writeSqlFile(
158
        string $path,
159
        string $direction = self::DIRECTION_UP
160
    ) : bool {
161 9
        $queries = $this->execute($direction, true);
162
163 9
        if (! empty($this->params)) {
164 1
            throw MigrationException::migrationNotConvertibleToSql($this->class);
165
        }
166
167 8
        $this->outputWriter->write("\n-- Version " . $this->version . "\n");
168
169 8
        $sqlQueries = [$this->version => $queries];
170
171
        /*
172
         * Since the configuration object changes during the creation we cannot inject things
173
         * properly, so I had to violate LoD here (so please, let's find a way to solve it on v2).
174
         */
175 8
        return $this->configuration
176 8
            ->getQueryWriter()
177 8
            ->write($path, $direction, $sqlQueries);
178
    }
179
180 11
    public function getMigration() : AbstractMigration
181
    {
182 11
        return $this->migration;
183
    }
184
185
    /** @return string[] */
186 50
    public function execute(
187
        string $direction,
188
        bool $dryRun = false,
189
        bool $timeAllQueries = false
190
    ) : array {
191 50
        $this->dispatchEvent(Events::onMigrationsVersionExecuting, $direction, $dryRun);
192
193 50
        $this->sql = [];
194
195 50
        $transaction = $this->migration->isTransactional();
196 50
        if ($transaction) {
197
            //only start transaction if in transactional mode
198 50
            $this->connection->beginTransaction();
199
        }
200
201
        try {
202 50
            $migrationStart = microtime(true);
203
204 50
            $this->state = self::STATE_PRE;
205 50
            $fromSchema  = $this->schemaProvider->createFromSchema();
206
207 50
            $this->migration->{'pre' . ucfirst($direction)}($fromSchema);
208
209 48
            if ($direction === self::DIRECTION_UP) {
210 46
                $this->outputWriter->write("\n" . sprintf('  <info>++</info> migrating <comment>%s</comment>', $this->version) . "\n");
211
            } else {
212 8
                $this->outputWriter->write("\n" . sprintf('  <info>--</info> reverting <comment>%s</comment>', $this->version) . "\n");
213
            }
214
215 48
            $this->state = self::STATE_EXEC;
216
217 48
            $toSchema = $this->schemaProvider->createToSchema($fromSchema);
218 48
            $this->migration->$direction($toSchema);
219
220 47
            $this->addSql($this->schemaProvider->getSqlDiffToMigrate($fromSchema, $toSchema));
221
222 47
            $this->executeRegisteredSql($dryRun, $timeAllQueries);
223
224 47
            $this->state = self::STATE_POST;
225 47
            $this->migration->{'post' . ucfirst($direction)}($toSchema);
226
227 47
            if (! $dryRun) {
228 30
                if ($direction === self::DIRECTION_UP) {
229 30
                    $this->markMigrated();
230
                } else {
231 6
                    $this->markNotMigrated();
232
                }
233
            }
234
235 47
            $migrationEnd = microtime(true);
236 47
            $this->time   = round($migrationEnd - $migrationStart, 2);
237 47
            if ($direction === self::DIRECTION_UP) {
238 45
                $this->outputWriter->write(sprintf("\n  <info>++</info> migrated (%ss)", $this->time));
239
            } else {
240 8
                $this->outputWriter->write(sprintf("\n  <info>--</info> reverted (%ss)", $this->time));
241
            }
242
243 47
            if ($transaction) {
244
                //commit only if running in transactional mode
245 47
                $this->connection->commit();
246
            }
247
248 47
            $this->state = self::STATE_NONE;
249
250 47
            $this->dispatchEvent(Events::onMigrationsVersionExecuted, $direction, $dryRun);
251
252 47
            return $this->sql;
253 7
        } catch (SkipMigrationException $e) {
254 6
            if ($transaction) {
255
                //only rollback transaction if in transactional mode
256 6
                $this->connection->rollBack();
257
            }
258
259 6
            if ($dryRun === false) {
260
                // now mark it as migrated
261 5
                if ($direction === self::DIRECTION_UP) {
262 5
                    $this->markMigrated();
263
                } else {
264 1
                    $this->markNotMigrated();
265
                }
266
            }
267
268 6
            $this->outputWriter->write(sprintf("\n  <info>SS</info> skipped (Reason: %s)", $e->getMessage()));
269
270 6
            $this->state = self::STATE_NONE;
271
272 6
            $this->dispatchEvent(Events::onMigrationsVersionSkipped, $direction, $dryRun);
273
274 6
            return [];
275 1
        } catch (Throwable $e) {
276 1
            $this->outputWriter->write(sprintf(
277 1
                '<error>Migration %s failed during %s. Error %s</error>',
278 1
                $this->version,
279 1
                $this->getExecutionState(),
280 1
                $e->getMessage()
281
            ));
282
283 1
            if ($transaction) {
284
                //only rollback transaction if in transactional mode
285 1
                $this->connection->rollBack();
286
            }
287
288 1
            $this->state = self::STATE_NONE;
289
290 1
            throw $e;
291
        }
292
    }
293
294 8
    public function getExecutionState() : string
295
    {
296 8
        switch ($this->state) {
297 8
            case self::STATE_PRE:
298 1
                return 'Pre-Checks';
299
300 7
            case self::STATE_POST:
301 1
                return 'Post-Checks';
302
303 6
            case self::STATE_EXEC:
304 2
                return 'Execution';
305
306
            default:
307 4
                return 'No State';
308
        }
309
    }
310
311 26
    public function getTime() : ?float
312
    {
313 26
        return $this->time;
314
    }
315
316 2
    public function __toString() : string
317
    {
318 2
        return $this->version;
319
    }
320
321 21
    private function outputQueryTime(float $queryStart, bool $timeAllQueries = false) : void
322
    {
323 21
        if ($timeAllQueries === false) {
324 20
            return;
325
        }
326
327 1
        $queryEnd  = microtime(true);
328 1
        $queryTime = round($queryEnd - $queryStart, 4);
329
330 1
        $this->outputWriter->write(sprintf('  <info>%ss</info>', $queryTime));
331 1
    }
332
333 43
    private function markVersion(string $direction) : void
334
    {
335 43
        $this->configuration->createMigrationTable();
336
337 43
        $migrationsColumnName = $this->configuration
338 43
            ->getQuotedMigrationsColumnName();
339
340 43
        if ($direction === self::DIRECTION_UP) {
341 43
            $this->connection->insert(
342 43
                $this->configuration->getMigrationsTableName(),
343
                [
344 43
                    $migrationsColumnName => $this->version,
345
                ]
346
            );
347
        } else {
348 9
            $this->connection->delete(
349 9
                $this->configuration->getMigrationsTableName(),
350
                [
351 9
                    $migrationsColumnName => $this->version,
352
                ]
353
            );
354
        }
355 43
    }
356
357
    /**
358
     * @param mixed[]|int $params
359
     * @param mixed[]|int $types
360
     */
361 19
    private function addQueryParams($params, $types) : void
362
    {
363 19
        $index                = count($this->sql) - 1;
364 19
        $this->params[$index] = $params;
365 19
        $this->types[$index]  = $types;
366 19
    }
367
368 47
    private function executeRegisteredSql(
369
        bool $dryRun = false,
370
        bool $timeAllQueries = false
371
    ) : void {
372 47
        if (! $dryRun) {
373 30
            if (! empty($this->sql)) {
374 19
                foreach ($this->sql as $key => $query) {
375 19
                    $queryStart = microtime(true);
376
377 19
                    $this->outputSqlQuery($key, $query);
378 19
                    if (! isset($this->params[$key])) {
379 18
                        $this->connection->executeQuery($query);
380
                    } else {
381 8
                        $this->connection->executeQuery($query, $this->params[$key], $this->types[$key]);
382
                    }
383
384 19
                    $this->outputQueryTime($queryStart, $timeAllQueries);
385
                }
386
            } else {
387 11
                $this->outputWriter->write(sprintf(
388 11
                    '<error>Migration %s was executed but did not result in any SQL statements.</error>',
389 30
                    $this->version
390
                ));
391
            }
392
        } else {
393 17
            foreach ($this->sql as $idx => $query) {
394 17
                $this->outputSqlQuery($idx, $query);
395
            }
396
        }
397 47
    }
398
399 36
    private function outputSqlQuery(int $idx, string $query) : void
400
    {
401 36
        $params = $this->formatParamsForOutput(
402 36
            $this->params[$idx] ?? [],
403 36
            $this->types[$idx] ?? []
404
        );
405
406 36
        $this->outputWriter->write(rtrim(sprintf(
407 36
            '     <comment>-></comment> %s %s',
408 36
            $query,
409 36
            $params
410
        )));
411 36
    }
412
413
    /**
414
     * @param mixed[] $params
415
     * @param mixed[] $types
416
     */
417 36
    private function formatParamsForOutput(array $params, array $types) : string
418
    {
419 36
        if (empty($params)) {
420 26
            return '';
421
        }
422
423 18
        $out = [];
424 18
        foreach ($params as $key => $value) {
425 18
            $type   = $types[$key] ?? 'string';
426 18
            $outval = '[' . $this->formatParameter($value, $type) . ']';
427 18
            $out[]  = is_string($key) ? sprintf(':%s => %s', $key, $outval) : $outval;
428
        }
429
430 18
        return sprintf('with parameters (%s)', implode(', ', $out));
431
    }
432
433 50
    private function dispatchEvent(
434
        string $eventName,
435
        string $direction,
436
        bool $dryRun
437
    ) : void {
438 50
        $event = $this->createMigrationsVersionEventArgs(
439 50
            $this,
440 50
            $this->configuration,
441 50
            $direction,
442 50
            $dryRun
443
        );
444
445 50
        $this->configuration->dispatchEvent($eventName, $event);
446 50
    }
447
448 50
    private function createMigrationsVersionEventArgs(
449
        Version $version,
450
        Configuration $config,
451
        string $direction,
452
        bool $dryRun
453
    ) : MigrationsVersionEventArgs {
454 50
        return new MigrationsVersionEventArgs(
455 50
            $this,
456 50
            $this->configuration,
457 50
            $direction,
458 50
            $dryRun
459
        );
460
    }
461
462
    /**
463
     * @param string|int $value
464
     * @param string|int $type
465
     *
466
     * @return string|int
467
     */
468 18
    private function formatParameter($value, $type)
469
    {
470 18
        if (is_string($type) && Type::hasType($type)) {
471 13
            return Type::getType($type)->convertToDatabaseValue(
472 13
                $value,
473 13
                $this->connection->getDatabasePlatform()
474
            );
475
        }
476
477 5
        return $this->parameterToString($value);
478
    }
479
480
    /**
481
     * @param int[]|bool[]|string[]|array|int|string|bool $value
482
     */
483 5
    private function parameterToString($value) : string
484
    {
485 5
        if (is_array($value)) {
486 3
            return implode(', ', array_map([$this, 'parameterToString'], $value));
487
        }
488
489 5
        if (is_int($value) || is_string($value)) {
490 3
            return (string) $value;
491
        }
492
493 2
        if (is_bool($value)) {
0 ignored issues
show
introduced by
The condition is_bool($value) is always true.
Loading history...
494 1
            return $value === true ? 'true' : 'false';
495
        }
496
497 1
        return '?';
498
    }
499
}
500