Passed
Push — tests-better-coverage ( b00e66...9c1c22 )
by Michael
23:49
created

Schema::__construct()   B

Complexity

Conditions 6
Paths 16

Size

Total Lines 22
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 6

Importance

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