Failed Conditions
Push — master ( bcb059...03ab35 )
by Andreas
13s
created

Version   B

Complexity

Total Complexity 53

Size/Duplication

Total Lines 461
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 12

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 53
lcom 1
cbo 12
dl 0
loc 461
ccs 179
cts 179
cp 1
rs 7.4757
c 0
b 0
f 0

20 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 executeRegisteredSql() 0 29 6
A dispatchEvent() 0 9 1
B addSql() 0 17 5
A outputSqlQuery() 0 13 1
B formatParamsForOutput() 0 20 5

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 115
    public function __construct(Configuration $configuration, $version, $class, SchemaDiffProviderInterface $schemaProvider = null)
90
    {
91 115
        $this->configuration = $configuration;
92 115
        $this->outputWriter  = $configuration->getOutputWriter();
93 115
        $this->class         = $class;
94 115
        $this->connection    = $configuration->getConnection();
95 115
        $this->migration     = new $class($this);
96 115
        $this->version       = $version;
97
98 115
        if ($schemaProvider !== null) {
99 1
            $this->schemaProvider = $schemaProvider;
100
        }
101 115
        if ($schemaProvider === null) {
102 114
            $schemaProvider       = new SchemaDiffProvider(
103 114
                $this->connection->getSchemaManager(),
104 114
                $this->connection->getDatabasePlatform()
105
            );
106 114
            $this->schemaProvider = LazySchemaDiffProvider::fromDefaultProxyFactoryConfiguration($schemaProvider);
107
        }
108 115
    }
109
110
    /**
111
     * Returns the string version in the format YYYYMMDDHHMMSS
112
     *
113
     * @return string $version
114
     */
115 77
    public function getVersion()
116
    {
117 77
        return $this->version;
118
    }
119
120
    /**
121
     * Returns the Migrations Configuration object instance
122
     *
123
     * @return Configuration $configuration
124
     */
125 111
    public function getConfiguration()
126
    {
127 111
        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 41
    public function markMigrated()
141
    {
142 41
        $this->markVersion('up');
143 41
    }
144
145 41
    private function markVersion($direction)
146
    {
147 41
        $action = $direction === 'up' ? 'insert' : 'delete';
148
149 41
        $this->configuration->createMigrationTable();
150 41
        $this->connection->$action(
151 41
            $this->configuration->getMigrationsTableName(),
152 41
            [$this->configuration->getMigrationsColumnName() => $this->version]
153
        );
154 41
    }
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 44
    public function addSql($sql, array $params = [], array $types = [])
169
    {
170 44
        if (is_array($sql)) {
171 43
            foreach ($sql as $key => $query) {
172 14
                $this->sql[] = $query;
173 14
                if ( ! empty($params[$key])) {
174 1
                    $queryTypes = $types[$key] ?? [];
175 43
                    $this->addQueryParams($params[$key], $queryTypes);
176
                }
177
            }
178
        } else {
179 25
            $this->sql[] = $sql;
180 25
            if ( ! empty($params)) {
181 13
                $this->addQueryParams($params, $types);
182
            }
183
        }
184 44
    }
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 14
    private function addQueryParams($params, $types)
191
    {
192 14
        $index                = count($this->sql) - 1;
193 14
        $this->params[$index] = $params;
194 14
        $this->types[$index]  = $types;
195 14
    }
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 6
    public function getMigration()
230
    {
231 6
        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 45
    public function execute($direction, $dryRun = false, $timeAllQueries = false)
250
    {
251 45
        $this->dispatchEvent(Events::onMigrationsVersionExecuting, $direction, $dryRun);
252
253 45
        $this->sql = [];
254
255 45
        $transaction = $this->migration->isTransactional();
256 45
        if ($transaction) {
257
            //only start transaction if in transactional mode
258 45
            $this->connection->beginTransaction();
259
        }
260
261
        try {
262 45
            $migrationStart = microtime(true);
263
264 45
            $this->state = self::STATE_PRE;
265 45
            $fromSchema  = $this->schemaProvider->createFromSchema();
266
267 45
            $this->migration->{'pre' . ucfirst($direction)}($fromSchema);
268
269 43
            if ($direction === self::DIRECTION_UP) {
270 41
                $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 43
            $this->state = self::STATE_EXEC;
276
277 43
            $toSchema = $this->schemaProvider->createToSchema($fromSchema);
278 43
            $this->migration->$direction($toSchema);
279
280 42
            $this->addSql($this->schemaProvider->getSqlDiffToMigrate($fromSchema, $toSchema));
281
282 42
            $this->executeRegisteredSql($dryRun, $timeAllQueries);
283
284 42
            $this->state = self::STATE_POST;
285 42
            $this->migration->{'post' . ucfirst($direction)}($toSchema);
286
287 42
            if ( ! $dryRun) {
288 29
                if ($direction === self::DIRECTION_UP) {
289 29
                    $this->markMigrated();
290
                } else {
291 6
                    $this->markNotMigrated();
292
                }
293
            }
294
295 42
            $migrationEnd = microtime(true);
296 42
            $this->time   = round($migrationEnd - $migrationStart, 2);
297 42
            if ($direction === self::DIRECTION_UP) {
298 40
                $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 42
            if ($transaction) {
304
                //commit only if running in transactional mode
305 42
                $this->connection->commit();
306
            }
307
308 42
            $this->state = self::STATE_NONE;
309
310 42
            $this->dispatchEvent(Events::onMigrationsVersionExecuted, $direction, $dryRun);
311
312 42
            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 20
    private function outputQueryTime($queryStart, $timeAllQueries = false)
369
    {
370 20
        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 20
    }
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 42
    private function executeRegisteredSql($dryRun = false, $timeAllQueries = false)
394
    {
395 42
        if ( ! $dryRun) {
396 29
            if ( ! empty($this->sql)) {
397 18
                foreach ($this->sql as $key => $query) {
398 18
                    $queryStart = microtime(true);
399
400 18
                    if ( ! isset($this->params[$key])) {
401 17
                        $this->outputWriter->write('     <comment>-></comment> ' . $query);
402 17
                        $this->connection->executeQuery($query);
403
                    } else {
404 7
                        $this->outputWriter->write(sprintf('    <comment>-</comment> %s (with parameters)', $query));
405 7
                        $this->connection->executeQuery($query, $this->params[$key], $this->types[$key]);
406
                    }
407
408 18
                    $this->outputQueryTime($queryStart, $timeAllQueries);
409
                }
410
            } else {
411 11
                $this->outputWriter->write(sprintf(
412 11
                    '<error>Migration %s was executed but did not result in any SQL statements.</error>',
413 29
                    $this->version
414
                ));
415
            }
416
        } else {
417 13
            foreach ($this->sql as $idx => $query) {
418 13
                $this->outputSqlQuery($idx, $query);
419
            }
420
        }
421 42
    }
422
423
    /**
424
     * Outputs a SQL query via the `OutputWriter`.
425
     *
426
     * @param int $idx The SQL query index. Used to look up params.
427
     * @param string $query the query to output
428
     * @return void
429
     */
430 13
    private function outputSqlQuery($idx, $query)
431
    {
432 13
        $params = $this->formatParamsForOutput(
433 13
            $this->params[$idx] ?? [],
434 13
            $this->types[$idx] ?? []
435
        );
436
437 13
        $this->outputWriter->write(rtrim(sprintf(
438 13
            '     <comment>-></comment> %s %s',
439 13
            $query,
440 13
            $params
441
        )));
442 13
    }
443
444
    /**
445
     * Formats a set of sql parameters for output with dry run.
446
     *
447
     * @param array $params The query parameters
448
     * @param array $types The types of the query params. Default type is a string
449
     * @return string|null a string of the parameters present.
450
     */
451 13
    private function formatParamsForOutput(array $params, array $types)
452
    {
453 13
        if (empty($params)) {
454 8
            return '';
455
        }
456
457 6
        $platform = $this->connection->getDatabasePlatform();
458 6
        $out      = [];
459 6
        foreach ($params as $key => $value) {
460 6
            $type = $types[$key] ?? 'string';
461 6
            if (Type::hasType($type)) {
462 5
                $outval = Type::getType($type)->convertToDatabaseValue($value, $platform);
463
            } else {
464 1
                $outval = '?';
465
            }
466 6
            $out[] = is_string($key) ? sprintf(':%s => %s', $key, $outval) : $outval;
467
        }
468
469 6
        return sprintf('with parameters (%s)', implode(', ', $out));
470
    }
471
472 45
    private function dispatchEvent($eventName, $direction, $dryRun)
473
    {
474 45
        $this->configuration->dispatchEvent($eventName, new MigrationsVersionEventArgs(
475 45
            $this,
476 45
            $this->configuration,
477 45
            $direction,
478 45
            $dryRun
479
        ));
480 45
    }
481
}
482