Completed
Push — master ( 6e739b...0f594c )
by Asmir
22s queued 12s
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 17
    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 17
        $this->connection         = $connection;
68 17
        $this->schemaProvider     = $schemaProvider;
69 17
        $this->parameterFormatter = $parameterFormatter;
70 17
        $this->stopwatch          = $stopwatch;
71 17
        $this->metadataStorage    = $metadataStorage;
72 17
        $this->logger             = $logger;
73 17
        $this->dispatcher         = $dispatcher;
74 17
    }
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 15
    public function execute(
90
        MigrationPlan $plan,
91
        MigratorConfiguration $configuration
92
    ) : ExecutionResult {
93 15
        $result = new ExecutionResult($plan->getVersion(), $plan->getDirection(), new DateTimeImmutable());
94
95 15
        $this->startMigration($plan, $configuration);
96
97
        try {
98 15
            $this->executeMigration(
99 15
                $plan,
100 15
                $result,
101 15
                $configuration
102
            );
103
104 11
            $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 12
        return $result;
118
    }
119
120 15
    private function startMigration(
121
        MigrationPlan $plan,
122
        MigratorConfiguration $configuration
123
    ) : void {
124 15
        $this->sql = [];
125
126 15
        $this->dispatcher->dispatchVersionEvent(
127 15
            Events::onMigrationsVersionExecuting,
128 15
            $plan,
129 15
            $configuration
130
        );
131
132 15
        if (! $plan->getMigration()->isTransactional()) {
133 1
            return;
134
        }
135
136
        // only start transaction if in transactional mode
137 14
        $this->connection->beginTransaction();
138 14
    }
139
140 15
    private function executeMigration(
141
        MigrationPlan $plan,
142
        ExecutionResult $result,
143
        MigratorConfiguration $configuration
144
    ) : ExecutionResult {
145 15
        $stopwatchEvent = $this->stopwatch->start('execute');
146
147 15
        $migration = $plan->getMigration();
148 15
        $direction = $plan->getDirection();
149
150 15
        $result->setState(State::PRE);
151
152 15
        $fromSchema = $this->getFromSchema($configuration);
153
154 15
        $migration->{'pre' . ucfirst($direction)}($fromSchema);
155
156 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

156
        $this->logger->info(/** @scrutinizer ignore-type */ ...$this->getMigrationHeader($plan, $migration, $direction));
Loading history...
157
158 15
        $result->setState(State::EXEC);
159
160 15
        $toSchema = $this->schemaProvider->createToSchema($fromSchema);
161
162 15
        $result->setToSchema($toSchema);
163
164 15
        $migration->$direction($toSchema);
165
166 12
        foreach ($migration->getSql() as $sqlQuery) {
167 10
            $this->addSql($sqlQuery);
168
        }
169
170 12
        foreach ($this->schemaProvider->getSqlDiffToMigrate($fromSchema, $toSchema) as $sql) {
171
            $this->addSql(new Query($sql));
172
        }
173
174 12
        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 2
            $this->logger->warning('Migration {version} was executed but did not result in any SQL statements.', [
184 2
                'version' => (string) $plan->getVersion(),
185
            ]);
186
        }
187
188 12
        $result->setState(State::POST);
189
190 12
        $migration->{'post' . ucfirst($direction)}($toSchema);
191
192 12
        $stopwatchEvent->stop();
193 12
        $periods    = $stopwatchEvent->getPeriods();
194 12
        $lastPeriod = $periods[count($periods) -1];
195
196 12
        $result->setTime((float) $lastPeriod->getDuration()/1000);
197 12
        $result->setMemory($lastPeriod->getMemory());
198
199
        $params = [
200 12
            'version' => (string) $plan->getVersion(),
201 12
            'time' => $lastPeriod->getDuration(),
202 12
            'memory' => BytesFormatter::formatBytes($lastPeriod->getMemory()),
203 12
            'direction' => $direction === Direction::UP ? 'migrated' : 'reverted',
204
        ];
205
206 12
        $this->logger->info('Migration {version} {direction} (took {time}ms, used {memory} memory)', $params);
207
208 12
        if (! $configuration->isDryRun()) {
209 11
            $this->metadataStorage->complete($result);
210
        }
211
212 11
        if ($migration->isTransactional()) {
213
            //commit only if running in transactional mode
214 10
            $this->connection->commit();
215
        }
216
217 11
        $plan->markAsExecuted($result);
218 11
        $result->setState(State::NONE);
219
220 11
        $this->dispatcher->dispatchVersionEvent(
221 11
            Events::onMigrationsVersionExecuted,
222 11
            $plan,
223 11
            $configuration
224
        );
225
226 11
        return $result;
227
    }
228
229
    /**
230
     * @return mixed[]
231
     */
232 15
    private function getMigrationHeader(MigrationPlan $planItem, AbstractMigration $migration, string $direction) : array
233
    {
234 15
        $versionInfo = (string) $planItem->getVersion();
235 15
        $description = $migration->getDescription();
236
237 15
        if ($description !== '') {
238 1
            $versionInfo .= ' (' . $description . ')';
239
        }
240
241 15
        $params = ['version_name' => $versionInfo];
242
243 15
        if ($direction === Direction::UP) {
244 13
            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 15
    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 15
        if ($configuration->isDryRun() && $configuration->getFromSchema() !== null) {
327
            return $configuration->getFromSchema();
328
        }
329
330 15
        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