Failed Conditions
Pull Request — master (#3535)
by Jonathan
60:43
created

Schema::getUnquotedAssetName()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

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