Passed
Pull Request — master (#827)
by Luís
02:35
created

Executor::executeVersionExecutionResult()   A

Complexity

Conditions 4
Paths 5

Size

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