Completed
Push — master ( bae954...5175a4 )
by Jonathan
08:48 queued 11s
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 183
    public function __construct(
60
        Configuration $configuration,
61
        Connection $connection,
62
        SchemaDiffProviderInterface $schemaProvider,
63
        OutputWriter $outputWriter,
64
        ParameterFormatterInterface $parameterFormatter,
65
        Stopwatch $stopwatch
66
    ) {
67 183
        $this->configuration      = $configuration;
68 183
        $this->connection         = $connection;
69 183
        $this->schemaProvider     = $schemaProvider;
70 183
        $this->outputWriter       = $outputWriter;
71 183
        $this->parameterFormatter = $parameterFormatter;
72 183
        $this->stopwatch          = $stopwatch;
73 183
    }
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 42
    public function addSql(string $sql, array $params = [], array $types = []) : void
104
    {
105 42
        $this->sql[] = $sql;
106
107 42
        if (count($params) === 0) {
108 28
            return;
109
        }
110
111 22
        $this->addQueryParams($params, $types);
112 22
    }
113
114 56
    public function execute(
115
        Version $version,
116
        AbstractMigration $migration,
117
        string $direction,
118
        ?MigratorConfiguration $migratorConfiguration = null
119
    ) : ExecutionResult {
120 56
        $migratorConfiguration = $migratorConfiguration ?? new MigratorConfiguration();
121
122 56
        $versionExecutionResult = new ExecutionResult();
123
124 56
        $this->startMigration($version, $migration, $direction, $migratorConfiguration);
125
126
        try {
127 56
            $this->executeMigration(
128 56
                $version,
129 56
                $migration,
130 56
                $versionExecutionResult,
131 56
                $direction,
132 56
                $migratorConfiguration
133
            );
134
135 52
            $versionExecutionResult->setSql($this->sql);
136 52
            $versionExecutionResult->setParams($this->params);
137 52
            $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 54
        return $versionExecutionResult;
158
    }
159
160 56
    private function startMigration(
161
        Version $version,
162
        AbstractMigration $migration,
163
        string $direction,
164
        MigratorConfiguration $migratorConfiguration
165
    ) : void {
166 56
        $this->sql    = [];
167 56
        $this->params = [];
168 56
        $this->types  = [];
169
170 56
        $this->configuration->dispatchVersionEvent(
171 56
            $version,
172 56
            Events::onMigrationsVersionExecuting,
173 56
            $direction,
174 56
            $migratorConfiguration->isDryRun()
175
        );
176
177 56
        if (! $migration->isTransactional()) {
178
            return;
179
        }
180
181
        // only start transaction if in transactional mode
182 56
        $this->connection->beginTransaction();
183 56
    }
184
185 56
    private function executeMigration(
186
        Version $version,
187
        AbstractMigration $migration,
188
        ExecutionResult $versionExecutionResult,
189
        string $direction,
190
        MigratorConfiguration $migratorConfiguration
191
    ) : ExecutionResult {
192 56
        $stopwatchEvent = $this->stopwatch->start('execute');
193
194 56
        $version->setState(State::PRE);
195
196 56
        $fromSchema = $this->getFromSchema($migratorConfiguration);
197
198 56
        $migration->{'pre' . ucfirst($direction)}($fromSchema);
199
200 54
        if ($direction === Direction::UP) {
201 51
            $this->outputWriter->write("\n" . sprintf('  <info>++</info> migrating <comment>%s</comment>', $version->getVersion()) . "\n");
202
        } else {
203 9
            $this->outputWriter->write("\n" . sprintf('  <info>--</info> reverting <comment>%s</comment>', $version->getVersion()) . "\n");
204
        }
205
206 54
        $version->setState(State::EXEC);
207
208 54
        $toSchema = $this->schemaProvider->createToSchema($fromSchema);
209
210 54
        $versionExecutionResult->setToSchema($toSchema);
211
212 54
        $migration->$direction($toSchema);
213
214 52
        foreach ($this->schemaProvider->getSqlDiffToMigrate($fromSchema, $toSchema) as $sql) {
215 15
            $this->addSql($sql);
216
        }
217
218 52
        if (count($this->sql) !== 0) {
219 41
            if (! $migratorConfiguration->isDryRun()) {
220 23
                $this->executeVersionExecutionResult($version, $migratorConfiguration);
221
            } else {
222 18
                foreach ($this->sql as $idx => $query) {
223 41
                    $this->outputSqlQuery($idx, $query);
224
                }
225
            }
226
        } else {
227 11
            $this->outputWriter->write(sprintf(
228 11
                '<error>Migration %s was executed but did not result in any SQL statements.</error>',
229 11
                $version->getVersion()
230
            ));
231
        }
232
233 52
        $version->setState(State::POST);
234
235 52
        $migration->{'post' . ucfirst($direction)}($toSchema);
236
237 52
        if (! $migratorConfiguration->isDryRun()) {
238 34
            $version->markVersion($direction);
239
        }
240
241 52
        $stopwatchEvent->stop();
242
243 52
        $versionExecutionResult->setTime($stopwatchEvent->getDuration());
244 52
        $versionExecutionResult->setMemory($stopwatchEvent->getMemory());
245
246 52
        if ($direction === Direction::UP) {
247 49
            $this->outputWriter->write(sprintf(
248 49
                "\n  <info>++</info> migrated (took %sms, used %s memory)",
249 49
                $stopwatchEvent->getDuration(),
250 49
                BytesFormatter::formatBytes($stopwatchEvent->getMemory())
251
            ));
252
        } else {
253 9
            $this->outputWriter->write(sprintf(
254 9
                "\n  <info>--</info> reverted (took %sms, used %s memory)",
255 9
                $stopwatchEvent->getDuration(),
256 9
                BytesFormatter::formatBytes($stopwatchEvent->getMemory())
257
            ));
258
        }
259
260 52
        if ($migration->isTransactional()) {
261
            //commit only if running in transactional mode
262 52
            $this->connection->commit();
263
        }
264
265 52
        $version->setState(State::NONE);
266
267 52
        $this->configuration->dispatchVersionEvent(
268 52
            $version,
269 52
            Events::onMigrationsVersionExecuted,
270 52
            $direction,
271 52
            $migratorConfiguration->isDryRun()
272
        );
273
274 52
        return $versionExecutionResult;
275
    }
276
277 6
    private function skipMigration(
278
        SkipMigration $e,
279
        Version $version,
280
        AbstractMigration $migration,
281
        string $direction,
282
        MigratorConfiguration $migratorConfiguration
283
    ) : void {
284 6
        if ($migration->isTransactional()) {
285
            //only rollback transaction if in transactional mode
286 6
            $this->connection->rollBack();
287
        }
288
289 6
        if (! $migratorConfiguration->isDryRun()) {
290 5
            $version->markVersion($direction);
291
        }
292
293 6
        $this->outputWriter->write(sprintf("\n  <info>SS</info> skipped (Reason: %s)", $e->getMessage()));
294
295 6
        $version->setState(State::NONE);
296
297 6
        $this->configuration->dispatchVersionEvent(
298 6
            $version,
299 6
            Events::onMigrationsVersionSkipped,
300 6
            $direction,
301 6
            $migratorConfiguration->isDryRun()
302
        );
303 6
    }
304
305
    /**
306
     * @throws Throwable
307
     */
308 2
    private function migrationError(Throwable $e, Version $version, AbstractMigration $migration) : void
309
    {
310 2
        $this->outputWriter->write(sprintf(
311 2
            '<error>Migration %s failed during %s. Error %s</error>',
312 2
            $version->getVersion(),
313 2
            $version->getExecutionState(),
314 2
            $e->getMessage()
315
        ));
316
317 2
        if ($migration->isTransactional()) {
318
            //only rollback transaction if in transactional mode
319 2
            $this->connection->rollBack();
320
        }
321
322 2
        $version->setState(State::NONE);
323 2
    }
324
325 23
    private function executeVersionExecutionResult(
326
        Version $version,
327
        MigratorConfiguration $migratorConfiguration
328
    ) : void {
329 23
        foreach ($this->sql as $key => $query) {
330 23
            $stopwatchEvent = $this->stopwatch->start('query');
331
332 23
            $this->outputSqlQuery($key, $query);
333
334 23
            if (! isset($this->params[$key])) {
335 20
                $this->connection->executeQuery($query);
336
            } else {
337 10
                $this->connection->executeQuery($query, $this->params[$key], $this->types[$key]);
338
            }
339
340 23
            $stopwatchEvent->stop();
341
342 23
            if (! $migratorConfiguration->getTimeAllQueries()) {
343 21
                continue;
344
            }
345
346 2
            $this->outputWriter->write(sprintf('  <info>%sms</info>', $stopwatchEvent->getDuration()));
347
        }
348 23
    }
349
350
    /**
351
     * @param mixed[]|int $params
352
     * @param mixed[]|int $types
353
     */
354 22
    private function addQueryParams($params, $types) : void
355
    {
356 22
        $index                = count($this->sql) - 1;
357 22
        $this->params[$index] = $params;
358 22
        $this->types[$index]  = $types;
359 22
    }
360
361 41
    private function outputSqlQuery(int $idx, string $query) : void
362
    {
363 41
        $params = $this->parameterFormatter->formatParameters(
364 41
            $this->params[$idx] ?? [],
365 41
            $this->types[$idx] ?? []
366
        );
367
368 41
        $this->outputWriter->write(rtrim(sprintf(
369 41
            '     <comment>-></comment> %s %s',
370 41
            $query,
371 41
            $params
372
        )));
373 41
    }
374
375 56
    private function getFromSchema(MigratorConfiguration $migratorConfiguration) : Schema
376
    {
377
        // if we're in a dry run, use the from Schema instead of reading the schema from the database
378 56
        if ($migratorConfiguration->isDryRun() && $migratorConfiguration->getFromSchema() !== null) {
379 2
            return $migratorConfiguration->getFromSchema();
380
        }
381
382 56
        return $this->schemaProvider->createFromSchema();
383
    }
384
}
385