DbalExecutor   A
last analyzed

Complexity

Total Complexity 37

Size/Duplication

Total Lines 319
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 15

Test Coverage

Coverage 97.35%

Importance

Changes 0
Metric Value
wmc 37
lcom 1
cbo 15
dl 0
loc 319
ccs 147
cts 151
cp 0.9735
rs 9.44
c 0
b 0
f 0

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 17 1
A getSql() 0 4 1
A addSql() 0 4 1
A execute() 0 30 3
A startMigration() 0 19 2
C executeMigration() 0 88 9
A getMigrationHeader() 0 17 3
A migrationEnd() 0 17 2
A logResult() 0 22 3
A executeResult() 0 18 3
A outputSqlQuery() 0 16 2
A getFromSchema() 0 9 3
A getExecutionStateAsString() 0 13 4
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 17
59
    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 17
    ) {
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
    }
76
77
    /**
78
     * @return Query[]
79 1
     */
80
    public function getSql() : array
81 1
    {
82
        return $this->sql;
83
    }
84 11
85
    public function addSql(Query $sqlQuery) : void
86 11
    {
87 11
        $this->sql[] = $sqlQuery;
88
    }
89 15
90
    public function execute(
91
        MigrationPlan $plan,
92
        MigratorConfiguration $configuration
93 15
    ) : ExecutionResult {
94
        $result = new ExecutionResult($plan->getVersion(), $plan->getDirection(), new DateTimeImmutable());
95 15
96
        $this->startMigration($plan, $configuration);
97
98 15
        try {
99 15
            $this->executeMigration(
100 15
                $plan,
101 15
                $result,
102
                $configuration
103
            );
104 11
105 4
            $result->setSql($this->sql);
106 1
        } catch (SkipMigration $e) {
107
            $result->setSkipped(true);
108 1
109 3
            $this->migrationEnd($e, $plan, $result, $configuration);
110 3
        } catch (Throwable $e) {
111
            $result->setError(true, $e);
112 3
113
            $this->migrationEnd($e, $plan, $result, $configuration);
114 3
115
            throw $e;
116
        }
117 12
118
        return $result;
119
    }
120 15
121
    private function startMigration(
122
        MigrationPlan $plan,
123
        MigratorConfiguration $configuration
124 15
    ) : void {
125
        $this->sql = [];
126 15
127 15
        $this->dispatcher->dispatchVersionEvent(
128 15
            Events::onMigrationsVersionExecuting,
129 15
            $plan,
130
            $configuration
131
        );
132 15
133 1
        if (! $plan->getMigration()->isTransactional()) {
134
            return;
135
        }
136
137 14
        // only start transaction if in transactional mode
138 14
        $this->connection->beginTransaction();
139
    }
140 15
141
    private function executeMigration(
142
        MigrationPlan $plan,
143
        ExecutionResult $result,
144
        MigratorConfiguration $configuration
145 15
    ) : ExecutionResult {
146
        $stopwatchEvent = $this->stopwatch->start('execute');
147 15
148 15
        $migration = $plan->getMigration();
149
        $direction = $plan->getDirection();
150 15
151
        $result->setState(State::PRE);
152 15
153
        $fromSchema = $this->getFromSchema($configuration);
154 15
155
        $migration->{'pre' . ucfirst($direction)}($fromSchema);
156 15
157
        $this->logger->info(...$this->getMigrationHeader($plan, $migration, $direction));
0 ignored issues
show
Documentation introduced by
$this->getMigrationHeade...$migration, $direction) is of type array<integer,*>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

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