Passed
Pull Request — master (#1010)
by
unknown
02:12
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\Tools\BytesFormatter;
21
use Psr\Log\LoggerInterface;
22
use Psr\Log\LogLevel;
23
use Symfony\Component\Stopwatch\Stopwatch;
24
use Throwable;
25
use function count;
26
use function ucfirst;
27
28
/**
29
 * The DbalExecutor class is responsible for executing a single migration version.
30
 *
31
 * @internal
32
 */
33
final class DbalExecutor implements Executor
34
{
35
    /** @var Connection */
36
    private $connection;
37
38
    /** @var SchemaDiffProvider */
39
    private $schemaProvider;
40
41
    /** @var ParameterFormatter */
42
    private $parameterFormatter;
43
44
    /** @var Stopwatch */
45
    private $stopwatch;
46
47
    /** @var Query[] */
48
    private $sql = [];
49
50
    /** @var MetadataStorage */
51
    private $metadataStorage;
52
53
    /** @var LoggerInterface */
54
    private $logger;
55
56
    /** @var EventDispatcher */
57
    private $dispatcher;
58
59 17
    public function __construct(
60
        MetadataStorage $metadataStorage,
61
        EventDispatcher $dispatcher,
62
        Connection $connection,
63
        SchemaDiffProvider $schemaProvider,
64
        LoggerInterface $logger,
65
        ParameterFormatter $parameterFormatter,
66
        Stopwatch $stopwatch
67
    ) {
68 17
        $this->connection         = $connection;
69 17
        $this->schemaProvider     = $schemaProvider;
70 17
        $this->parameterFormatter = $parameterFormatter;
71 17
        $this->stopwatch          = $stopwatch;
72 17
        $this->metadataStorage    = $metadataStorage;
73 17
        $this->logger             = $logger;
74 17
        $this->dispatcher         = $dispatcher;
75 17
    }
76
77
    /**
78
     * @return Query[]
79
     */
80 1
    public function getSql() : array
81
    {
82 1
        return $this->sql;
83
    }
84
85 11
    public function addSql(Query $sqlQuery) : void
86
    {
87 11
        $this->sql[] = $sqlQuery;
88 11
    }
89
90 15
    public function execute(
91
        MigrationPlan $plan,
92
        MigratorConfiguration $configuration
93
    ) : ExecutionResult {
94 15
        $result = new ExecutionResult($plan->getVersion(), $plan->getDirection(), new DateTimeImmutable());
95
96 15
        $this->startMigration($plan, $configuration);
97
98
        try {
99 15
            $this->executeMigration(
100 15
                $plan,
101 15
                $result,
102 15
                $configuration
103
            );
104
105 11
            $result->setSql($this->sql);
106 4
        } catch (SkipMigration $e) {
107 1
            $result->setSkipped(true);
108
109 1
            $this->migrationEnd($e, $plan, $result, $configuration);
110 3
        } catch (Throwable $e) {
111 3
            $result->setError(true, $e);
112
113 3
            $this->migrationEnd($e, $plan, $result, $configuration);
114
115 3
            throw $e;
116
        }
117
118 12
        return $result;
119
    }
120
121 15
    private function startMigration(
122
        MigrationPlan $plan,
123
        MigratorConfiguration $configuration
124
    ) : void {
125 15
        $this->sql = [];
126
127 15
        $this->dispatcher->dispatchVersionEvent(
128 15
            Events::onMigrationsVersionExecuting,
129 15
            $plan,
130 15
            $configuration
131
        );
132
133 15
        if (! $plan->getMigration()->isTransactional()) {
134 1
            return;
135
        }
136
137
        // only start transaction if in transactional mode
138 14
        $this->connection->beginTransaction();
139 14
    }
140
141 15
    private function executeMigration(
142
        MigrationPlan $plan,
143
        ExecutionResult $result,
144
        MigratorConfiguration $configuration
145
    ) : ExecutionResult {
146 15
        $stopwatchEvent = $this->stopwatch->start('execute');
147
148 15
        $migration = $plan->getMigration();
149 15
        $direction = $plan->getDirection();
150
151 15
        $result->setState(State::PRE);
152
153 15
        $fromSchema = $this->getFromSchema($configuration);
154
155 15
        $migration->{'pre' . ucfirst($direction)}($fromSchema);
156
157 15
        $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

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