Completed
Pull Request — master (#3610)
by Sergei
31:59 queued 28:53
created

Schema::__construct()   B

Complexity

Conditions 6
Paths 16

Size

Total Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 24
ccs 12
cts 12
cp 1
rs 8.9137
c 0
b 0
f 0
cc 6
nc 16
nop 4
crap 6
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\DBAL\Schema;
6
7
use Doctrine\DBAL\Platforms\AbstractPlatform;
8
use Doctrine\DBAL\Schema\Exception\NamespaceAlreadyExists;
9
use Doctrine\DBAL\Schema\Exception\SequenceAlreadyExists;
10
use Doctrine\DBAL\Schema\Exception\SequenceDoesNotExist;
11
use Doctrine\DBAL\Schema\Exception\TableAlreadyExists;
12
use Doctrine\DBAL\Schema\Exception\TableDoesNotExist;
13
use Doctrine\DBAL\Schema\Visitor\CreateSchemaSqlCollector;
14
use Doctrine\DBAL\Schema\Visitor\DropSchemaSqlCollector;
15
use Doctrine\DBAL\Schema\Visitor\NamespaceVisitor;
16
use Doctrine\DBAL\Schema\Visitor\Visitor;
17
use function array_keys;
18
use function strpos;
19
use function strtolower;
20
21
/**
22
 * Object representation of a database schema.
23
 *
24
 * Different vendors have very inconsistent naming with regard to the concept
25
 * of a "schema". Doctrine understands a schema as the entity that conceptually
26
 * wraps a set of database objects such as tables, sequences, indexes and
27
 * foreign keys that belong to each other into a namespace. A Doctrine Schema
28
 * has nothing to do with the "SCHEMA" defined as in PostgreSQL, it is more
29
 * related to the concept of "DATABASE" that exists in MySQL and PostgreSQL.
30
 *
31
 * Every asset in the doctrine schema has a name. A name consists of either a
32
 * namespace.local name pair or just a local unqualified name.
33
 *
34
 * The abstraction layer that covers a PostgreSQL schema is the namespace of an
35
 * database object (asset). A schema can have a name, which will be used as
36
 * default namespace for the unqualified database objects that are created in
37
 * the schema.
38
 *
39
 * In the case of MySQL where cross-database queries are allowed this leads to
40
 * databases being "misinterpreted" as namespaces. This is intentional, however
41
 * the CREATE/DROP SQL visitors will just filter this queries and do not
42
 * execute them. Only the queries for the currently connected database are
43
 * executed.
44
 */
45
class Schema extends AbstractAsset
46
{
47
    /**
48
     * The namespaces in this schema.
49
     *
50
     * @var array<string, string>
51
     */
52
    private $namespaces = [];
53
54
    /** @var array<string, Table> */
55
    protected $_tables = [];
56
57
    /** @var array<string, Sequence> */
58
    protected $_sequences = [];
59
60
    /** @var SchemaConfig */
61
    protected $_schemaConfig;
62
63
    /**
64
     * @param array<Table>    $tables
65
     * @param array<Sequence> $sequences
66
     * @param array<string>   $namespaces
67
     */
68 1844
    public function __construct(
69
        array $tables = [],
70
        array $sequences = [],
71
        ?SchemaConfig $schemaConfig = null,
72
        array $namespaces = []
73
    ) {
74 1844
        if ($schemaConfig === null) {
75 1584
            $schemaConfig = new SchemaConfig();
76
        }
77 1844
        $this->_schemaConfig = $schemaConfig;
78 1844
        $this->_setName($schemaConfig->getName() ?: 'public');
79
80 1844
        foreach ($namespaces as $namespace) {
81 7
            $this->createNamespace($namespace);
82
        }
83
84 1844
        foreach ($tables as $table) {
85 547
            $this->_addTable($table);
86
        }
87
88 1817
        foreach ($sequences as $sequence) {
89 117
            $this->_addSequence($sequence);
90
        }
91 1790
    }
92
93
    public function hasExplicitForeignKeyIndexes() : bool
94
    {
95
        return $this->_schemaConfig->hasExplicitForeignKeyIndexes();
96
    }
97
98
    /**
99
     * @throws SchemaException
100
     */
101 1439
    protected function _addTable(Table $table) : void
102
    {
103 1439
        $namespaceName = $table->getNamespaceName();
104 1439
        $tableName     = $table->getFullQualifiedName($this->getName());
105
106 1439
        if (isset($this->_tables[$tableName])) {
107 27
            throw TableAlreadyExists::new($tableName);
108
        }
109
110 1439
        if ($namespaceName !== null
111 1439
            && ! $table->isInDefaultNamespace($this->getName())
112 1439
            && ! $this->hasNamespace($namespaceName)) {
113 216
            $this->createNamespace($namespaceName);
114
        }
115
116 1439
        $this->_tables[$tableName] = $table;
117 1439
        $table->setSchemaConfig($this->_schemaConfig);
118 1439
    }
119
120
    /**
121
     * @throws SchemaException
122
     */
123 468
    protected function _addSequence(Sequence $sequence) : void
124
    {
125 468
        $namespaceName = $sequence->getNamespaceName();
126 468
        $seqName       = $sequence->getFullQualifiedName($this->getName());
127
128 468
        if (isset($this->_sequences[$seqName])) {
129 27
            throw SequenceAlreadyExists::new($seqName);
130
        }
131
132 468
        if ($namespaceName !== null
133 468
            && ! $sequence->isInDefaultNamespace($this->getName())
134 468
            && ! $this->hasNamespace($namespaceName)) {
135 27
            $this->createNamespace($namespaceName);
136
        }
137
138 468
        $this->_sequences[$seqName] = $sequence;
139 468
    }
140
141
    /**
142
     * Returns the namespaces of this schema.
143
     *
144
     * @return array<string, string> A list of namespace names.
0 ignored issues
show
Documentation introduced by
The doc-type array<string, could not be parsed: Expected ">" at position 5, but found "end of type". (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
145
     */
146 763
    public function getNamespaces() : array
147
    {
148 763
        return $this->namespaces;
149
    }
150
151
    /**
152
     * Gets all tables of this schema.
153
     *
154
     * @return array<string, Table>
0 ignored issues
show
Documentation introduced by
The doc-type array<string, could not be parsed: Expected ">" at position 5, but found "end of type". (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
155
     */
156 844
    public function getTables() : array
157
    {
158 844
        return $this->_tables;
159
    }
160
161
    /**
162
     * @throws SchemaException
163
     */
164 925
    public function getTable(string $tableName) : Table
165
    {
166 925
        $tableName = $this->getFullQualifiedAssetName($tableName);
167 925
        if (! isset($this->_tables[$tableName])) {
168 27
            throw TableDoesNotExist::new($tableName);
169
        }
170
171 898
        return $this->_tables[$tableName];
172
    }
173
174 1276
    private function getFullQualifiedAssetName(string $name) : string
175
    {
176 1276
        $name = $this->getUnquotedAssetName($name);
177
178 1276
        if (strpos($name, '.') === false) {
179 1168
            $name = $this->getName() . '.' . $name;
180
        }
181
182 1276
        return strtolower($name);
183
    }
184
185
    /**
186
     * Returns the unquoted representation of a given asset name.
187
     */
188 1411
    private function getUnquotedAssetName(string $assetName) : string
189
    {
190 1411
        if ($this->isIdentifierQuoted($assetName)) {
191 54
            return $this->trimQuotes($assetName);
192
        }
193
194 1384
        return $assetName;
195
    }
196
197
    /**
198
     * Does this schema have a namespace with the given name?
199
     */
200 275
    public function hasNamespace(string $namespaceName) : bool
201
    {
202 275
        $namespaceName = strtolower($this->getUnquotedAssetName($namespaceName));
203
204 275
        return isset($this->namespaces[$namespaceName]);
205
    }
206
207
    /**
208
     * Does this schema have a table with the given name?
209
     */
210 898
    public function hasTable(string $tableName) : bool
211
    {
212 898
        $tableName = $this->getFullQualifiedAssetName($tableName);
213
214 898
        return isset($this->_tables[$tableName]);
215
    }
216
217
    /**
218
     * Gets all table names, prefixed with a schema name, even the default one if present.
219
     *
220
     * @return array<int, string>
0 ignored issues
show
Documentation introduced by
The doc-type array<int, could not be parsed: Expected ">" at position 5, but found "end of type". (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
221
     */
222
    public function getTableNames() : array
223
    {
224
        return array_keys($this->_tables);
225
    }
226
227 351
    public function hasSequence(string $sequenceName) : bool
228
    {
229 351
        $sequenceName = $this->getFullQualifiedAssetName($sequenceName);
230
231 351
        return isset($this->_sequences[$sequenceName]);
232
    }
233
234
    /**
235
     * @throws SchemaException
236
     */
237 243
    public function getSequence(string $sequenceName) : Sequence
238
    {
239 243
        $sequenceName = $this->getFullQualifiedAssetName($sequenceName);
240 243
        if (! $this->hasSequence($sequenceName)) {
241 27
            throw SequenceDoesNotExist::new($sequenceName);
242
        }
243
244 216
        return $this->_sequences[$sequenceName];
245
    }
246
247
    /**
248
     * @return array<string, Sequence>
0 ignored issues
show
Documentation introduced by
The doc-type array<string, could not be parsed: Expected ">" at position 5, but found "end of type". (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
249
     */
250 817
    public function getSequences() : array
251
    {
252 817
        return $this->_sequences;
253
    }
254
255
    /**
256
     * Creates a new namespace.
257
     *
258
     * @return $this
259
     *
260
     * @throws SchemaException
261
     */
262 304
    public function createNamespace(string $namespaceName) : self
263
    {
264 304
        $unquotedNamespaceName = strtolower($this->getUnquotedAssetName($namespaceName));
265
266 304
        if (isset($this->namespaces[$unquotedNamespaceName])) {
267 27
            throw NamespaceAlreadyExists::new($unquotedNamespaceName);
268
        }
269
270 304
        $this->namespaces[$unquotedNamespaceName] = $namespaceName;
271
272 304
        return $this;
273
    }
274
275
    /**
276
     * Creates a new table.
277
     */
278 963
    public function createTable(string $tableName) : Table
279
    {
280 963
        $table = new Table($tableName);
281 963
        $this->_addTable($table);
282
283 963
        foreach ($this->_schemaConfig->getDefaultTableOptions() as $name => $value) {
284
            $table->addOption($name, $value);
285
        }
286
287 963
        return $table;
288
    }
289
290
    /**
291
     * Renames a table.
292
     *
293
     * @return $this
294
     */
295 27
    public function renameTable(string $oldTableName, string $newTableName) : self
296
    {
297 27
        $table = $this->getTable($oldTableName);
298 27
        $table->_setName($newTableName);
0 ignored issues
show
Bug introduced by
The method _setName() cannot be called from this context as it is declared protected in class Doctrine\DBAL\Schema\AbstractAsset.

This check looks for access to methods that are not accessible from the current context.

If you need to make a method accessible to another context you can raise its visibility level in the defining class.

Loading history...
299
300 27
        $this->dropTable($oldTableName);
301 27
        $this->_addTable($table);
302
303 27
        return $this;
304
    }
305
306
    /**
307
     * Drops a table from the schema.
308
     *
309
     * @return $this
310
     */
311 135
    public function dropTable(string $tableName) : self
312
    {
313 135
        $tableName = $this->getFullQualifiedAssetName($tableName);
314 135
        $this->getTable($tableName);
315 135
        unset($this->_tables[$tableName]);
316
317 135
        return $this;
318
    }
319
320
    /**
321
     * Creates a new sequence.
322
     */
323 351
    public function createSequence(string $sequenceName, int $allocationSize = 1, int $initialValue = 1) : Sequence
324
    {
325 351
        $seq = new Sequence($sequenceName, $allocationSize, $initialValue);
326 351
        $this->_addSequence($seq);
327
328 351
        return $seq;
329
    }
330
331
    /**
332
     * @return $this
333
     */
334 27
    public function dropSequence(string $sequenceName) : self
335
    {
336 27
        $sequenceName = $this->getFullQualifiedAssetName($sequenceName);
337 27
        unset($this->_sequences[$sequenceName]);
338
339 27
        return $this;
340
    }
341
342
    /**
343
     * Returns an array of necessary SQL queries to create the schema on the given platform.
344
     *
345
     * @return array<int, string>
0 ignored issues
show
Documentation introduced by
The doc-type array<int, could not be parsed: Expected ">" at position 5, but found "end of type". (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
346
     */
347 321
    public function toSql(AbstractPlatform $platform) : array
348
    {
349 321
        $sqlCollector = new CreateSchemaSqlCollector($platform);
350 321
        $this->visit($sqlCollector);
351
352 321
        return $sqlCollector->getQueries();
353
    }
354
355
    /**
356
     * Return an array of necessary SQL queries to drop the schema on the given platform.
357
     *
358
     * @return array<int, string>
0 ignored issues
show
Documentation introduced by
The doc-type array<int, could not be parsed: Expected ">" at position 5, but found "end of type". (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
359
     */
360 27
    public function toDropSql(AbstractPlatform $platform) : array
361
    {
362 27
        $dropSqlCollector = new DropSchemaSqlCollector($platform);
363 27
        $this->visit($dropSqlCollector);
364
365 27
        return $dropSqlCollector->getQueries();
366
    }
367
368
    /**
369
     * @return array<int, string>
0 ignored issues
show
Documentation introduced by
The doc-type array<int, could not be parsed: Expected ">" at position 5, but found "end of type". (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
370
     */
371 17
    public function getMigrateToSql(Schema $toSchema, AbstractPlatform $platform) : array
372
    {
373 17
        $comparator = new Comparator();
374 17
        $schemaDiff = $comparator->compare($this, $toSchema);
375
376 17
        return $schemaDiff->toSql($platform);
377
    }
378
379
    /**
380
     * @return array<int, string>
0 ignored issues
show
Documentation introduced by
The doc-type array<int, could not be parsed: Expected ">" at position 5, but found "end of type". (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
381
     */
382
    public function getMigrateFromSql(Schema $fromSchema, AbstractPlatform $platform) : array
383
    {
384
        $comparator = new Comparator();
385
        $schemaDiff = $comparator->compare($fromSchema, $this);
386
387
        return $schemaDiff->toSql($platform);
388
    }
389
390 429
    public function visit(Visitor $visitor) : void
391
    {
392 429
        $visitor->acceptSchema($this);
393
394 429
        if ($visitor instanceof NamespaceVisitor) {
395 402
            foreach ($this->namespaces as $namespace) {
396 108
                $visitor->acceptNamespace($namespace);
397
            }
398
        }
399
400 429
        foreach ($this->_tables as $table) {
401 429
            $table->visit($visitor);
402
        }
403
404 429
        foreach ($this->_sequences as $sequence) {
405 108
            $sequence->visit($visitor);
406
        }
407 429
    }
408
409
    /**
410
     * Cloning a Schema triggers a deep clone of all related assets.
411
     */
412 71
    public function __clone()
413
    {
414 71
        foreach ($this->_tables as $k => $table) {
415 44
            $this->_tables[$k] = clone $table;
416
        }
417 71
        foreach ($this->_sequences as $k => $sequence) {
418 54
            $this->_sequences[$k] = clone $sequence;
419
        }
420 71
    }
421
}
422