Failed Conditions
Pull Request — develop (#3348)
by Sergei
22:47
created

Schema::getName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 3
dl 0
loc 6
ccs 4
cts 4
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 1
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\Visitor\CreateSchemaSqlCollector;
9
use Doctrine\DBAL\Schema\Visitor\DropSchemaSqlCollector;
10
use Doctrine\DBAL\Schema\Visitor\NamespaceVisitor;
11
use Doctrine\DBAL\Schema\Visitor\Visitor;
12
use function array_keys;
13
use function assert;
14
use function is_string;
15
use function strpos;
16
use function strtolower;
17
18
/**
19
 * Object representation of a database schema.
20
 *
21
 * Different vendors have very inconsistent naming with regard to the concept
22
 * of a "schema". Doctrine understands a schema as the entity that conceptually
23
 * wraps a set of database objects such as tables, sequences, indexes and
24
 * foreign keys that belong to each other into a namespace. A Doctrine Schema
25
 * has nothing to do with the "SCHEMA" defined as in PostgreSQL, it is more
26
 * related to the concept of "DATABASE" that exists in MySQL and PostgreSQL.
27
 *
28
 * Every asset in the doctrine schema has a name. A name consists of either a
29
 * namespace.local name pair or just a local unqualified name.
30
 *
31
 * The abstraction layer that covers a PostgreSQL schema is the namespace of an
32
 * database object (asset). A schema can have a name, which will be used as
33
 * default namespace for the unqualified database objects that are created in
34
 * the schema.
35
 *
36
 * In the case of MySQL where cross-database queries are allowed this leads to
37
 * databases being "misinterpreted" as namespaces. This is intentional, however
38
 * the CREATE/DROP SQL visitors will just filter this queries and do not
39
 * execute them. Only the queries for the currently connected database are
40
 * executed.
41
 */
42
class Schema extends AbstractAsset
43
{
44
    /**
45
     * The namespaces in this schema.
46
     *
47
     * @var string[]
48
     */
49
    private $namespaces = [];
50
51
    /** @var Table[] */
52
    protected $_tables = [];
53
54
    /** @var Sequence[] */
55
    protected $_sequences = [];
56
57
    /** @var SchemaConfig */
58
    protected $_schemaConfig = false;
59
60
    /**
61
     * @param Table[]    $tables
62
     * @param Sequence[] $sequences
63
     * @param string[]   $namespaces
64
     */
65 1762
    public function __construct(
66
        array $tables = [],
67
        array $sequences = [],
68
        ?SchemaConfig $schemaConfig = null,
69
        array $namespaces = []
70
    ) {
71 1762
        if ($schemaConfig === null) {
72 1750
            $schemaConfig = new SchemaConfig();
73
        }
74 1762
        $this->_schemaConfig = $schemaConfig;
75 1762
        $this->_setName($schemaConfig->getName() ?: 'public');
76
77 1762
        foreach ($namespaces as $namespace) {
78 504
            $this->createNamespace($namespace);
79
        }
80
81 1762
        foreach ($tables as $table) {
82 1571
            $this->_addTable($table);
83
        }
84
85 1761
        foreach ($sequences as $sequence) {
86 941
            $this->_addSequence($sequence);
87
        }
88 1760
    }
89
90 1759
    public function getName() : string
91
    {
92 1759
        $name = parent::getName();
93 1759
        assert(is_string($name));
94
95 1759
        return $name;
96
    }
97
98
    public function hasExplicitForeignKeyIndexes() : bool
99
    {
100
        return $this->_schemaConfig->hasExplicitForeignKeyIndexes();
101
    }
102
103
    /**
104
     * @throws SchemaException
105
     */
106 1747
    protected function _addTable(Table $table) : void
107
    {
108 1747
        $namespaceName = $table->getNamespaceName();
109 1747
        $tableName     = $table->getFullQualifiedName($this->getName());
110
111 1747
        if (isset($this->_tables[$tableName])) {
112 745
            throw SchemaException::tableAlreadyExists($tableName);
113
        }
114
115 1747
        if ($namespaceName !== null
116 1747
            && ! $table->isInDefaultNamespace($this->getName())
117 1747
            && ! $this->hasNamespace($namespaceName)) {
118 1064
            $this->createNamespace($namespaceName);
119
        }
120
121 1747
        $this->_tables[$tableName] = $table;
122 1747
        $table->setSchemaConfig($this->_schemaConfig);
123 1747
    }
124
125
    /**
126
     * @throws SchemaException
127
     */
128 1322
    protected function _addSequence(Sequence $sequence) : void
129
    {
130 1322
        $namespaceName = $sequence->getNamespaceName();
131 1322
        $seqName       = $sequence->getFullQualifiedName($this->getName());
132
133 1322
        if (isset($this->_sequences[$seqName])) {
134 529
            throw SchemaException::sequenceAlreadyExists($seqName);
135
        }
136
137 1322
        if ($namespaceName !== null
138 1322
            && ! $sequence->isInDefaultNamespace($this->getName())
139 1322
            && ! $this->hasNamespace($namespaceName)) {
140 337
            $this->createNamespace($namespaceName);
141
        }
142
143 1322
        $this->_sequences[$seqName] = $sequence;
144 1322
    }
145
146
    /**
147
     * Returns the namespaces of this schema.
148
     *
149
     * @return string[] A list of namespace names.
150
     */
151 1551
    public function getNamespaces() : array
152
    {
153 1551
        return $this->namespaces;
154
    }
155
156
    /**
157
     * Gets all tables of this schema.
158
     *
159
     * @return Table[]
160
     */
161 1554
    public function getTables() : array
162
    {
163 1554
        return $this->_tables;
164
    }
165
166
    /**
167
     * @throws SchemaException
168
     */
169 1557
    public function getTable(string $tableName) : Table
170
    {
171 1557
        $tableName = $this->getFullQualifiedAssetName($tableName);
172 1557
        if (! isset($this->_tables[$tableName])) {
173 769
            throw SchemaException::tableDoesNotExist($tableName);
174
        }
175
176 1556
        return $this->_tables[$tableName];
177
    }
178
179 1598
    private function getFullQualifiedAssetName(string $name) : string
180
    {
181 1598
        $name = $this->getUnquotedAssetName($name);
182
183 1598
        if (strpos($name, '.') === false) {
184 1594
            $name = $this->getName() . '.' . $name;
185
        }
186
187 1598
        return strtolower($name);
188
    }
189
190
    /**
191
     * Returns the unquoted representation of a given asset name.
192
     *
193
     * @param string $assetName Quoted or unquoted representation of an asset name.
194
     */
195 1603
    private function getUnquotedAssetName(string $assetName) : string
196
    {
197 1603
        if ($this->isIdentifierQuoted($assetName)) {
198 458
            return $this->trimQuotes($assetName);
199
        }
200
201 1602
        return $assetName;
202
    }
203
204
    /**
205
     * Does this schema have a namespace with the given name?
206
     */
207 1180
    public function hasNamespace(string $namespaceName) : bool
208
    {
209 1180
        $namespaceName = strtolower($this->getUnquotedAssetName($namespaceName));
210
211 1180
        return isset($this->namespaces[$namespaceName]);
212
    }
213
214
    /**
215
     * Does this schema have a table with the given name?
216
     */
217 1584
    public function hasTable(string $tableName) : bool
218
    {
219 1584
        $tableName = $this->getFullQualifiedAssetName($tableName);
220
221 1584
        return isset($this->_tables[$tableName]);
222
    }
223
224
    /**
225
     * Gets all table names, prefixed with a schema name, even the default one if present.
226
     *
227
     * @return string[]
228
     */
229
    public function getTableNames() : array
230
    {
231
        return array_keys($this->_tables);
232
    }
233
234 1213
    public function hasSequence(string $sequenceName) : bool
235
    {
236 1213
        $sequenceName = $this->getFullQualifiedAssetName($sequenceName);
237
238 1213
        return isset($this->_sequences[$sequenceName]);
239
    }
240
241
    /**
242
     * @throws SchemaException
243
     */
244 1137
    public function getSequence(string $sequenceName) : Sequence
245
    {
246 1137
        $sequenceName = $this->getFullQualifiedAssetName($sequenceName);
247 1137
        if (! $this->hasSequence($sequenceName)) {
248 601
            throw SchemaException::sequenceDoesNotExist($sequenceName);
249
        }
250
251 1136
        return $this->_sequences[$sequenceName];
252
    }
253
254
    /**
255
     * @return Sequence[]
256
     */
257 1553
    public function getSequences() : array
258
    {
259 1553
        return $this->_sequences;
260
    }
261
262
    /**
263
     * Creates a new namespace.
264
     *
265
     * @param string $namespaceName The name of the namespace to create.
266
     *
267
     * @return $this
268
     *
269
     * @throws SchemaException
270
     */
271 1219
    public function createNamespace(string $namespaceName) : self
272
    {
273 1219
        $unquotedNamespaceName = strtolower($this->getUnquotedAssetName($namespaceName));
274
275 1219
        if (isset($this->namespaces[$unquotedNamespaceName])) {
276 385
            throw SchemaException::namespaceAlreadyExists($unquotedNamespaceName);
277
        }
278
279 1219
        $this->namespaces[$unquotedNamespaceName] = $namespaceName;
280
281 1219
        return $this;
282
    }
283
284
    /**
285
     * Creates a new table.
286
     */
287 1727
    public function createTable(string $tableName) : Table
288
    {
289 1727
        $table = new Table($tableName);
290 1727
        $this->_addTable($table);
291
292 1727
        foreach ($this->_schemaConfig->getDefaultTableOptions() as $name => $value) {
293
            $table->addOption($name, $value);
294
        }
295
296 1727
        return $table;
297
    }
298
299
    /**
300
     * Renames a table.
301
     *
302
     * @return $this
303
     */
304 721
    public function renameTable(string $oldTableName, string $newTableName) : self
305
    {
306 721
        $table = $this->getTable($oldTableName);
307 721
        $table->_setName($newTableName);
308
309 721
        $this->dropTable($oldTableName);
310 721
        $this->_addTable($table);
311
312 721
        return $this;
313
    }
314
315
    /**
316
     * Drops a table from the schema.
317
     *
318
     * @return $this
319
     */
320 725
    public function dropTable(string $tableName) : self
321
    {
322 725
        $tableName = $this->getFullQualifiedAssetName($tableName);
323 725
        $this->getTable($tableName);
324 725
        unset($this->_tables[$tableName]);
325
326 725
        return $this;
327
    }
328
329
    /**
330
     * Creates a new sequence.
331
     */
332 1213
    public function createSequence(string $sequenceName, int $allocationSize = 1, int $initialValue = 1) : Sequence
333
    {
334 1213
        $seq = new Sequence($sequenceName, $allocationSize, $initialValue);
335 1213
        $this->_addSequence($seq);
336
337 1213
        return $seq;
338
    }
339
340
    /**
341
     * @return $this
342
     */
343 553
    public function dropSequence(string $sequenceName) : self
344
    {
345 553
        $sequenceName = $this->getFullQualifiedAssetName($sequenceName);
346 553
        unset($this->_sequences[$sequenceName]);
347
348 553
        return $this;
349
    }
350
351
    /**
352
     * Returns an array of necessary SQL queries to create the schema on the given platform.
353
     *
354
     * @return string[]
355
     */
356 1704
    public function toSql(AbstractPlatform $platform) : array
357
    {
358 1704
        $sqlCollector = new CreateSchemaSqlCollector($platform);
359 1704
        $this->visit($sqlCollector);
360
361 1704
        return $sqlCollector->getQueries();
362
    }
363
364
    /**
365
     * Return an array of necessary SQL queries to drop the schema on the given platform.
366
     *
367
     * @return string[]
368
     */
369 133
    public function toDropSql(AbstractPlatform $platform) : array
370
    {
371 133
        $dropSqlCollector = new DropSchemaSqlCollector($platform);
372 133
        $this->visit($dropSqlCollector);
373
374 133
        return $dropSqlCollector->getQueries();
375
    }
376
377
    /**
378
     * @return string[]
379
     */
380 896
    public function getMigrateToSql(Schema $toSchema, AbstractPlatform $platform) : array
381
    {
382 896
        $comparator = new Comparator();
383 896
        $schemaDiff = $comparator->compare($this, $toSchema);
384
385 896
        return $schemaDiff->toSql($platform);
386
    }
387
388
    /**
389
     * @return string[]
390
     */
391
    public function getMigrateFromSql(Schema $fromSchema, AbstractPlatform $platform) : array
392
    {
393
        $comparator = new Comparator();
394
        $schemaDiff = $comparator->compare($fromSchema, $this);
395
396
        return $schemaDiff->toSql($platform);
397
    }
398
399 1711
    public function visit(Visitor $visitor) : void
400
    {
401 1711
        $visitor->acceptSchema($this);
402
403 1711
        if ($visitor instanceof NamespaceVisitor) {
404 1708
            foreach ($this->namespaces as $namespace) {
405 292
                $visitor->acceptNamespace($namespace);
406
            }
407
        }
408
409 1711
        foreach ($this->_tables as $table) {
410 1710
            $table->visit($visitor);
411
        }
412
413 1711
        foreach ($this->_sequences as $sequence) {
414 316
            $sequence->visit($visitor);
415
        }
416 1711
    }
417
418
    /**
419
     * Cloning a Schema triggers a deep clone of all related assets.
420
     */
421 1386
    public function __clone()
422
    {
423 1386
        foreach ($this->_tables as $k => $table) {
424 1125
            $this->_tables[$k] = clone $table;
425
        }
426 1386
        foreach ($this->_sequences as $k => $sequence) {
427 1106
            $this->_sequences[$k] = clone $sequence;
428
        }
429 1386
    }
430
}
431