Completed
Push — master ( ca826f...dcd1d0 )
by Asmir
18s queued 15s
created

DbalExecutor::executeMigration()   B

Complexity

Conditions 9
Paths 96

Size

Total Lines 84
Code Lines 46

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 43
CRAP Score 9.0009

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 9
eloc 46
c 1
b 0
f 0
nc 96
nop 3
dl 0
loc 84
ccs 43
cts 44
cp 0.9773
crap 9.0009
rs 7.6226

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