AddColumn.php 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  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\Operations;
  7. use Magento\Framework\Setup\Declaration\Schema\Db\DbSchemaWriterInterface;
  8. use Magento\Framework\Setup\Declaration\Schema\Db\DDLTriggerInterface;
  9. use Magento\Framework\Setup\Declaration\Schema\Db\DefinitionAggregator;
  10. use Magento\Framework\Setup\Declaration\Schema\Db\Statement;
  11. use Magento\Framework\Setup\Declaration\Schema\Dto\Column;
  12. use Magento\Framework\Setup\Declaration\Schema\Dto\Columns\Integer;
  13. use Magento\Framework\Setup\Declaration\Schema\Dto\ElementFactory;
  14. use Magento\Framework\Setup\Declaration\Schema\Dto\Index;
  15. use Magento\Framework\Setup\Declaration\Schema\ElementHistory;
  16. use Magento\Framework\Setup\Declaration\Schema\ElementHistoryFactory;
  17. use Magento\Framework\Setup\Declaration\Schema\OperationInterface;
  18. /**
  19. * Add column to table operation.
  20. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  21. */
  22. class AddColumn implements OperationInterface
  23. {
  24. /**
  25. * Operation name.
  26. */
  27. const OPERATION_NAME = 'add_column';
  28. /**
  29. * This key is service key and need only for migration of data on auto_increment field.
  30. */
  31. const TEMPORARY_KEY = 'AUTO_INCREMENT_TEMPORARY_KEY';
  32. /**
  33. * @var DefinitionAggregator
  34. */
  35. private $definitionAggregator;
  36. /**
  37. * @var DbSchemaWriterInterface
  38. */
  39. private $dbSchemaWriter;
  40. /**
  41. * @var ElementFactory
  42. */
  43. private $elementFactory;
  44. /**
  45. * @var ElementHistoryFactory
  46. */
  47. private $elementHistoryFactory;
  48. /**
  49. * @var AddComplexElement
  50. */
  51. private $addComplexElement;
  52. /**
  53. * @var DropElement
  54. */
  55. private $dropElement;
  56. /**
  57. * @var DDLTriggerInterface[]
  58. */
  59. private $triggers;
  60. /**
  61. * AddColumn constructor.
  62. *
  63. * @param DefinitionAggregator $definitionAggregator
  64. * @param DbSchemaWriterInterface $dbSchemaWriter
  65. * @param ElementFactory $elementFactory
  66. * @param ElementHistoryFactory $elementHistoryFactory
  67. * @param AddComplexElement $addComplexElement
  68. * @param DropElement $dropElement
  69. * @param array $triggers
  70. */
  71. public function __construct(
  72. DefinitionAggregator $definitionAggregator,
  73. DbSchemaWriterInterface $dbSchemaWriter,
  74. ElementFactory $elementFactory,
  75. ElementHistoryFactory $elementHistoryFactory,
  76. AddComplexElement $addComplexElement,
  77. DropElement $dropElement,
  78. array $triggers = []
  79. ) {
  80. $this->definitionAggregator = $definitionAggregator;
  81. $this->dbSchemaWriter = $dbSchemaWriter;
  82. $this->elementFactory = $elementFactory;
  83. $this->elementHistoryFactory = $elementHistoryFactory;
  84. $this->addComplexElement = $addComplexElement;
  85. $this->dropElement = $dropElement;
  86. $this->triggers = $triggers;
  87. }
  88. /**
  89. * Creates index history.
  90. *
  91. * @param Column $column
  92. * @return ElementHistory
  93. */
  94. private function getTemporaryIndexHistory(Column $column)
  95. {
  96. $index = $this->elementFactory->create(
  97. Index::TYPE,
  98. [
  99. 'name' => self::TEMPORARY_KEY,
  100. 'column' => [$column->getName()],
  101. 'columns' => [$column],
  102. 'table' => $column->getTable()
  103. ]
  104. );
  105. return $this->elementHistoryFactory->create(['new' => $index]);
  106. }
  107. /**
  108. * @inheritdoc
  109. */
  110. public function getOperationName()
  111. {
  112. return self::OPERATION_NAME;
  113. }
  114. /**
  115. * @inheritdoc
  116. */
  117. public function isOperationDestructive()
  118. {
  119. return false;
  120. }
  121. /**
  122. * Check whether column is auto increment or not.
  123. *
  124. * @param Column $column
  125. * @return bool
  126. */
  127. private function columnIsAutoIncrement(Column $column)
  128. {
  129. return $column instanceof Integer && $column->isIdentity();
  130. }
  131. /**
  132. * Setup triggers if column have onCreate syntax.
  133. *
  134. * @param Statement $statement
  135. * @param ElementHistory $elementHistory
  136. * @return array
  137. */
  138. private function setupTriggersIfExists(Statement $statement, ElementHistory $elementHistory)
  139. {
  140. /** @var Column $column */
  141. $column = $elementHistory->getNew();
  142. //Add triggers to column
  143. foreach ($this->triggers as $ddlTrigger) {
  144. if ($ddlTrigger->isApplicable((string) $column->getOnCreate())) {
  145. $statement->addTrigger($ddlTrigger->getCallback($elementHistory));
  146. }
  147. }
  148. $statements = [$statement];
  149. /**
  150. * If column has triggers, only than we need to create temporary index on it.
  151. * As triggers means, that we will not enable primary key until all data will be transferred,
  152. * so column can left without key (as primary key is disabled) and this cause an error.
  153. */
  154. if ($this->columnIsAutoIncrement($column) && !empty($statement->getTriggers())) {
  155. /**
  156. * We need to create additional index for auto_increment.
  157. * As we create new field, and for this field we do not have any key/index, that are
  158. * required by SQL on any auto_increment field.
  159. * Primary key will be added to the column later, because column is empty at the moment
  160. * and if the table is not empty we will get error, such as "Duplicate key entry:".
  161. */
  162. $indexHistory = $this->getTemporaryIndexHistory($column);
  163. /** Add index should goes first */
  164. $statements = array_merge($this->addComplexElement->doOperation($indexHistory), $statements);
  165. /** Drop index should goes last and in another query */
  166. $statements = array_merge($statements, $this->dropElement->doOperation($indexHistory));
  167. }
  168. return $statements;
  169. }
  170. /**
  171. * @inheritdoc
  172. */
  173. public function doOperation(ElementHistory $elementHistory)
  174. {
  175. /**
  176. * @var Column $element
  177. */
  178. $element = $elementHistory->getNew();
  179. $definition = $this->definitionAggregator->toDefinition($element);
  180. $statement = $this->dbSchemaWriter->addElement(
  181. $element->getName(),
  182. $element->getTable()->getResource(),
  183. $element->getTable()->getName(),
  184. $definition,
  185. Column::TYPE
  186. );
  187. $statements = $this->setupTriggersIfExists($statement, $elementHistory);
  188. if ($this->columnIsAutoIncrement($element)) {
  189. /** We need to reset auto_increment as new field should goes from 1 */
  190. $statements[] = $this->dbSchemaWriter->resetAutoIncrement(
  191. $element->getTable()->getName(),
  192. $element->getTable()->getResource()
  193. );
  194. }
  195. return $statements;
  196. }
  197. }