Passed
Pull Request — master (#681)
by Jonathan
02:01
created

VersionExecutor::executeVersionExecutionResult()   B

Complexity

Conditions 4
Paths 5

Size

Total Lines 23
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 11
nc 5
nop 3
dl 0
loc 23
ccs 11
cts 11
cp 1
crap 4
rs 8.7972
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\Exception\SkipMigration;
10
use Doctrine\Migrations\Provider\SchemaDiffProviderInterface;
11
use Doctrine\Migrations\Tools\BytesFormatter;
12
use Throwable;
13
use function count;
14
use function rtrim;
15
use function sprintf;
16
use function ucfirst;
17
18
/**
19
 * @internal
20
 */
21
final class VersionExecutor implements VersionExecutorInterface
22
{
23
    /** @var Configuration */
24
    private $configuration;
25
26
    /** @var Connection */
27
    private $connection;
28
29
    /** @var SchemaDiffProviderInterface */
30
    private $schemaProvider;
31
32
    /** @var OutputWriter */
33
    private $outputWriter;
34
35
    /** @var ParameterFormatterInterface */
36
    private $parameterFormatter;
37
38
    /** @var Stopwatch */
39
    private $stopwatch;
40
41
    /** @var string[] */
42
    private $sql = [];
43
44
    /** @var mixed[] */
45
    private $params = [];
46
47
    /** @var mixed[] */
48
    private $types = [];
49
50 174
    public function __construct(
51
        Configuration $configuration,
52
        Connection $connection,
53
        SchemaDiffProviderInterface $schemaProvider,
54
        OutputWriter $outputWriter,
55
        ParameterFormatterInterface $parameterFormatter,
56
        Stopwatch $stopwatch
57
    ) {
58 174
        $this->configuration      = $configuration;
59 174
        $this->connection         = $connection;
60 174
        $this->schemaProvider     = $schemaProvider;
61 174
        $this->outputWriter       = $outputWriter;
62 174
        $this->parameterFormatter = $parameterFormatter;
63 174
        $this->stopwatch          = $stopwatch;
64 174
    }
65
66
    /**
67
     * @return string[]
68
     */
69 1
    public function getSql() : array
70
    {
71 1
        return $this->sql;
72
    }
73
74
    /**
75
     * @return mixed[]
76
     */
77 1
    public function getParams() : array
78
    {
79 1
        return $this->params;
80
    }
81
82
    /**
83
     * @return mixed[]
84
     */
85 1
    public function getTypes() : array
86
    {
87 1
        return $this->types;
88
    }
89
90
    /**
91
     * @param mixed[] $params
92
     * @param mixed[] $types
93
     */
94 42
    public function addSql(string $sql, array $params = [], array $types = []) : void
95
    {
96 42
        $this->sql[] = $sql;
97
98 42
        if (count($params) === 0) {
99 28
            return;
100
        }
101
102 23
        $this->addQueryParams($params, $types);
103 23
    }
104
105 54
    public function execute(
106
        Version $version,
107
        AbstractMigration $migration,
108
        string $direction,
109
        bool $dryRun = false,
110
        bool $timeAllQueries = false
111
    ) : VersionExecutionResult {
112 54
        $versionExecutionResult = new VersionExecutionResult();
113
114 54
        $this->startMigration($version, $migration, $direction, $dryRun);
115
116
        try {
117 54
            $this->executeMigration(
118 54
                $version,
119 54
                $migration,
120 54
                $versionExecutionResult,
121 54
                $direction,
122 54
                $dryRun,
123 54
                $timeAllQueries
124
            );
125
126 51
            $versionExecutionResult->setSql($this->sql);
127 51
            $versionExecutionResult->setParams($this->params);
128 51
            $versionExecutionResult->setTypes($this->types);
129 7
        } catch (SkipMigration $e) {
130 6
            $this->skipMigration(
131 6
                $e,
132 6
                $version,
133 6
                $migration,
134 6
                $direction,
135 6
                $dryRun
136
            );
137
138 6
            $versionExecutionResult->setSkipped(true);
139 1
        } catch (Throwable $e) {
140 1
            $this->migrationError($e, $version, $migration);
141
142 1
            $versionExecutionResult->setError(true);
143 1
            $versionExecutionResult->setException($e);
144
145 1
            throw $e;
146
        }
147
148 53
        return $versionExecutionResult;
149
    }
150
151 54
    private function startMigration(
152
        Version $version,
153
        AbstractMigration $migration,
154
        string $direction,
155
        bool $dryRun
156
    ) : void {
157 54
        $this->sql    = [];
158 54
        $this->params = [];
159 54
        $this->types  = [];
160
161 54
        $this->configuration->dispatchVersionEvent(
162 54
            $version,
163 54
            Events::onMigrationsVersionExecuting,
164 54
            $direction,
165 54
            $dryRun
166
        );
167
168 54
        if (! $migration->isTransactional()) {
169
            return;
170
        }
171
172
        //only start transaction if in transactional mode
173 54
        $this->connection->beginTransaction();
174 54
    }
175
176 54
    private function executeMigration(
177
        Version $version,
178
        AbstractMigration $migration,
179
        VersionExecutionResult $versionExecutionResult,
180
        string $direction,
181
        bool $dryRun,
182
        bool $timeAllQueries
183
    ) : VersionExecutionResult {
184 54
        $stopwatchEvent = $this->stopwatch->start('execute');
185
186 54
        $version->setState(VersionState::PRE);
187
188 54
        $fromSchema = $this->schemaProvider->createFromSchema();
189
190 54
        $migration->{'pre' . ucfirst($direction)}($fromSchema);
191
192 52
        if ($direction === VersionDirection::UP) {
193 49
            $this->outputWriter->write("\n" . sprintf('  <info>++</info> migrating <comment>%s</comment>', $version) . "\n");
194
        } else {
195 9
            $this->outputWriter->write("\n" . sprintf('  <info>--</info> reverting <comment>%s</comment>', $version) . "\n");
196
        }
197
198 52
        $version->setState(VersionState::EXEC);
199
200 52
        $toSchema = $this->schemaProvider->createToSchema($fromSchema);
201
202 52
        $migration->$direction($toSchema);
203
204 51
        foreach ($this->schemaProvider->getSqlDiffToMigrate($fromSchema, $toSchema) as $sql) {
205 14
            $this->addSql($sql);
206
        }
207
208 51
        if (count($this->sql) !== 0) {
209 40
            if (! $dryRun) {
210 22
                $this->executeVersionExecutionResult($version, $dryRun, $timeAllQueries);
211
            } else {
212 18
                foreach ($this->sql as $idx => $query) {
213 40
                    $this->outputSqlQuery($idx, $query);
214
                }
215
            }
216
        } else {
217 11
            $this->outputWriter->write(sprintf(
218 11
                '<error>Migration %s was executed but did not result in any SQL statements.</error>',
219 11
                $version
220
            ));
221
        }
222
223 51
        $version->setState(VersionState::POST);
224
225 51
        $migration->{'post' . ucfirst($direction)}($toSchema);
226
227 51
        if (! $dryRun) {
228 33
            $version->markVersion($direction);
229
        }
230
231 51
        $stopwatchEvent->stop();
232
233 51
        $versionExecutionResult->setTime($stopwatchEvent->getDuration());
234 51
        $versionExecutionResult->setMemory($stopwatchEvent->getMemory());
235
236 51
        if ($direction === VersionDirection::UP) {
237 48
            $this->outputWriter->write(sprintf(
238 48
                "\n  <info>++</info> migrated (took %sms, used %s memory)",
239 48
                $stopwatchEvent->getDuration(),
240 48
                BytesFormatter::formatBytes($stopwatchEvent->getMemory())
241
            ));
242
        } else {
243 9
            $this->outputWriter->write(sprintf(
244 9
                "\n  <info>--</info> reverted (took %sms, used %s memory)",
245 9
                $stopwatchEvent->getDuration(),
246 9
                BytesFormatter::formatBytes($stopwatchEvent->getMemory())
247
            ));
248
        }
249
250 51
        if ($migration->isTransactional()) {
251
            //commit only if running in transactional mode
252 51
            $this->connection->commit();
253
        }
254
255 51
        $version->setState(VersionState::NONE);
256
257 51
        $this->configuration->dispatchVersionEvent(
258 51
            $version,
259 51
            Events::onMigrationsVersionExecuted,
260 51
            $direction,
261 51
            $dryRun
262
        );
263
264 51
        return $versionExecutionResult;
265
    }
266
267 6
    private function skipMigration(
268
        SkipMigration $e,
269
        Version $version,
270
        AbstractMigration $migration,
271
        string $direction,
272
        bool $dryRun
273
    ) : void {
274 6
        if ($migration->isTransactional()) {
275
            //only rollback transaction if in transactional mode
276 6
            $this->connection->rollBack();
277
        }
278
279 6
        if ($dryRun === false) {
280 5
            $version->markVersion($direction);
281
        }
282
283 6
        $this->outputWriter->write(sprintf("\n  <info>SS</info> skipped (Reason: %s)", $e->getMessage()));
284
285 6
        $version->setState(VersionState::NONE);
286
287 6
        $this->configuration->dispatchVersionEvent(
288 6
            $version,
289 6
            Events::onMigrationsVersionSkipped,
290 6
            $direction,
291 6
            $dryRun
292
        );
293 6
    }
294
295
    /**
296
     * @throws Throwable
297
     */
298 1
    private function migrationError(Throwable $e, Version $version, AbstractMigration $migration) : void
299
    {
300 1
        $this->outputWriter->write(sprintf(
301 1
            '<error>Migration %s failed during %s. Error %s</error>',
302 1
            $version,
303 1
            $version->getExecutionState(),
304 1
            $e->getMessage()
305
        ));
306
307 1
        if ($migration->isTransactional()) {
308
            //only rollback transaction if in transactional mode
309 1
            $this->connection->rollBack();
310
        }
311
312 1
        $version->setState(VersionState::NONE);
313 1
    }
314
315 22
    private function executeVersionExecutionResult(
316
        Version $version,
317
        bool $dryRun = false,
318
        bool $timeAllQueries = false
319
    ) : void {
320 22
        foreach ($this->sql as $key => $query) {
321 22
            $stopwatchEvent = $this->stopwatch->start('query');
322
323 22
            $this->outputSqlQuery($key, $query);
324
325 22
            if (! isset($this->params[$key])) {
326 19
                $this->connection->executeQuery($query);
327
            } else {
328 10
                $this->connection->executeQuery($query, $this->params[$key], $this->types[$key]);
329
            }
330
331 22
            $stopwatchEvent->stop();
332
333 22
            if (! $timeAllQueries) {
334 20
                continue;
335
            }
336
337 2
            $this->outputWriter->write(sprintf('  <info>%sms</info>', $stopwatchEvent->getDuration()));
338
        }
339 22
    }
340
341
    /**
342
     * @param mixed[]|int $params
343
     * @param mixed[]|int $types
344
     */
345 23
    private function addQueryParams($params, $types) : void
346
    {
347 23
        $index                = count($this->sql) - 1;
348 23
        $this->params[$index] = $params;
349 23
        $this->types[$index]  = $types;
350 23
    }
351
352 40
    private function outputSqlQuery(int $idx, string $query) : void
353
    {
354 40
        $params = $this->parameterFormatter->formatParameters(
355 40
            $this->params[$idx] ?? [],
356 40
            $this->types[$idx] ?? []
357
        );
358
359 40
        $this->outputWriter->write(rtrim(sprintf(
360 40
            '     <comment>-></comment> %s %s',
361 40
            $query,
362 40
            $params
363
        )));
364 40
    }
365
}
366