Failed Conditions
Pull Request — master (#940)
by Asmir
02:14
created

TableMetadataStorage::getExecutedMigrations()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 34
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 21
c 1
b 0
f 0
nc 6
nop 0
dl 0
loc 34
ccs 22
cts 22
cp 1
crap 6
rs 8.9617
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\Migrations\Metadata\Storage;
6
7
use DateTimeImmutable;
8
use Doctrine\DBAL\Connection;
9
use Doctrine\DBAL\Connections\MasterSlaveConnection;
10
use Doctrine\DBAL\Platforms\AbstractPlatform;
11
use Doctrine\DBAL\Schema\AbstractSchemaManager;
12
use Doctrine\DBAL\Schema\Comparator;
13
use Doctrine\DBAL\Schema\Table;
14
use Doctrine\DBAL\Schema\TableDiff;
15
use Doctrine\DBAL\Types\Type;
16
use Doctrine\Migrations\Exception\MetadataStorageError;
17
use Doctrine\Migrations\Metadata\AvailableMigration;
18
use Doctrine\Migrations\Metadata\ExecutedMigration;
19
use Doctrine\Migrations\Metadata\ExecutedMigrationsSet;
20
use Doctrine\Migrations\MigrationRepository;
21
use Doctrine\Migrations\Version\Direction;
22
use Doctrine\Migrations\Version\ExecutionResult;
23
use Doctrine\Migrations\Version\Version;
24
use InvalidArgumentException;
25
use function array_change_key_case;
26
use function floatval;
27
use function round;
28
use function sprintf;
29
use function strlen;
30
use function strpos;
31
use function strtolower;
32
use const CASE_LOWER;
33
34
final class TableMetadataStorage implements MetadataStorage
35
{
36
    /** @var Connection */
37
    private $connection;
38
39
    /** @var AbstractSchemaManager */
40
    private $schemaManager;
41
42
    /** @var AbstractPlatform */
43
    private $platform;
44
45
    /** @var TableMetadataStorageConfiguration */
46
    private $configuration;
47
48
    /** @var MigrationRepository|null */
49
    private $migrationRepository;
50
51 67
    public function __construct(
52
        Connection $connection,
53
        ?MetadataStorageConfiguration $configuration = null,
54
        ?MigrationRepository $migrationRepository = null
55
    ) {
56 67
        $this->migrationRepository = $migrationRepository;
57 67
        $this->connection          = $connection;
58 67
        $this->schemaManager       = $connection->getSchemaManager();
59 67
        $this->platform            = $connection->getDatabasePlatform();
60
61 67
        if ($configuration !== null && ! ($configuration instanceof TableMetadataStorageConfiguration)) {
62
            throw new InvalidArgumentException(sprintf('%s accepts only %s as configuration', self::class, TableMetadataStorageConfiguration::class));
63
        }
64
65 67
        $this->configuration = $configuration ?: new TableMetadataStorageConfiguration();
0 ignored issues
show
introduced by
$configuration is of type null, thus it always evaluated to false.
Loading history...
66 67
    }
67
68 54
    public function getExecutedMigrations() : ExecutedMigrationsSet
69
    {
70 54
        if (! $this->isInitialized()) {
71 1
            return new ExecutedMigrationsSet([]);
72
        }
73
74 53
        $this->checkInitialization();
75 51
        $rows = $this->connection->fetchAll(sprintf('SELECT * FROM %s', $this->configuration->getTableName()));
76
77 51
        $migrations = [];
78 51
        foreach ($rows as $row) {
79 32
            $row = array_change_key_case($row, CASE_LOWER);
80
81 32
            $version = new Version($row[strtolower($this->configuration->getVersionColumnName())]);
82
83 32
            $executedAt = $row[strtolower($this->configuration->getExecutedAtColumnName())] ?? '';
84 32
            $executedAt = $executedAt !== ''
85 4
                ? DateTimeImmutable::createFromFormat($this->platform->getDateTimeFormatString(), $executedAt)
86 32
                : null;
87
88 32
            $executionTime = isset($row[strtolower($this->configuration->getExecutionTimeColumnName())])
89 4
                ? floatval($row[strtolower($this->configuration->getExecutionTimeColumnName())]/1000)
90 32
                : null;
91
92 32
            $migration = new ExecutedMigration(
93 32
                $version,
94 32
                $executedAt instanceof DateTimeImmutable ? $executedAt : null,
95 32
                $executionTime
96
            );
97
98 32
            $migrations[(string) $version] = $migration;
99
        }
100
101 51
        return new ExecutedMigrationsSet($migrations);
102
    }
103
104 2
    public function reset() : void
105
    {
106 2
        $this->checkInitialization();
107
108 2
        $this->connection->executeUpdate(
109 2
            sprintf(
110 2
                'DELETE FROM %s WHERE 1 = 1',
111 2
                $this->platform->quoteIdentifier($this->configuration->getTableName())
112
            )
113
        );
114 2
    }
115
116 35
    public function complete(ExecutionResult $result) : void
117
    {
118 35
        $this->checkInitialization();
119
120 35
        if ($result->getDirection() === Direction::DOWN) {
121 5
            $this->connection->delete($this->configuration->getTableName(), [
122 5
                $this->configuration->getVersionColumnName() => (string) $result->getVersion(),
123
            ]);
124
        } else {
125 35
            $this->connection->insert($this->configuration->getTableName(), [
126 35
                $this->configuration->getVersionColumnName() => (string) $result->getVersion(),
127 35
                $this->configuration->getExecutedAtColumnName() => $result->getExecutedAt(),
128 35
                $this->configuration->getExecutionTimeColumnName() => $result->getTime() === null ? null : round($result->getTime()*1000),
129
            ], [
130 35
                Type::STRING,
131
                Type::DATETIME,
132
                Type::INTEGER,
133
            ]);
134
        }
135 35
    }
136
137 63
    public function ensureInitialized() : void
138
    {
139 63
        if (! $this->isInitialized()) {
140 60
            $expectedSchemaChangelog = $this->getExpectedTable();
141 60
            $this->schemaManager->createTable($expectedSchemaChangelog);
142
143 60
            return;
144
        }
145
146 5
        $expectedSchemaChangelog = $this->getExpectedTable();
147 5
        $diff                    = $this->needsUpdate($expectedSchemaChangelog);
148 5
        if ($diff === null) {
149 2
            return;
150
        }
151
152 3
        $this->schemaManager->alterTable($diff);
153 3
        $this->updateMigratedVersionsFromV1orV2toV3();
154 3
    }
155
156 60
    private function needsUpdate(Table $expectedTable) : ?TableDiff
157
    {
158 60
        $comparator   = new Comparator();
159 60
        $currentTable = $this->schemaManager->listTableDetails($this->configuration->getTableName());
160 60
        $diff         = $comparator->diffTable($currentTable, $expectedTable);
161
162 60
        return $diff instanceof TableDiff ? $diff : null;
163
    }
164
165 66
    private function isInitialized() : bool
166
    {
167 66
        if ($this->connection instanceof MasterSlaveConnection) {
168 1
            $this->connection->connect('master');
169
        }
170
171 66
        return $this->schemaManager->tablesExist([$this->configuration->getTableName()]);
172
    }
173
174 58
    private function checkInitialization() : void
175
    {
176 58
        if (! $this->isInitialized()) {
177
            throw MetadataStorageError::notInitialized();
178
        }
179
180 58
        $expectedTable = $this->getExpectedTable();
181 58
        if ($this->needsUpdate($expectedTable) !== null) {
182 2
            throw MetadataStorageError::notUpToDate();
183
        }
184 56
    }
185
186 65
    private function getExpectedTable() : Table
187
    {
188 65
        $schemaChangelog = new Table($this->configuration->getTableName());
189
190 65
        $schemaChangelog->addColumn(
191 65
            $this->configuration->getVersionColumnName(),
192 65
            'string',
193 65
            ['notnull' => true, 'length' => $this->configuration->getVersionColumnLength()]
194
        );
195 65
        $schemaChangelog->addColumn($this->configuration->getExecutedAtColumnName(), 'datetime', ['notnull' => false]);
196 65
        $schemaChangelog->addColumn($this->configuration->getExecutionTimeColumnName(), 'integer', ['notnull' => false]);
197
198 65
        $schemaChangelog->setPrimaryKey([$this->configuration->getVersionColumnName()]);
199
200 65
        return $schemaChangelog;
201
    }
202
203 3
    private function updateMigratedVersionsFromV1orV2toV3() : void
204
    {
205 3
        if ($this->migrationRepository === null) {
206 2
            return;
207
        }
208
209 1
        $availableMigrations = $this->migrationRepository->getMigrations()->getItems();
210 1
        $executedMigrations  = $this->getExecutedMigrations()->getItems();
211
212 1
        foreach ($availableMigrations as $availableMigration) {
213 1
            foreach ($executedMigrations as $k => $executedMigration) {
214 1
                if ($this->isAlreadyV3Format($availableMigration, $executedMigration)) {
215 1
                    continue;
216
                }
217
218 1
                $this->connection->update(
219 1
                    $this->configuration->getTableName(),
220
                    [
221 1
                        $this->configuration->getVersionColumnName() => (string) $availableMigration->getVersion(),
222
                    ],
223
                    [
224 1
                        $this->configuration->getVersionColumnName() => (string) $executedMigration->getVersion(),
225
                    ]
226
                );
227 1
                unset($executedMigrations[$k]);
228
            }
229
        }
230 1
    }
231
232 1
    private function isAlreadyV3Format(AvailableMigration $availableMigration, ExecutedMigration $executedMigration) : bool
233
    {
234 1
        return strpos(
235 1
            (string) $availableMigration->getVersion(),
236 1
            (string) $executedMigration->getVersion()
237 1
        ) !== strlen((string) $availableMigration->getVersion()) -
238 1
                strlen((string) $executedMigration->getVersion());
239
    }
240
}
241