Schema.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482
  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;
  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 (
  95. $namespaceName !== null
  96. && ! $table->isInDefaultNamespace($this->getName())
  97. && ! $this->hasNamespace($namespaceName)
  98. ) {
  99. $this->createNamespace($namespaceName);
  100. }
  101. $this->_tables[$tableName] = $table;
  102. $table->setSchemaConfig($this->_schemaConfig);
  103. }
  104. /**
  105. * @return void
  106. *
  107. * @throws SchemaException
  108. */
  109. protected function _addSequence(Sequence $sequence)
  110. {
  111. $namespaceName = $sequence->getNamespaceName();
  112. $seqName = $sequence->getFullQualifiedName($this->getName());
  113. if (isset($this->_sequences[$seqName])) {
  114. throw SchemaException::sequenceAlreadyExists($seqName);
  115. }
  116. if (
  117. $namespaceName !== null
  118. && ! $sequence->isInDefaultNamespace($this->getName())
  119. && ! $this->hasNamespace($namespaceName)
  120. ) {
  121. $this->createNamespace($namespaceName);
  122. }
  123. $this->_sequences[$seqName] = $sequence;
  124. }
  125. /**
  126. * Returns the namespaces of this schema.
  127. *
  128. * @return string[] A list of namespace names.
  129. */
  130. public function getNamespaces()
  131. {
  132. return $this->namespaces;
  133. }
  134. /**
  135. * Gets all tables of this schema.
  136. *
  137. * @return Table[]
  138. */
  139. public function getTables()
  140. {
  141. return $this->_tables;
  142. }
  143. /**
  144. * @param string $name
  145. *
  146. * @return Table
  147. *
  148. * @throws SchemaException
  149. */
  150. public function getTable($name)
  151. {
  152. $name = $this->getFullQualifiedAssetName($name);
  153. if (! isset($this->_tables[$name])) {
  154. throw SchemaException::tableDoesNotExist($name);
  155. }
  156. return $this->_tables[$name];
  157. }
  158. /**
  159. * @param string $name
  160. *
  161. * @return string
  162. */
  163. private function getFullQualifiedAssetName($name)
  164. {
  165. $name = $this->getUnquotedAssetName($name);
  166. if (strpos($name, '.') === false) {
  167. $name = $this->getName() . '.' . $name;
  168. }
  169. return strtolower($name);
  170. }
  171. /**
  172. * Returns the unquoted representation of a given asset name.
  173. *
  174. * @param string $assetName Quoted or unquoted representation of an asset name.
  175. *
  176. * @return string
  177. */
  178. private function getUnquotedAssetName($assetName)
  179. {
  180. if ($this->isIdentifierQuoted($assetName)) {
  181. return $this->trimQuotes($assetName);
  182. }
  183. return $assetName;
  184. }
  185. /**
  186. * Does this schema have a namespace with the given name?
  187. *
  188. * @param string $name
  189. *
  190. * @return bool
  191. */
  192. public function hasNamespace($name)
  193. {
  194. $name = strtolower($this->getUnquotedAssetName($name));
  195. return isset($this->namespaces[$name]);
  196. }
  197. /**
  198. * Does this schema have a table with the given name?
  199. *
  200. * @param string $name
  201. *
  202. * @return bool
  203. */
  204. public function hasTable($name)
  205. {
  206. $name = $this->getFullQualifiedAssetName($name);
  207. return isset($this->_tables[$name]);
  208. }
  209. /**
  210. * Gets all table names, prefixed with a schema name, even the default one if present.
  211. *
  212. * @return string[]
  213. */
  214. public function getTableNames()
  215. {
  216. return array_keys($this->_tables);
  217. }
  218. /**
  219. * @param string $name
  220. *
  221. * @return bool
  222. */
  223. public function hasSequence($name)
  224. {
  225. $name = $this->getFullQualifiedAssetName($name);
  226. return isset($this->_sequences[$name]);
  227. }
  228. /**
  229. * @param string $name
  230. *
  231. * @return Sequence
  232. *
  233. * @throws SchemaException
  234. */
  235. public function getSequence($name)
  236. {
  237. $name = $this->getFullQualifiedAssetName($name);
  238. if (! $this->hasSequence($name)) {
  239. throw SchemaException::sequenceDoesNotExist($name);
  240. }
  241. return $this->_sequences[$name];
  242. }
  243. /**
  244. * @return Sequence[]
  245. */
  246. public function getSequences()
  247. {
  248. return $this->_sequences;
  249. }
  250. /**
  251. * Creates a new namespace.
  252. *
  253. * @param string $name The name of the namespace to create.
  254. *
  255. * @return Schema This schema instance.
  256. *
  257. * @throws SchemaException
  258. */
  259. public function createNamespace($name)
  260. {
  261. $unquotedName = strtolower($this->getUnquotedAssetName($name));
  262. if (isset($this->namespaces[$unquotedName])) {
  263. throw SchemaException::namespaceAlreadyExists($unquotedName);
  264. }
  265. $this->namespaces[$unquotedName] = $name;
  266. return $this;
  267. }
  268. /**
  269. * Creates a new table.
  270. *
  271. * @param string $name
  272. *
  273. * @return Table
  274. */
  275. public function createTable($name)
  276. {
  277. $table = new Table($name);
  278. $this->_addTable($table);
  279. foreach ($this->_schemaConfig->getDefaultTableOptions() as $option => $value) {
  280. $table->addOption($option, $value);
  281. }
  282. return $table;
  283. }
  284. /**
  285. * Renames a table.
  286. *
  287. * @param string $oldName
  288. * @param string $newName
  289. *
  290. * @return Schema
  291. */
  292. public function renameTable($oldName, $newName)
  293. {
  294. $table = $this->getTable($oldName);
  295. $table->_setName($newName);
  296. $this->dropTable($oldName);
  297. $this->_addTable($table);
  298. return $this;
  299. }
  300. /**
  301. * Drops a table from the schema.
  302. *
  303. * @param string $name
  304. *
  305. * @return Schema
  306. */
  307. public function dropTable($name)
  308. {
  309. $name = $this->getFullQualifiedAssetName($name);
  310. $this->getTable($name);
  311. unset($this->_tables[$name]);
  312. return $this;
  313. }
  314. /**
  315. * Creates a new sequence.
  316. *
  317. * @param string $name
  318. * @param int $allocationSize
  319. * @param int $initialValue
  320. *
  321. * @return Sequence
  322. */
  323. public function createSequence($name, $allocationSize = 1, $initialValue = 1)
  324. {
  325. $seq = new Sequence($name, $allocationSize, $initialValue);
  326. $this->_addSequence($seq);
  327. return $seq;
  328. }
  329. /**
  330. * @param string $name
  331. *
  332. * @return Schema
  333. */
  334. public function dropSequence($name)
  335. {
  336. $name = $this->getFullQualifiedAssetName($name);
  337. unset($this->_sequences[$name]);
  338. return $this;
  339. }
  340. /**
  341. * Returns an array of necessary SQL queries to create the schema on the given platform.
  342. *
  343. * @return string[]
  344. */
  345. public function toSql(AbstractPlatform $platform)
  346. {
  347. $sqlCollector = new CreateSchemaSqlCollector($platform);
  348. $this->visit($sqlCollector);
  349. return $sqlCollector->getQueries();
  350. }
  351. /**
  352. * Return an array of necessary SQL queries to drop the schema on the given platform.
  353. *
  354. * @return string[]
  355. */
  356. public function toDropSql(AbstractPlatform $platform)
  357. {
  358. $dropSqlCollector = new DropSchemaSqlCollector($platform);
  359. $this->visit($dropSqlCollector);
  360. return $dropSqlCollector->getQueries();
  361. }
  362. /**
  363. * @return string[]
  364. */
  365. public function getMigrateToSql(Schema $toSchema, AbstractPlatform $platform)
  366. {
  367. $comparator = new Comparator();
  368. $schemaDiff = $comparator->compare($this, $toSchema);
  369. return $schemaDiff->toSql($platform);
  370. }
  371. /**
  372. * @return string[]
  373. */
  374. public function getMigrateFromSql(Schema $fromSchema, AbstractPlatform $platform)
  375. {
  376. $comparator = new Comparator();
  377. $schemaDiff = $comparator->compare($fromSchema, $this);
  378. return $schemaDiff->toSql($platform);
  379. }
  380. /**
  381. * @return void
  382. */
  383. public function visit(Visitor $visitor)
  384. {
  385. $visitor->acceptSchema($this);
  386. if ($visitor instanceof NamespaceVisitor) {
  387. foreach ($this->namespaces as $namespace) {
  388. $visitor->acceptNamespace($namespace);
  389. }
  390. }
  391. foreach ($this->_tables as $table) {
  392. $table->visit($visitor);
  393. }
  394. foreach ($this->_sequences as $sequence) {
  395. $sequence->visit($visitor);
  396. }
  397. }
  398. /**
  399. * Cloning a Schema triggers a deep clone of all related assets.
  400. *
  401. * @return void
  402. */
  403. public function __clone()
  404. {
  405. foreach ($this->_tables as $k => $table) {
  406. $this->_tables[$k] = clone $table;
  407. }
  408. foreach ($this->_sequences as $k => $sequence) {
  409. $this->_sequences[$k] = clone $sequence;
  410. }
  411. }
  412. }