Completed
Pull Request — master (#3233)
by Sergey
65:15
created

Schema::_addView()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 4

Importance

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