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