Subscription.php 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Framework\Mview\View;
  7. use Magento\Framework\App\ResourceConnection;
  8. use Magento\Framework\DB\Ddl\Trigger;
  9. use Magento\Framework\Mview\View\StateInterface;
  10. /**
  11. * Class Subscription
  12. *
  13. * @package Magento\Framework\Mview\View
  14. */
  15. class Subscription implements SubscriptionInterface
  16. {
  17. /**
  18. * Database connection
  19. *
  20. * @var \Magento\Framework\DB\Adapter\AdapterInterface
  21. */
  22. protected $connection;
  23. /**
  24. * @var \Magento\Framework\DB\Ddl\TriggerFactory
  25. */
  26. protected $triggerFactory;
  27. /**
  28. * @var \Magento\Framework\Mview\View\CollectionInterface
  29. */
  30. protected $viewCollection;
  31. /**
  32. * @var string
  33. */
  34. protected $view;
  35. /**
  36. * @var string
  37. */
  38. protected $tableName;
  39. /**
  40. * @var string
  41. */
  42. protected $columnName;
  43. /**
  44. * List of views linked to the same entity as the current view
  45. *
  46. * @var array
  47. */
  48. protected $linkedViews = [];
  49. /**
  50. * List of columns that can be updated in a subscribed table
  51. * without creating a new change log entry
  52. *
  53. * @var array
  54. */
  55. private $ignoredUpdateColumns = [];
  56. /**
  57. * @var Resource
  58. */
  59. protected $resource;
  60. /**
  61. * @param ResourceConnection $resource
  62. * @param \Magento\Framework\DB\Ddl\TriggerFactory $triggerFactory
  63. * @param \Magento\Framework\Mview\View\CollectionInterface $viewCollection
  64. * @param \Magento\Framework\Mview\ViewInterface $view
  65. * @param string $tableName
  66. * @param string $columnName
  67. * @param array $ignoredUpdateColumns
  68. */
  69. public function __construct(
  70. ResourceConnection $resource,
  71. \Magento\Framework\DB\Ddl\TriggerFactory $triggerFactory,
  72. \Magento\Framework\Mview\View\CollectionInterface $viewCollection,
  73. \Magento\Framework\Mview\ViewInterface $view,
  74. $tableName,
  75. $columnName,
  76. $ignoredUpdateColumns = []
  77. ) {
  78. $this->connection = $resource->getConnection();
  79. $this->triggerFactory = $triggerFactory;
  80. $this->viewCollection = $viewCollection;
  81. $this->view = $view;
  82. $this->tableName = $tableName;
  83. $this->columnName = $columnName;
  84. $this->resource = $resource;
  85. $this->ignoredUpdateColumns = $ignoredUpdateColumns;
  86. }
  87. /**
  88. * Create subsciption
  89. *
  90. * @return \Magento\Framework\Mview\View\SubscriptionInterface
  91. */
  92. public function create()
  93. {
  94. foreach (Trigger::getListOfEvents() as $event) {
  95. $triggerName = $this->getAfterEventTriggerName($event);
  96. /** @var Trigger $trigger */
  97. $trigger = $this->triggerFactory->create()
  98. ->setName($triggerName)
  99. ->setTime(Trigger::TIME_AFTER)
  100. ->setEvent($event)
  101. ->setTable($this->resource->getTableName($this->tableName));
  102. $trigger->addStatement($this->buildStatement($event, $this->getView()->getChangelog()));
  103. // Add statements for linked views
  104. foreach ($this->getLinkedViews() as $view) {
  105. /** @var \Magento\Framework\Mview\ViewInterface $view */
  106. $trigger->addStatement($this->buildStatement($event, $view->getChangelog()));
  107. }
  108. $this->connection->dropTrigger($trigger->getName());
  109. $this->connection->createTrigger($trigger);
  110. }
  111. return $this;
  112. }
  113. /**
  114. * Remove subscription
  115. *
  116. * @return \Magento\Framework\Mview\View\SubscriptionInterface
  117. */
  118. public function remove()
  119. {
  120. foreach (Trigger::getListOfEvents() as $event) {
  121. $triggerName = $this->getAfterEventTriggerName($event);
  122. /** @var Trigger $trigger */
  123. $trigger = $this->triggerFactory->create()
  124. ->setName($triggerName)
  125. ->setTime(Trigger::TIME_AFTER)
  126. ->setEvent($event)
  127. ->setTable($this->resource->getTableName($this->getTableName()));
  128. // Add statements for linked views
  129. foreach ($this->getLinkedViews() as $view) {
  130. /** @var \Magento\Framework\Mview\ViewInterface $view */
  131. $trigger->addStatement($this->buildStatement($event, $view->getChangelog()));
  132. }
  133. $this->connection->dropTrigger($trigger->getName());
  134. // Re-create trigger if trigger used by linked views
  135. if ($trigger->getStatements()) {
  136. $this->connection->createTrigger($trigger);
  137. }
  138. }
  139. return $this;
  140. }
  141. /**
  142. * Retrieve list of linked views
  143. *
  144. * @return array
  145. */
  146. protected function getLinkedViews()
  147. {
  148. if (!$this->linkedViews) {
  149. $viewList = $this->viewCollection->getViewsByStateMode(StateInterface::MODE_ENABLED);
  150. foreach ($viewList as $view) {
  151. /** @var \Magento\Framework\Mview\ViewInterface $view */
  152. // Skip the current view
  153. if ($view->getId() == $this->getView()->getId()) {
  154. continue;
  155. }
  156. // Search in view subscriptions
  157. foreach ($view->getSubscriptions() as $subscription) {
  158. if ($subscription['name'] != $this->getTableName()) {
  159. continue;
  160. }
  161. $this->linkedViews[] = $view;
  162. }
  163. }
  164. }
  165. return $this->linkedViews;
  166. }
  167. /**
  168. * Build trigger statement for INSERT, UPDATE, DELETE events
  169. *
  170. * @param string $event
  171. * @param \Magento\Framework\Mview\View\ChangelogInterface $changelog
  172. * @return string
  173. */
  174. protected function buildStatement($event, $changelog)
  175. {
  176. switch ($event) {
  177. case Trigger::EVENT_INSERT:
  178. $trigger = "INSERT IGNORE INTO %s (%s) VALUES (NEW.%s);";
  179. break;
  180. case Trigger::EVENT_UPDATE:
  181. $tableName = $this->resource->getTableName($this->getTableName());
  182. $trigger = "INSERT IGNORE INTO %s (%s) VALUES (NEW.%s);";
  183. if ($this->connection->isTableExists($tableName) &&
  184. $describe = $this->connection->describeTable($tableName)
  185. ) {
  186. $columnNames = array_column($describe, 'COLUMN_NAME');
  187. $columnNames = array_diff($columnNames, $this->ignoredUpdateColumns);
  188. if ($columnNames) {
  189. $columns = [];
  190. foreach ($columnNames as $columnName) {
  191. $columns[] = sprintf(
  192. 'NEW.%1$s <=> OLD.%1$s',
  193. $this->connection->quoteIdentifier($columnName)
  194. );
  195. }
  196. $trigger = sprintf(
  197. "IF (%s) THEN %s END IF;",
  198. implode(' OR ', $columns),
  199. $trigger
  200. );
  201. }
  202. }
  203. break;
  204. case Trigger::EVENT_DELETE:
  205. $trigger = "INSERT IGNORE INTO %s (%s) VALUES (OLD.%s);";
  206. break;
  207. default:
  208. return '';
  209. }
  210. return sprintf(
  211. $trigger,
  212. $this->connection->quoteIdentifier($this->resource->getTableName($changelog->getName())),
  213. $this->connection->quoteIdentifier($changelog->getColumnName()),
  214. $this->connection->quoteIdentifier($this->getColumnName())
  215. );
  216. }
  217. /**
  218. * Build an "after" event for the given table and event
  219. *
  220. * @param string $event The DB level event, like "update" or "insert"
  221. *
  222. * @return string
  223. */
  224. private function getAfterEventTriggerName($event)
  225. {
  226. return $this->resource->getTriggerName(
  227. $this->resource->getTableName($this->getTableName()),
  228. Trigger::TIME_AFTER,
  229. $event
  230. );
  231. }
  232. /**
  233. * Retrieve View related to subscription
  234. *
  235. * @return \Magento\Framework\Mview\ViewInterface
  236. * @codeCoverageIgnore
  237. */
  238. public function getView()
  239. {
  240. return $this->view;
  241. }
  242. /**
  243. * Retrieve table name
  244. *
  245. * @return string
  246. * @codeCoverageIgnore
  247. */
  248. public function getTableName()
  249. {
  250. return $this->tableName;
  251. }
  252. /**
  253. * Retrieve table column name
  254. *
  255. * @return string
  256. * @codeCoverageIgnore
  257. */
  258. public function getColumnName()
  259. {
  260. return $this->columnName;
  261. }
  262. }