TableDiff.php 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Framework\Setup\Declaration\Schema\Diff;
  7. use Magento\Framework\Setup\Declaration\Schema\Dto\Column;
  8. use Magento\Framework\Setup\Declaration\Schema\Dto\Constraint;
  9. use Magento\Framework\Setup\Declaration\Schema\Dto\ElementInterface;
  10. use Magento\Framework\Setup\Declaration\Schema\Dto\Index;
  11. use Magento\Framework\Setup\Declaration\Schema\Dto\Table;
  12. use Magento\Framework\Setup\Declaration\Schema\Operations\ModifyColumn;
  13. /**
  14. * As table can have different types of elements inside itself.
  15. * We need to compare all of this elements.
  16. *
  17. * If element exists only in XML -> then we need to create element.
  18. * If element exists in both version and are different -> then we need to modify element.
  19. * If element exists only in db -> then we need to remove this element.
  20. */
  21. class TableDiff
  22. {
  23. /**
  24. * Column type for diff.
  25. */
  26. const COLUMN_DIFF_TYPE = "columns";
  27. /**
  28. * Constraint type for diff.
  29. */
  30. const CONSTRAINT_DIFF_TYPE = "constraints";
  31. /**
  32. * Constraint type for diff.
  33. */
  34. const INDEX_DIFF_TYPE = "indexes";
  35. /**
  36. * @var DiffManager
  37. */
  38. private $diffManager;
  39. /**
  40. * Constructor.
  41. *
  42. * @param DiffManager $diffManager
  43. */
  44. public function __construct(DiffManager $diffManager)
  45. {
  46. $this->diffManager = $diffManager;
  47. }
  48. /**
  49. * As SQL engine can automatically create indexes for foreign keys in order to speedup selection
  50. * and we do not have this keys in declaration - we need to ignore them.
  51. *
  52. * @param Table $table
  53. * @param Index[] $indexes
  54. * @return Index[]
  55. */
  56. private function excludeAutoIndexes(Table $table, array $indexes)
  57. {
  58. foreach ($table->getReferenceConstraints() as $constraint) {
  59. unset($indexes[$constraint->getName()]);
  60. }
  61. return $indexes;
  62. }
  63. /**
  64. * As foreign key is constraint, that do not allow to change column schema definition
  65. * we need to disable it in order to change column definition. When column definition
  66. * will be changed we need to enable foreign key again.
  67. * We need to start column modification from parent table (reference table) and then go to
  68. * tables that have foreign keys.
  69. *
  70. * @param Table $declaredTable
  71. * @param Table $generatedTable
  72. * @param Diff $diff
  73. * @return Diff
  74. */
  75. private function turnOffForeignKeys(Table $declaredTable, Table $generatedTable, Diff $diff)
  76. {
  77. $changes = $diff->getChange($generatedTable->getName(), ModifyColumn::OPERATION_NAME);
  78. foreach ($changes as $elementHistory) {
  79. /** If this is column we need to recreate foreign key */
  80. if ($elementHistory->getNew() instanceof Column) {
  81. $column = $elementHistory->getNew();
  82. $references = $generatedTable->getReferenceConstraints();
  83. $declaredReferences = $this->getElementsListByNameWithoutPrefix(
  84. $declaredTable->getReferenceConstraints()
  85. );
  86. foreach ($references as $reference) {
  87. /** In case when we have foreign key on column, that should be modified */
  88. if ($reference->getColumn()->getName() === $column->getName() &&
  89. isset($declaredReferences[$reference->getNameWithoutPrefix()])
  90. ) {
  91. /**
  92. * Lets disable foreign key and enable it again
  93. * As between drop and create operations we have operation of modification
  94. * we will drop key, modify column, add key
  95. */
  96. $diff = $this->diffManager->registerRemoval($diff, [$reference]);
  97. $diff = $this->diffManager
  98. ->registerCreation(
  99. $diff,
  100. $declaredReferences[$reference->getNameWithoutPrefix()]
  101. );
  102. }
  103. }
  104. }
  105. }
  106. return $diff;
  107. }
  108. /**
  109. * Switches keys of the array on the element name without prefix.
  110. *
  111. * @param Constraint[]|Index[] $elements
  112. * @return array
  113. */
  114. private function getElementsListByNameWithoutPrefix(array $elements)
  115. {
  116. $elementsList = [];
  117. foreach ($elements as $element) {
  118. $elementsList[$element->getNameWithoutPrefix()] = $element;
  119. }
  120. return $elementsList;
  121. }
  122. /**
  123. * Diff between tables.
  124. *
  125. * @param Table | ElementInterface $declaredTable
  126. * @param Table | ElementInterface $generatedTable
  127. * @param Diff $diff
  128. * @inheritdoc
  129. */
  130. public function diff(
  131. ElementInterface $declaredTable,
  132. ElementInterface $generatedTable,
  133. Diff $diff
  134. ) {
  135. if ($declaredTable->getResource() !== $generatedTable->getResource()) {
  136. $this->diffManager->registerRecreation($declaredTable, $generatedTable, $diff);
  137. return $diff;
  138. }
  139. if ($this->diffManager->shouldBeModified($declaredTable, $generatedTable)) {
  140. $this->diffManager->registerTableModification($declaredTable, $generatedTable, $diff);
  141. }
  142. return $this->calculateDiff($declaredTable, $generatedTable, $diff);
  143. }
  144. /**
  145. * Calculate the difference between tables.
  146. *
  147. * @param Table|ElementInterface $declaredTable
  148. * @param Table|ElementInterface $generatedTable
  149. * @param Diff $diff
  150. * @return Diff
  151. */
  152. private function calculateDiff(ElementInterface $declaredTable, ElementInterface $generatedTable, Diff $diff)
  153. {
  154. $types = [self::COLUMN_DIFF_TYPE, self::CONSTRAINT_DIFF_TYPE, self::INDEX_DIFF_TYPE];
  155. //We do inspection for each element type
  156. foreach ($types as $elementType) {
  157. $generatedElements = $generatedTable->getElementsByType($elementType);
  158. $declaredElements = $declaredTable->getElementsByType($elementType);
  159. if ($elementType === self::INDEX_DIFF_TYPE) {
  160. $generatedElements = $this->excludeAutoIndexes($generatedTable, $generatedElements);
  161. $declaredElements = $this->excludeAutoIndexes($declaredTable, $declaredElements);
  162. }
  163. if (in_array($elementType, [self::CONSTRAINT_DIFF_TYPE, self::INDEX_DIFF_TYPE], true)) {
  164. $generatedElements = $this->getElementsListByNameWithoutPrefix($generatedElements);
  165. $declaredElements = $this->getElementsListByNameWithoutPrefix($declaredElements);
  166. }
  167. foreach ($declaredElements as $elementName => $element) {
  168. //If it is new for generated (generated from db) elements - we need to create it
  169. if (!isset($generatedElements[$elementName])) {
  170. $diff = $this->diffManager->registerCreation($diff, $element);
  171. } elseif ($this->diffManager->shouldBeModified(
  172. $element,
  173. $generatedElements[$elementName]
  174. )) {
  175. $diff = $this->diffManager
  176. ->registerModification($diff, $element, $generatedElements[$elementName]);
  177. }
  178. //Unset processed elements from generated from db schema
  179. //All other unprocessed elements will be added as removed ones
  180. unset($generatedElements[$elementName]);
  181. }
  182. //Elements that should be removed
  183. if ($this->diffManager->shouldBeRemoved($generatedElements)) {
  184. $diff = $this->diffManager->registerRemoval($diff, $generatedElements);
  185. }
  186. }
  187. $diff = $this->turnOffForeignKeys($declaredTable, $generatedTable, $diff);
  188. return $diff;
  189. }
  190. }