Passed
Pull Request — master (#739)
by Michael
02:38
created

Executor   A

Complexity

Total Complexity 31

Size/Duplication

Total Lines 340
Duplicated Lines 0 %

Test Coverage

Coverage 99.35%

Importance

Changes 0
Metric Value
dl 0
loc 340
ccs 153
cts 154
cp 0.9935
rs 9.92
c 0
b 0
f 0
wmc 31

13 Methods

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