Completed
Pull Request — develop (#3348)
by Sergei
63:43
created

Schema::getName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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