Completed
Push — master ( 39d367...6607a0 )
by Andreas
14s queued 11s
created

DbalExecutor::executeMigration()   B

Complexity

Conditions 9
Paths 96

Size

Total Lines 87
Code Lines 48

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 45
CRAP Score 9.0008

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 9
eloc 48
c 1
b 0
f 0
nc 96
nop 3
dl 0
loc 87
ccs 45
cts 46
cp 0.9783
crap 9.0008
rs 7.5789

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\Migrations\Version;
6
7
use DateTimeImmutable;
8
use Doctrine\DBAL\Connection;
9
use Doctrine\DBAL\Schema\Schema;
10
use Doctrine\Migrations\AbstractMigration;
11
use Doctrine\Migrations\EventDispatcher;
12
use Doctrine\Migrations\Events;
13
use Doctrine\Migrations\Exception\SkipMigration;
14
use Doctrine\Migrations\Metadata\MigrationPlan;
15
use Doctrine\Migrations\Metadata\Storage\MetadataStorage;
16
use Doctrine\Migrations\MigratorConfiguration;
17
use Doctrine\Migrations\ParameterFormatter;
18
use Doctrine\Migrations\Provider\SchemaDiffProvider;
19
use Doctrine\Migrations\Query\Query;
20
use Doctrine\Migrations\Stopwatch;
21
use Doctrine\Migrations\Tools\BytesFormatter;
22
use Psr\Log\LoggerInterface;
23
use Throwable;
24
use function count;
25
use function ucfirst;
26
27
/**
28
 * The DbalExecutor class is responsible for executing a single migration version.
29
 *
30
 * @internal
31
 */
32
final class DbalExecutor implements Executor
33
{
34
    /** @var Connection */
35
    private $connection;
36
37
    /** @var SchemaDiffProvider */
38
    private $schemaProvider;
39
40
    /** @var ParameterFormatter */
41
    private $parameterFormatter;
42
43
    /** @var Stopwatch */
44
    private $stopwatch;
45
46
    /** @var Query[] */
47
    private $sql = [];
48
49
    /** @var MetadataStorage */
50
    private $metadataStorage;
51
52
    /** @var LoggerInterface */
53
    private $logger;
54
55
    /** @var EventDispatcher */
56
    private $dispatcher;
57
58 16
    public function __construct(
59
        MetadataStorage $metadataStorage,
60
        EventDispatcher $dispatcher,
61
        Connection $connection,
62
        SchemaDiffProvider $schemaProvider,
63
        LoggerInterface $logger,
64
        ParameterFormatter $parameterFormatter,
65
        Stopwatch $stopwatch
66
    ) {
67 16
        $this->connection         = $connection;
68 16
        $this->schemaProvider     = $schemaProvider;
69 16
        $this->parameterFormatter = $parameterFormatter;
70 16
        $this->stopwatch          = $stopwatch;
71 16
        $this->metadataStorage    = $metadataStorage;
72 16
        $this->logger             = $logger;
73 16
        $this->dispatcher         = $dispatcher;
74 16
    }
75
76
    /**
77
     * @return Query[]
78
     */
79 1
    public function getSql() : array
80
    {
81 1
        return $this->sql;
82
    }
83
84 11
    public function addSql(Query $sqlQuery) : void
85
    {
86 11
        $this->sql[] = $sqlQuery;
87 11
    }
88
89 14
    public function execute(
90
        MigrationPlan $plan,
91
        MigratorConfiguration $configuration
92
    ) : ExecutionResult {
93 14
        $result = new ExecutionResult($plan->getVersion(), $plan->getDirection(), new DateTimeImmutable());
94
95 14
        $this->startMigration($plan, $configuration);
96
97
        try {
98 14
            $this->executeMigration(
99 14
                $plan,
100 14
                $result,
101 14
                $configuration
102
            );
103
104 10
            $result->setSql($this->sql);
105 4
        } catch (SkipMigration $e) {
106 1
            $result->setSkipped(true);
107
108 1
            $this->migrationEnd($e, $plan, $result, $configuration);
109 3
        } catch (Throwable $e) {
110 3
            $result->setError(true, $e);
111
112 3
            $this->migrationEnd($e, $plan, $result, $configuration);
113
114 3
            throw $e;
115
        }
116
117 11
        return $result;
118
    }
119
120 14
    private function startMigration(
121
        MigrationPlan $plan,
122
        MigratorConfiguration $configuration
123
    ) : void {
124 14
        $this->sql = [];
125
126 14
        $this->dispatcher->dispatchVersionEvent(
127 14
            Events::onMigrationsVersionExecuting,
128 14
            $plan,
129 14
            $configuration
130
        );
131
132 14
        if (! $plan->getMigration()->isTransactional()) {
133
            return;
134
        }
135
136
        // only start transaction if in transactional mode
137 14
        $this->connection->beginTransaction();
138 14
    }
139
140 14
    private function executeMigration(
141
        MigrationPlan $plan,
142
        ExecutionResult $result,
143
        MigratorConfiguration $configuration
144
    ) : ExecutionResult {
145 14
        $stopwatchEvent = $this->stopwatch->start('execute');
146
147 14
        $migration = $plan->getMigration();
148 14
        $direction = $plan->getDirection();
149
150 14
        $result->setState(State::PRE);
151
152 14
        $fromSchema = $this->getFromSchema($configuration);
153
154 14
        $migration->{'pre' . ucfirst($direction)}($fromSchema);
155
156 14
        $this->logger->info(...$this->getMigrationHeader($plan, $migration, $direction));
0 ignored issues
show
Bug introduced by
It seems like $this->getMigrationHeade...$migration, $direction) can also be of type array<string,string>; however, parameter $message of Psr\Log\LoggerInterface::info() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

156
        $this->logger->info(/** @scrutinizer ignore-type */ ...$this->getMigrationHeader($plan, $migration, $direction));
Loading history...
157
158 14
        $result->setState(State::EXEC);
159
160 14
        $toSchema = $this->schemaProvider->createToSchema($fromSchema);
161
162 14
        $result->setToSchema($toSchema);
163
164 14
        $migration->$direction($toSchema);
165
166 11
        foreach ($migration->getSql() as $sqlQuery) {
167 10
            $this->addSql($sqlQuery);
168
        }
169
170 11
        foreach ($this->schemaProvider->getSqlDiffToMigrate($fromSchema, $toSchema) as $sql) {
171
            $this->addSql(new Query($sql));
172
        }
173
174 11
        if (count($this->sql) !== 0) {
175 10
            if (! $configuration->isDryRun()) {
176 9
                $this->executeResult($configuration);
177
            } else {
178 10
                foreach ($this->sql as $query) {
179 1
                    $this->outputSqlQuery($query);
180
                }
181
            }
182
        } else {
183 1
            $this->logger->warning('Migration {version} was executed but did not result in any SQL statements.', [
184 1
                'version' => (string) $plan->getVersion(),
185
            ]);
186
        }
187
188 11
        $result->setState(State::POST);
189
190 11
        $migration->{'post' . ucfirst($direction)}($toSchema);
191
192 11
        $stopwatchEvent->stop();
193 11
        $periods    = $stopwatchEvent->getPeriods();
194 11
        $lastPeriod = $periods[count($periods) -1];
195
196 11
        $result->setTime((float) $lastPeriod->getDuration()/1000);
197 11
        $result->setMemory($lastPeriod->getMemory());
198
199
        $params = [
200 11
            'version' => (string) $plan->getVersion(),
201 11
            'time' => $lastPeriod->getDuration(),
202 11
            'memory' => BytesFormatter::formatBytes($lastPeriod->getMemory()),
203 11
            'direction' => $direction === Direction::UP ? 'migrated' : 'reverted',
204
        ];
205
206 11
        $this->logger->info('Migration {version} {direction} (took {time}ms, used {memory} memory)', $params);
207
208 11
        if (! $configuration->isDryRun()) {
209 10
            $this->metadataStorage->complete($result);
210
        }
211
212 10
        if ($migration->isTransactional()) {
213
            //commit only if running in transactional mode
214 10
            $this->connection->commit();
215
        }
216
217 10
        $plan->markAsExecuted($result);
218 10
        $result->setState(State::NONE);
219
220 10
        $this->dispatcher->dispatchVersionEvent(
221 10
            Events::onMigrationsVersionExecuted,
222 10
            $plan,
223 10
            $configuration
224
        );
225
226 10
        return $result;
227
    }
228
229
    /**
230
     * @return mixed[]
231
     */
232 14
    private function getMigrationHeader(MigrationPlan $planItem, AbstractMigration $migration, string $direction) : array
233
    {
234 14
        $versionInfo = (string) $planItem->getVersion();
235 14
        $description = $migration->getDescription();
236
237 14
        if ($description !== '') {
238 1
            $versionInfo .= ' (' . $description . ')';
239
        }
240
241 14
        $params = ['version_name' => $versionInfo];
242
243 14
        if ($direction === Direction::UP) {
244 12
            return ['++ migrating {version_name}', $params];
245
        }
246
247 2
        return ['++ reverting {version_name}', $params];
248
    }
249
250 4
    private function migrationEnd(Throwable $e, MigrationPlan $plan, ExecutionResult $result, MigratorConfiguration $configuration) : void
251
    {
252 4
        $migration = $plan->getMigration();
253 4
        if ($migration->isTransactional()) {
254
            //only rollback transaction if in transactional mode
255 4
            $this->connection->rollBack();
256
        }
257
258 4
        $plan->markAsExecuted($result);
259 4
        $this->logResult($e, $result, $plan);
260
261 4
        $this->dispatcher->dispatchVersionEvent(
262 4
            Events::onMigrationsVersionSkipped,
263 4
            $plan,
264 4
            $configuration
265
        );
266 4
    }
267
268 4
    private function logResult(Throwable $e, ExecutionResult $result, MigrationPlan $plan) : void
269
    {
270 4
        if ($result->isSkipped()) {
271 1
            $this->logger->error(
272 1
                'Migration {version} skipped during {state}. Reason: "{reason}"',
273
                [
274 1
                    'version' => (string) $plan->getVersion(),
275 1
                    'reason' => $e->getMessage(),
276 1
                    'state' => $this->getExecutionStateAsString($result->getState()),
277
                ]
278
            );
279 3
        } elseif ($result->hasError()) {
280 3
            $this->logger->error(
281 3
                'Migration {version} failed during {state}. Error: "{error}"',
282
                [
283 3
                    'version' => (string) $plan->getVersion(),
284 3
                    'error' => $e->getMessage(),
285 3
                    'state' => $this->getExecutionStateAsString($result->getState()),
286
                ]
287
            );
288
        }
289 4
    }
290
291 9
    private function executeResult(MigratorConfiguration $configuration) : void
292
    {
293 9
        foreach ($this->sql as $key => $query) {
294 9
            $stopwatchEvent = $this->stopwatch->start('query');
295
296 9
            $this->outputSqlQuery($query);
297
298 9
            if (count($query->getParameters()) === 0) {
299 7
                $this->connection->executeUpdate($query->getStatement());
300
            } else {
301 7
                $this->connection->executeUpdate($query->getStatement(), $query->getParameters(), $query->getTypes());
302
            }
303
304 9
            $stopwatchEvent->stop();
305
306 9
            if (! $configuration->getTimeAllQueries()) {
307 2
                continue;
308
            }
309
310 7
            $this->logger->debug('{duration}ms', [
311 7
                'duration' => $stopwatchEvent->getDuration(),
312
            ]);
313
        }
314 9
    }
315
316 10
    private function outputSqlQuery(Query $query) : void
317
    {
318 10
        $params = $this->parameterFormatter->formatParameters(
319 10
            $query->getParameters(),
320 10
            $query->getTypes()
321
        );
322
323 10
        $this->logger->debug('{query} {params}', [
324 10
            'query' => $query->getStatement(),
325 10
            'params' => $params,
326
        ]);
327 10
    }
328
329 14
    private function getFromSchema(MigratorConfiguration $configuration) : Schema
330
    {
331
        // if we're in a dry run, use the from Schema instead of reading the schema from the database
332 14
        if ($configuration->isDryRun() && $configuration->getFromSchema() !== null) {
333
            return $configuration->getFromSchema();
334
        }
335
336 14
        return $this->schemaProvider->createFromSchema();
337
    }
338
339 4
    private function getExecutionStateAsString(int $state) : string
340
    {
341
        switch ($state) {
342 4
            case State::PRE:
343
                return 'Pre-Checks';
344 4
            case State::POST:
345 1
                return 'Post-Checks';
346 3
            case State::EXEC:
347 3
                return 'Execution';
348
            default:
349
                return 'No State';
350
        }
351
    }
352
}
353