Failed Conditions
Pull Request — master (#3233)
by Sergey
64:48
created

Schema::_addTable()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 4

Importance

Changes 0
Metric Value
eloc 8
dl 0
loc 15
ccs 6
cts 6
cp 1
rs 10
c 0
b 0
f 0
cc 4
nc 3
nop 1
crap 4
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 View[]  */
54
    private $views = [];
55
56
    /** @var SchemaConfig */
57
    protected $_schemaConfig = false;
58
59
    /**
60
     * @param Table[]             $tables
61 1308
     * @param Sequence[]          $sequences
62
     * @param SchemaConfig | null $schemaConfig
63
     * @param string[]            $namespaces
64
     * @param View[]              $views
65
     */
66
    public function __construct(
67 1308
        array $tables = [],
68 1137
        array $sequences = [],
69
        ?SchemaConfig $schemaConfig = null,
70 1308
        array $namespaces = [],
71 1308
        array $views = []
72
    ) {
73 1308
        if ($schemaConfig === null) {
74 8
            $schemaConfig = new SchemaConfig();
75
        }
76
        $this->_schemaConfig = $schemaConfig;
77 1308
        $this->_setName($schemaConfig->getName() ?: 'public');
78 361
79
        foreach ($namespaces as $namespace) {
80
            $this->createNamespace($namespace);
81 1289
        }
82 85
83
        foreach ($tables as $table) {
84 1270
            $this->_addTable($table);
85
        }
86
87
        foreach ($sequences as $sequence) {
88
            $this->_addSequence($sequence);
89
        }
90
91
        foreach ($views as $view) {
92
            $this->addView($view);
93
        }
94
    }
95
96
    /**
97
     * @return bool
98
     */
99 1023
    public function hasExplicitForeignKeyIndexes()
100
    {
101 1023
        return $this->_schemaConfig->hasExplicitForeignKeyIndexes();
102 1023
    }
103
104 1023
    /**
105 19
     * @return void
106
     *
107
     * @throws SchemaException
108 1023
     */
109 152
    protected function _addTable(Table $table)
110
    {
111
        $namespaceName = $table->getNamespaceName();
112 1023
        $tableName     = $table->getFullQualifiedName($this->getName());
113 1023
114 1023
        if (isset($this->_tables[$tableName])) {
115
            throw SchemaException::tableAlreadyExists($tableName);
116
        }
117
118
        if (! $table->isInDefaultNamespace($this->getName()) && ! $this->hasNamespace($namespaceName)) {
119
            $this->createNamespace($namespaceName);
120
        }
121 332
122
        $this->_tables[$tableName] = $table;
123 332
        $table->setSchemaConfig($this->_schemaConfig);
124 332
    }
125
126 332
    /**
127 19
     * @return void
128
     *
129
     * @throws SchemaException
130 332
     */
131 19
    protected function _addSequence(Sequence $sequence)
132
    {
133
        $namespaceName = $sequence->getNamespaceName();
134 332
        $seqName       = $sequence->getFullQualifiedName($this->getName());
135 332
136
        if (isset($this->_sequences[$seqName])) {
137
            throw SchemaException::sequenceAlreadyExists($seqName);
138
        }
139
140
        if (! $sequence->isInDefaultNamespace($this->getName()) && ! $this->hasNamespace($namespaceName)) {
141
            $this->createNamespace($namespaceName);
142 513
        }
143
144 513
        $this->_sequences[$seqName] = $sequence;
145
    }
146
147
    /**
148
     * @throws SchemaException
149
     */
150
    private function addView(View $view) : void
151
    {
152 570
        $namespaceName = $view->getNamespaceName();
153
        $viewName      = $view->getFullQualifiedName($this->getName());
154 570
155
        if (isset($this->views[$viewName])) {
156
            throw SchemaException::viewAlreadyExists($viewName);
157
        }
158
159
        if (! $view->isInDefaultNamespace($this->getName()) && ! $this->hasNamespace($namespaceName)) {
160
            $this->createNamespace($namespaceName);
161
        }
162
163
        $this->views[$viewName] = $view;
164 627
    }
165
166 627
    /**
167 627
     * Returns the namespaces of this schema.
168 19
     *
169
     * @return string[] A list of namespace names.
170
     */
171 608
    public function getNamespaces()
172
    {
173
        return $this->namespaces;
174
    }
175
176
    /**
177
     * Gets all tables of this schema.
178
     *
179 874
     * @return Table[]
180
     */
181 874
    public function getTables()
182
    {
183 874
        return $this->_tables;
184 798
    }
185
186
    /**
187 874
     * @param string $tableName
188
     *
189
     * @return Table
190
     *
191
     * @throws SchemaException
192
     */
193
    public function getTable($tableName)
194
    {
195
        $tableName = $this->getFullQualifiedAssetName($tableName);
196
        if (! isset($this->_tables[$tableName])) {
197 969
            throw SchemaException::tableDoesNotExist($tableName);
198
        }
199 969
200 38
        return $this->_tables[$tableName];
201
    }
202
203 950
    /**
204
     * @param string $name
205
     *
206
     * @return string
207
     */
208
    private function getFullQualifiedAssetName($name)
209
    {
210
        $name = $this->getUnquotedAssetName($name);
211
212
        if (strpos($name, '.') === false) {
213 196
            $name = $this->getName() . '.' . $name;
214
        }
215 196
216
        return strtolower($name);
217 196
    }
218
219
    /**
220
     * Returns the unquoted representation of a given asset name.
221
     *
222
     * @param string $assetName Quoted or unquoted representation of an asset name.
223
     *
224
     * @return string
225
     */
226
    private function getUnquotedAssetName($assetName)
227 608
    {
228
        if ($this->isIdentifierQuoted($assetName)) {
229 608
            return $this->trimQuotes($assetName);
230
        }
231 608
232
        return $assetName;
233
    }
234
235
    /**
236
     * Does this schema have a namespace with the given name?
237
     *
238
     * @param string $namespaceName
239
     *
240
     * @return bool
241
     */
242
    public function hasNamespace($namespaceName)
243
    {
244
        $namespaceName = strtolower($this->getUnquotedAssetName($namespaceName));
245
246
        return isset($this->namespaces[$namespaceName]);
247
    }
248
249 247
    /**
250
     * Does this schema have a table with the given name?
251 247
     *
252
     * @param string $tableName
253 247
     *
254
     * @return bool
255
     */
256
    public function hasTable($tableName)
257
    {
258
        $tableName = $this->getFullQualifiedAssetName($tableName);
259
260
        return isset($this->_tables[$tableName]);
261
    }
262
263 171
    /**
264
     * Gets all table names, prefixed with a schema name, even the default one if present.
265 171
     *
266 171
     * @return string[]
267 19
     */
268
    public function getTableNames()
269
    {
270 152
        return array_keys($this->_tables);
271
    }
272
273
    /**
274
     * @param string $sequenceName
275
     *
276 551
     * @return bool
277
     */
278 551
    public function hasSequence($sequenceName)
279
    {
280
        $sequenceName = $this->getFullQualifiedAssetName($sequenceName);
281
282
        return isset($this->_sequences[$sequenceName]);
283
    }
284
285
    /**
286
     * @param string $sequenceName
287
     *
288
     * @return Sequence
289
     *
290 217
     * @throws SchemaException
291
     */
292 217
    public function getSequence($sequenceName)
293
    {
294 217
        $sequenceName = $this->getFullQualifiedAssetName($sequenceName);
295 19
        if (! $this->hasSequence($sequenceName)) {
296
            throw SchemaException::sequenceDoesNotExist($sequenceName);
297
        }
298 217
299
        return $this->_sequences[$sequenceName];
300 217
    }
301
302
    /**
303
     * @return Sequence[]
304
     */
305
    public function getSequences()
306
    {
307
        return $this->_sequences;
308
    }
309
310 700
    /**
311
     * Creates a new namespace.
312 700
     *
313 700
     * @param string $namespaceName The name of the namespace to create.
314
     *
315 700
     * @return \Doctrine\DBAL\Schema\Schema This schema instance.
316
     *
317
     * @throws SchemaException
318
     */
319 700
    public function createNamespace($namespaceName)
320
    {
321
        $unquotedNamespaceName = strtolower($this->getUnquotedAssetName($namespaceName));
322
323
        if (isset($this->namespaces[$unquotedNamespaceName])) {
324
            throw SchemaException::namespaceAlreadyExists($unquotedNamespaceName);
325
        }
326
327
        $this->namespaces[$unquotedNamespaceName] = $namespaceName;
328
329
        return $this;
330 19
    }
331
332 19
    /**
333 19
     * Creates a new table.
334
     *
335 19
     * @param string $tableName
336 19
     *
337
     * @return Table
338 19
     */
339
    public function createTable($tableName)
340
    {
341
        $table = new Table($tableName);
342
        $this->_addTable($table);
343
344
        foreach ($this->_schemaConfig->getDefaultTableOptions() as $name => $value) {
345
            $table->addOption($name, $value);
346
        }
347
348 95
        return $table;
349
    }
350 95
351 95
    /**
352 95
     * Renames a table.
353
     *
354 95
     * @param string $oldTableName
355
     * @param string $newTableName
356
     *
357
     * @return \Doctrine\DBAL\Schema\Schema
358
     */
359
    public function renameTable($oldTableName, $newTableName)
360
    {
361
        $table = $this->getTable($oldTableName);
362
        $table->_setName($newTableName);
363
364
        $this->dropTable($oldTableName);
365
        $this->_addTable($table);
366 247
367
        return $this;
368 247
    }
369 247
370
    /**
371 247
     * Drops a table from the schema.
372
     *
373
     * @param string $tableName
374
     *
375
     * @return \Doctrine\DBAL\Schema\Schema
376
     */
377
    public function dropTable($tableName)
378
    {
379 19
        $tableName = $this->getFullQualifiedAssetName($tableName);
380
        $this->getTable($tableName);
381 19
        unset($this->_tables[$tableName]);
382 19
383
        return $this;
384 19
    }
385
386
    /**
387
     * Creates a new sequence.
388
     *
389
     * @param string $sequenceName
390
     * @param int    $allocationSize
391
     * @param int    $initialValue
392 219
     *
393
     * @return Sequence
394 219
     */
395 219
    public function createSequence($sequenceName, $allocationSize = 1, $initialValue = 1)
396
    {
397 219
        $seq = new Sequence($sequenceName, $allocationSize, $initialValue);
398
        $this->_addSequence($seq);
399
400
        return $seq;
401
    }
402
403
    /**
404
     * @param string $sequenceName
405 19
     *
406
     * @return \Doctrine\DBAL\Schema\Schema
407 19
     */
408 19
    public function dropSequence($sequenceName)
409
    {
410 19
        $sequenceName = $this->getFullQualifiedAssetName($sequenceName);
411
        unset($this->_sequences[$sequenceName]);
412
413
        return $this;
414
    }
415
416
    public function hasView(string $viewName) : bool
417
    {
418
        $viewName = $this->getFullQualifiedAssetName($viewName);
419
420
        return isset($this->views[$viewName]);
421
    }
422
423
    /**
424
     * @throws SchemaException
425
     */
426
    public function getView(string $viewName) : View
427
    {
428
        $viewName = $this->getFullQualifiedAssetName($viewName);
429
        if (! isset($this->views[$viewName])) {
430
            throw SchemaException::viewDoesNotExist($viewName);
431
        }
432
433
        return $this->views[$viewName];
434
    }
435
436
    /**
437
     * @return View[]
438 333
     */
439
    public function getViews() : array
440 333
    {
441
        return $this->views;
442 333
    }
443 276
444 76
    public function createView(string $viewName, string $sql) : View
445
    {
446
        $view = new View($viewName, $sql);
447
        $this->addView($view);
448 333
449 333
        return $view;
450
    }
451
452 333
    /**
453 76
     * @return $this
454
     */
455 333
    public function dropView(string $viewName) : self
456
    {
457
        $viewName = $this->getFullQualifiedAssetName($viewName);
458
        unset($this->views[$viewName]);
459
460
        return $this;
461
    }
462 38
463
    /**
464 38
     * Returns an array of necessary SQL queries to create the schema on the given platform.
465 19
     *
466
     * @return string[]
467 38
     */
468 38
    public function toSql(AbstractPlatform $platform)
469
    {
470 38
        $sqlCollector = new CreateSchemaSqlCollector($platform);
471
        $this->visit($sqlCollector);
472
473
        return $sqlCollector->getQueries();
474
    }
475
476
    /**
477
     * Return an array of necessary SQL queries to drop the schema on the given platform.
478
     *
479
     * @return string[]
480
     */
481
    public function toDropSql(AbstractPlatform $platform)
482
    {
483
        $dropSqlCollector = new DropSchemaSqlCollector($platform);
484
        $this->visit($dropSqlCollector);
485
486
        return $dropSqlCollector->getQueries();
487
    }
488
489
    /**
490
     * @return string[]
491
     */
492
    public function getMigrateToSql(Schema $toSchema, AbstractPlatform $platform)
493
    {
494
        $comparator = new Comparator();
495
        $schemaDiff = $comparator->compare($this, $toSchema);
496
497
        return $schemaDiff->toSql($platform);
498
    }
499
500
    /**
501
     * @return string[]
502
     */
503
    public function getMigrateFromSql(Schema $fromSchema, AbstractPlatform $platform)
504
    {
505
        $comparator = new Comparator();
506
        $schemaDiff = $comparator->compare($fromSchema, $this);
507
508
        return $schemaDiff->toSql($platform);
509
    }
510
511
    /**
512
     * @return void
513
     */
514
    public function visit(Visitor $visitor)
515
    {
516
        $visitor->acceptSchema($this);
517
518
        if ($visitor instanceof NamespaceVisitor) {
519
            foreach ($this->namespaces as $namespace) {
520
                $visitor->acceptNamespace($namespace);
521
            }
522
        }
523
524
        foreach ($this->_tables as $table) {
525
            $table->visit($visitor);
526
        }
527
528
        foreach ($this->_sequences as $sequence) {
529
            $sequence->visit($visitor);
530
        }
531
532
        foreach ($this->views as $view) {
533
            $view->visit($visitor);
534
        }
535
    }
536
537
    /**
538
     * Cloning a Schema triggers a deep clone of all related assets.
539
     *
540
     * @return void
541
     */
542
    public function __clone()
543
    {
544
        foreach ($this->_tables as $k => $table) {
545
            $this->_tables[$k] = clone $table;
546
        }
547
        foreach ($this->_sequences as $k => $sequence) {
548
            $this->_sequences[$k] = clone $sequence;
549
        }
550
        foreach ($this->views as $k => $view) {
551
            $this->views[$k] = clone $view;
552
        }
553
    }
554
}
555