Completed
Push — master ( 4e25ef...bcbed0 )
by Jonathan
12s
created

Version::addSql()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 5

Importance

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