Schema.php 12 KB

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