Failed Conditions
Push — master ( 96a683...ed5b12 )
by Asmir
02:46 queued 10s
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 Symfony\Component\Stopwatch\Stopwatch;
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
            $this->outputSqlQuery($query);
295
296 9
            $stopwatchEvent = $this->stopwatch->start('query');
297 9
            $this->connection->executeUpdate($query->getStatement(), $query->getParameters(), $query->getTypes());
298 9
            $stopwatchEvent->stop();
299
300 9
            if (! $configuration->getTimeAllQueries()) {
301 2
                continue;
302
            }
303
304 7
            $this->logger->debug('{duration}ms', [
305 7
                'duration' => $stopwatchEvent->getDuration(),
306
            ]);
307
        }
308 9
    }
309
310 10
    private function outputSqlQuery(Query $query) : void
311
    {
312 10
        $params = $this->parameterFormatter->formatParameters(
313 10
            $query->getParameters(),
314 10
            $query->getTypes()
315
        );
316
317 10
        $this->logger->debug('{query} {params}', [
318 10
            'query' => $query->getStatement(),
319 10
            'params' => $params,
320
        ]);
321 10
    }
322
323 14
    private function getFromSchema(MigratorConfiguration $configuration) : Schema
324
    {
325
        // if we're in a dry run, use the from Schema instead of reading the schema from the database
326 14
        if ($configuration->isDryRun() && $configuration->getFromSchema() !== null) {
327
            return $configuration->getFromSchema();
328
        }
329
330 14
        return $this->schemaProvider->createFromSchema();
331
    }
332
333 4
    private function getExecutionStateAsString(int $state) : string
334
    {
335
        switch ($state) {
336 4
            case State::PRE:
337
                return 'Pre-Checks';
338 4
            case State::POST:
339 1
                return 'Post-Checks';
340 3
            case State::EXEC:
341 3
                return 'Execution';
342
            default:
343
                return 'No State';
344
        }
345
    }
346
}
347