Failed Conditions
Pull Request — master (#610)
by Mike
04:28
created

Version   C

Complexity

Total Complexity 60

Size/Duplication

Total Lines 484
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 12

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 60
lcom 1
cbo 12
dl 0
loc 484
ccs 189
cts 189
cp 1
rs 6.0975
c 0
b 0
f 0

22 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 20 3
A getVersion() 0 4 1
A getConfiguration() 0 4 1
A isMigrated() 0 4 1
A markMigrated() 0 4 1
A markVersion() 0 10 2
A markNotMigrated() 0 4 1
A addQueryParams() 0 6 1
A getMigration() 0 4 1
A outputQueryTime() 0 9 2
A getTime() 0 4 1
A __toString() 0 4 1
A writeSqlFile() 0 19 2
F execute() 0 104 13
A getExecutionState() 0 13 4
B addSql() 0 17 5
B executeRegisteredSql() 0 28 6
A outputSqlQuery() 0 13 1
A formatParamsForOutput() 0 15 4
A dispatchEvent() 0 9 1
A formatParameter() 0 11 2
B parameterToString() 0 16 6

How to fix   Complexity   

Complex Class

Complex classes like Version often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Version, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Doctrine\DBAL\Migrations;
4
5
use Doctrine\DBAL\Migrations\Configuration\Configuration;
6
use Doctrine\DBAL\Migrations\Event\MigrationsVersionEventArgs;
7
use Doctrine\DBAL\Migrations\Provider\LazySchemaDiffProvider;
8
use Doctrine\DBAL\Migrations\Provider\SchemaDiffProvider;
9
use Doctrine\DBAL\Migrations\Provider\SchemaDiffProviderInterface;
10
use Doctrine\DBAL\Types\Type;
11
12
/**
13
 * Class which wraps a migration version and allows execution of the
14
 * individual migration version up or down method.
15
 *
16
 * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
17
 * @link        www.doctrine-project.org
18
 * @since       2.0
19
 * @author      Jonathan H. Wage <[email protected]>
20
 */
21
class Version
22
{
23
    const STATE_NONE = 0;
24
    const STATE_PRE  = 1;
25
    const STATE_EXEC = 2;
26
    const STATE_POST = 3;
27
28
    const DIRECTION_UP   = 'up';
29
    const DIRECTION_DOWN = 'down';
30
31
    /**
32
     * The Migrations Configuration instance for this migration
33
     *
34
     * @var Configuration
35
     */
36
    private $configuration;
37
38
    /**
39
     * The OutputWriter object instance used for outputting information
40
     *
41
     * @var OutputWriter
42
     */
43
    private $outputWriter;
44
45
    /**
46
     * The version in timestamp format (YYYYMMDDHHMMSS)
47
     *
48
     * @var string
49
     */
50
    private $version;
51
52
    /**
53
     * The migration instance for this version
54
     *
55
     * @var AbstractMigration
56
     */
57
    private $migration;
58
59
    /**
60
     * @var \Doctrine\DBAL\Connection
61
     */
62
    private $connection;
63
64
    /**
65
     * @var string
66
     */
67
    private $class;
68
69
    /** The array of collected SQL statements for this version */
70
    private $sql = [];
71
72
    /** The array of collected parameters for SQL statements for this version */
73
    private $params = [];
74
75
    /** The array of collected types for SQL statements for this version */
76
    private $types = [];
77
78
    /** The time in seconds that this migration version took to execute */
79
    private $time;
80
81
    /**
82
     * @var int
83
     */
84
    private $state = self::STATE_NONE;
85
86
    /** @var SchemaDiffProviderInterface */
87
    private $schemaProvider;
88
89 121
    public function __construct(Configuration $configuration, $version, $class, SchemaDiffProviderInterface $schemaProvider = null)
90
    {
91 121
        $this->configuration = $configuration;
92 121
        $this->outputWriter  = $configuration->getOutputWriter();
93 121
        $this->class         = $class;
94 121
        $this->connection    = $configuration->getConnection();
95 121
        $this->migration     = new $class($this);
96 121
        $this->version       = $version;
97
98 121
        if ($schemaProvider !== null) {
99 1
            $this->schemaProvider = $schemaProvider;
100
        }
101 121
        if ($schemaProvider === null) {
102 120
            $schemaProvider       = new SchemaDiffProvider(
103 120
                $this->connection->getSchemaManager(),
104 120
                $this->connection->getDatabasePlatform()
105
            );
106 120
            $this->schemaProvider = LazySchemaDiffProvider::fromDefaultProxyFactoryConfiguration($schemaProvider);
107
        }
108 121
    }
109
110
    /**
111
     * Returns the string version in the format YYYYMMDDHHMMSS
112
     *
113
     * @return string $version
114
     */
115 78
    public function getVersion()
116
    {
117 78
        return $this->version;
118
    }
119
120
    /**
121
     * Returns the Migrations Configuration object instance
122
     *
123
     * @return Configuration $configuration
124
     */
125 117
    public function getConfiguration()
126
    {
127 117
        return $this->configuration;
128
    }
129
130
    /**
131
     * Check if this version has been migrated or not.
132
     *
133
     * @return boolean
134
     */
135 15
    public function isMigrated()
136
    {
137 15
        return $this->configuration->hasVersionMigrated($this);
138
    }
139
140 43
    public function markMigrated()
141
    {
142 43
        $this->markVersion('up');
143 43
    }
144
145 43
    private function markVersion($direction)
146
    {
147 43
        $action = $direction === 'up' ? 'insert' : 'delete';
148
149 43
        $this->configuration->createMigrationTable();
150 43
        $this->connection->$action(
151 43
            $this->configuration->getMigrationsTableName(),
152 43
            [$this->configuration->getMigrationsColumnName() => $this->version]
153
        );
154 43
    }
155
156 9
    public function markNotMigrated()
157
    {
158 9
        $this->markVersion('down');
159 9
    }
160
161
    /**
162
     * Add some SQL queries to this versions migration
163
     *
164
     * @param array|string $sql
165
     * @param array        $params
166
     * @param array        $types
167
     */
168 49
    public function addSql($sql, array $params = [], array $types = [])
169
    {
170 49
        if (is_array($sql)) {
171 48
            foreach ($sql as $key => $query) {
172 14
                $this->sql[] = $query;
173 14
                if ( ! empty($params[$key])) {
174 1
                    $queryTypes = $types[$key] ?? [];
175 48
                    $this->addQueryParams($params[$key], $queryTypes);
176
                }
177
            }
178
        } else {
179 30
            $this->sql[] = $sql;
180 30
            if ( ! empty($params)) {
181 18
                $this->addQueryParams($params, $types);
182
            }
183
        }
184 49
    }
185
186
    /**
187
     * @param mixed[] $params Array of prepared statement parameters
188
     * @param string[] $types Array of the types of each statement parameters
189
     */
190 19
    private function addQueryParams($params, $types)
191
    {
192 19
        $index                = count($this->sql) - 1;
193 19
        $this->params[$index] = $params;
194 19
        $this->types[$index]  = $types;
195 19
    }
196
197
    /**
198
     * Write a migration SQL file to the given path
199
     *
200
     * @param string $path      The path to write the migration SQL file.
201
     * @param string $direction The direction to execute.
202
     *
203
     * @return boolean $written
204
     * @throws MigrationException
205
     */
206 9
    public function writeSqlFile($path, $direction = self::DIRECTION_UP)
207
    {
208 9
        $queries = $this->execute($direction, true);
209
210 9
        if ( ! empty($this->params)) {
211 1
            throw MigrationException::migrationNotConvertibleToSql($this->class);
212
        }
213
214 8
        $this->outputWriter->write("\n-- Version " . $this->version . "\n");
215
216 8
        $sqlQueries = [$this->version => $queries];
217
218
        /*
219
         * Since the configuration object changes during the creation we cannot inject things
220
         * properly, so I had to violate LoD here (so please, let's find a way to solve it on v2).
221
         */
222 8
        return $this->configuration->getQueryWriter()
223 8
                                   ->write($path, $direction, $sqlQueries);
224
    }
225
226
    /**
227
     * @return AbstractMigration
228
     */
229 11
    public function getMigration()
230
    {
231 11
        return $this->migration;
232
    }
233
234
    /**
235
     * Execute this migration version up or down and and return the SQL.
236
     * We are only allowing the addSql call and the schema modification to take effect in the up and down call.
237
     * This is necessary to ensure that the migration is revertable.
238
     * The schema is passed to the pre and post method only to be able to test the presence of some table, And the
239
     * connection that can get used trough it allow for the test of the presence of records.
240
     *
241
     * @param string  $direction      The direction to execute the migration.
242
     * @param boolean $dryRun         Whether to not actually execute the migration SQL and just do a dry run.
243
     * @param boolean $timeAllQueries Measuring or not the execution time of each SQL query.
244
     *
245
     * @return array $sql
246
     *
247
     * @throws \Exception when migration fails
248
     */
249 50
    public function execute($direction, $dryRun = false, $timeAllQueries = false)
250
    {
251 50
        $this->dispatchEvent(Events::onMigrationsVersionExecuting, $direction, $dryRun);
252
253 50
        $this->sql = [];
254
255 50
        $transaction = $this->migration->isTransactional();
256 50
        if ($transaction) {
257
            //only start transaction if in transactional mode
258 50
            $this->connection->beginTransaction();
259
        }
260
261
        try {
262 50
            $migrationStart = microtime(true);
263
264 50
            $this->state = self::STATE_PRE;
265 50
            $fromSchema  = $this->schemaProvider->createFromSchema();
266
267 50
            $this->migration->{'pre' . ucfirst($direction)}($fromSchema);
268
269 48
            if ($direction === self::DIRECTION_UP) {
270 46
                $this->outputWriter->write("\n" . sprintf('  <info>++</info> migrating <comment>%s</comment>', $this->version) . "\n");
271
            } else {
272 8
                $this->outputWriter->write("\n" . sprintf('  <info>--</info> reverting <comment>%s</comment>', $this->version) . "\n");
273
            }
274
275 48
            $this->state = self::STATE_EXEC;
276
277 48
            $toSchema = $this->schemaProvider->createToSchema($fromSchema);
278 48
            $this->migration->$direction($toSchema);
279
280 47
            $this->addSql($this->schemaProvider->getSqlDiffToMigrate($fromSchema, $toSchema));
281
282 47
            $this->executeRegisteredSql($dryRun, $timeAllQueries);
283
284 47
            $this->state = self::STATE_POST;
285 47
            $this->migration->{'post' . ucfirst($direction)}($toSchema);
286
287 47
            if ( ! $dryRun) {
288 30
                if ($direction === self::DIRECTION_UP) {
289 30
                    $this->markMigrated();
290
                } else {
291 6
                    $this->markNotMigrated();
292
                }
293
            }
294
295 47
            $migrationEnd = microtime(true);
296 47
            $this->time   = round($migrationEnd - $migrationStart, 2);
297 47
            if ($direction === self::DIRECTION_UP) {
298 45
                $this->outputWriter->write(sprintf("\n  <info>++</info> migrated (%ss)", $this->time));
299
            } else {
300 8
                $this->outputWriter->write(sprintf("\n  <info>--</info> reverted (%ss)", $this->time));
301
            }
302
303 47
            if ($transaction) {
304
                //commit only if running in transactional mode
305 47
                $this->connection->commit();
306
            }
307
308 47
            $this->state = self::STATE_NONE;
309
310 47
            $this->dispatchEvent(Events::onMigrationsVersionExecuted, $direction, $dryRun);
311
312 47
            return $this->sql;
313 7
        } catch (SkipMigrationException $e) {
314 6
            if ($transaction) {
315
                //only rollback transaction if in transactional mode
316 6
                $this->connection->rollBack();
317
            }
318
319 6
            if ($dryRun === false) {
320
                // now mark it as migrated
321 5
                if ($direction === self::DIRECTION_UP) {
322 5
                    $this->markMigrated();
323
                } else {
324 1
                    $this->markNotMigrated();
325
                }
326
            }
327
328 6
            $this->outputWriter->write(sprintf("\n  <info>SS</info> skipped (Reason: %s)", $e->getMessage()));
329
330 6
            $this->state = self::STATE_NONE;
331
332 6
            $this->dispatchEvent(Events::onMigrationsVersionSkipped, $direction, $dryRun);
333
334 6
            return [];
335 1
        } catch (\Exception $e) {
336 1
            $this->outputWriter->write(sprintf(
337 1
                '<error>Migration %s failed during %s. Error %s</error>',
338 1
                $this->version,
339 1
                $this->getExecutionState(),
340 1
                $e->getMessage()
341
            ));
342
343 1
            if ($transaction) {
344
                //only rollback transaction if in transactional mode
345 1
                $this->connection->rollBack();
346
            }
347
348 1
            $this->state = self::STATE_NONE;
349
350 1
            throw $e;
351
        }
352
    }
353
354 8
    public function getExecutionState()
355
    {
356 8
        switch ($this->state) {
357 8
            case self::STATE_PRE:
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
358 1
                return 'Pre-Checks';
359 7
            case self::STATE_POST:
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
360 1
                return 'Post-Checks';
361 6
            case self::STATE_EXEC:
362 2
                return 'Execution';
363
            default:
364 4
                return 'No State';
365
        }
366
    }
367
368 21
    private function outputQueryTime($queryStart, $timeAllQueries = false)
369
    {
370 21
        if ($timeAllQueries !== false) {
371 1
            $queryEnd  = microtime(true);
372 1
            $queryTime = round($queryEnd - $queryStart, 4);
373
374 1
            $this->outputWriter->write(sprintf("  <info>%ss</info>", $queryTime));
375
        }
376 21
    }
377
378
    /**
379
     * Returns the time this migration version took to execute
380
     *
381
     * @return integer $time The time this migration version took to execute
382
     */
383 26
    public function getTime()
384
    {
385 26
        return $this->time;
386
    }
387
388 2
    public function __toString()
389
    {
390 2
        return $this->version;
391
    }
392
393 47
    private function executeRegisteredSql($dryRun = false, $timeAllQueries = false)
394
    {
395 47
        if ( ! $dryRun) {
396 30
            if ( ! empty($this->sql)) {
397 19
                foreach ($this->sql as $key => $query) {
398 19
                    $queryStart = microtime(true);
399
400 19
                    $this->outputSqlQuery($key, $query);
401 19
                    if ( ! isset($this->params[$key])) {
402 18
                        $this->connection->executeQuery($query);
403
                    } else {
404 8
                        $this->connection->executeQuery($query, $this->params[$key], $this->types[$key]);
405
                    }
406
407 19
                    $this->outputQueryTime($queryStart, $timeAllQueries);
408
                }
409
            } else {
410 11
                $this->outputWriter->write(sprintf(
411 11
                    '<error>Migration %s was executed but did not result in any SQL statements.</error>',
412 30
                    $this->version
413
                ));
414
            }
415
        } else {
416 17
            foreach ($this->sql as $idx => $query) {
417 17
                $this->outputSqlQuery($idx, $query);
418
            }
419
        }
420 47
    }
421
422
    /**
423
     * Outputs a SQL query via the `OutputWriter`.
424
     *
425
     * @param int $idx The SQL query index. Used to look up params.
426
     * @param string $query the query to output
427
     * @return void
428
     */
429 36
    private function outputSqlQuery($idx, $query)
430
    {
431 36
        $params = $this->formatParamsForOutput(
432 36
            $this->params[$idx] ?? [],
433 36
            $this->types[$idx] ?? []
434
        );
435
436 36
        $this->outputWriter->write(rtrim(sprintf(
437 36
            '     <comment>-></comment> %s %s',
438 36
            $query,
439 36
            $params
440
        )));
441 36
    }
442
443
    /**
444
     * Formats a set of sql parameters for output with dry run.
445
     *
446
     * @param array $params The query parameters
447
     * @param array $types The types of the query params. Default type is a string
448
     * @return string|null a string of the parameters present.
449
     */
450 36
    private function formatParamsForOutput(array $params, array $types)
451
    {
452 36
        if (empty($params)) {
453 26
            return '';
454
        }
455
456 18
        $out = [];
457 18
        foreach ($params as $key => $value) {
458 18
            $type   = $types[$key] ?? 'string';
459 18
            $outval = '[' . $this->formatParameter($value, $type) . ']';
460 18
            $out[]  = is_string($key) ? sprintf(':%s => %s', $key, $outval) : $outval;
461
        }
462
463 18
        return sprintf('with parameters (%s)', implode(', ', $out));
464
    }
465
466 50
    private function dispatchEvent($eventName, $direction, $dryRun)
467
    {
468 50
        $this->configuration->dispatchEvent($eventName, new MigrationsVersionEventArgs(
469 50
            $this,
470 50
            $this->configuration,
471 50
            $direction,
472 50
            $dryRun
473
        ));
474 50
    }
475
476 18
    private function formatParameter($value, string $type) : string
477
    {
478 18
        if (Type::hasType($type)) {
479 13
            return Type::getType($type)->convertToDatabaseValue(
480 13
                $value,
481 13
                $this->connection->getDatabasePlatform()
482
            );
483
        }
484
485 5
        return $this->parameterToString($value);
486
    }
487
488 5
    private function parameterToString($value) : string
489
    {
490 5
        if (is_array($value)) {
491 3
            return implode(', ', array_map([$this, 'parameterToString'], $value));
492
        }
493
494 5
        if (is_int($value) || is_string($value)) {
495 3
            return (string) $value;
496
        }
497
498 2
        if (is_bool($value)) {
499 1
            return $value === true ? 'true' : 'false';
500
        }
501
502 1
        return '?';
503
    }
504
}
505