PageRenderTime 55ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/app/code/Magento/Catalog/Model/Indexer/Product/Flat/FlatTableBuilder.php

https://gitlab.com/crazybutterfly815/magento2
PHP | 378 lines | 250 code | 38 blank | 90 comment | 14 complexity | 332023143aa5352b4a9c16f10a3cd4c4 MD5 | raw file
  1. <?php
  2. /**
  3. * Copyright © 2016 Magento. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Catalog\Model\Indexer\Product\Flat;
  7. use Magento\Catalog\Api\Data\ProductInterface;
  8. use Magento\Framework\App\ResourceConnection;
  9. use Magento\Framework\EntityManager\MetadataPool;
  10. /**
  11. * Class FlatTableBuilder
  12. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  13. */
  14. class FlatTableBuilder
  15. {
  16. /**
  17. * @var MetadataPool
  18. */
  19. protected $metadataPool;
  20. /**
  21. * Path to maximum available amount of indexes for flat indexer
  22. */
  23. const XML_NODE_MAX_INDEX_COUNT = 'catalog/product/flat/max_index_count';
  24. /**
  25. * @var \Magento\Catalog\Helper\Product\Flat\Indexer
  26. */
  27. protected $_productIndexerHelper;
  28. /**
  29. * @var \Magento\Framework\DB\Adapter\AdapterInterface
  30. */
  31. protected $_connection;
  32. /**
  33. * @var \Magento\Framework\App\Config\ScopeConfigInterface $config
  34. */
  35. protected $_config;
  36. /**
  37. * @var \Magento\Store\Model\StoreManagerInterface
  38. */
  39. protected $_storeManager;
  40. /**
  41. * @var TableDataInterface
  42. */
  43. protected $_tableData;
  44. /**
  45. * @var \Magento\Framework\App\ResourceConnection
  46. */
  47. protected $resource;
  48. /**
  49. * @param \Magento\Catalog\Helper\Product\Flat\Indexer $productIndexerHelper
  50. * @param ResourceConnection $resource
  51. * @param \Magento\Framework\App\Config\ScopeConfigInterface $config
  52. * @param \Magento\Store\Model\StoreManagerInterface $storeManager
  53. * @param TableDataInterface $tableData
  54. */
  55. public function __construct(
  56. \Magento\Catalog\Helper\Product\Flat\Indexer $productIndexerHelper,
  57. \Magento\Framework\App\ResourceConnection $resource,
  58. \Magento\Framework\App\Config\ScopeConfigInterface $config,
  59. \Magento\Store\Model\StoreManagerInterface $storeManager,
  60. \Magento\Catalog\Model\Indexer\Product\Flat\TableDataInterface $tableData
  61. ) {
  62. $this->_productIndexerHelper = $productIndexerHelper;
  63. $this->resource = $resource;
  64. $this->_connection = $resource->getConnection();
  65. $this->_config = $config;
  66. $this->_storeManager = $storeManager;
  67. $this->_tableData = $tableData;
  68. }
  69. /**
  70. * Prepare temporary flat tables
  71. *
  72. * @param int|string $storeId
  73. * @param array $changedIds
  74. * @param string $valueFieldSuffix
  75. * @param string $tableDropSuffix
  76. * @param bool $fillTmpTables
  77. * @return void
  78. */
  79. public function build($storeId, $changedIds, $valueFieldSuffix, $tableDropSuffix, $fillTmpTables)
  80. {
  81. $attributes = $this->_productIndexerHelper->getAttributes();
  82. $eavAttributes = $this->_productIndexerHelper->getTablesStructure($attributes);
  83. $this->_createTemporaryFlatTable($storeId);
  84. if ($fillTmpTables) {
  85. $this->_fillTemporaryFlatTable($eavAttributes, $storeId, $valueFieldSuffix);
  86. //Update zero based attributes by values from current store
  87. $this->_updateTemporaryTableByStoreValues($eavAttributes, $changedIds, $storeId, $valueFieldSuffix);
  88. }
  89. $flatTable = $this->_productIndexerHelper->getFlatTableName($storeId);
  90. $flatDropName = $flatTable . $tableDropSuffix;
  91. $temporaryFlatTableName = $this->_getTemporaryTableName(
  92. $this->_productIndexerHelper->getFlatTableName($storeId)
  93. );
  94. $this->_tableData->move($flatTable, $flatDropName, $temporaryFlatTableName);
  95. }
  96. /**
  97. * Prepare flat table for store
  98. *
  99. * @param int|string $storeId
  100. * @return void
  101. * @throws \Magento\Framework\Exception\LocalizedException
  102. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  103. * @SuppressWarnings(PHPMD.NPathComplexity)
  104. */
  105. protected function _createTemporaryFlatTable($storeId)
  106. {
  107. $columns = $this->_productIndexerHelper->getFlatColumns();
  108. $indexesNeed = $this->_productIndexerHelper->getFlatIndexes();
  109. $maxIndex = $this->_config->getValue(
  110. self::XML_NODE_MAX_INDEX_COUNT
  111. );
  112. if ($maxIndex && count($indexesNeed) > $maxIndex) {
  113. throw new \Magento\Framework\Exception\LocalizedException(
  114. __(
  115. 'The Flat Catalog module has a limit of %2$d filterable and/or sortable attributes.'
  116. . 'Currently there are %1$d of them.'
  117. . 'Please reduce the number of filterable/sortable attributes in order to use this module',
  118. count($indexesNeed),
  119. $maxIndex
  120. )
  121. );
  122. }
  123. $indexKeys = [];
  124. $indexProps = array_values($indexesNeed);
  125. $upperPrimaryKey = strtoupper(\Magento\Framework\DB\Adapter\AdapterInterface::INDEX_TYPE_PRIMARY);
  126. foreach ($indexProps as $i => $indexProp) {
  127. $indexName = $this->_connection->getIndexName(
  128. $this->_getTemporaryTableName($this->_productIndexerHelper->getFlatTableName($storeId)),
  129. $indexProp['fields'],
  130. $indexProp['type']
  131. );
  132. $indexProp['type'] = strtoupper($indexProp['type']);
  133. if ($indexProp['type'] == $upperPrimaryKey) {
  134. $indexKey = $upperPrimaryKey;
  135. } else {
  136. $indexKey = $indexName;
  137. }
  138. $indexProps[$i] = [
  139. 'KEY_NAME' => $indexName,
  140. 'COLUMNS_LIST' => $indexProp['fields'],
  141. 'INDEX_TYPE' => strtolower($indexProp['type']),
  142. ];
  143. $indexKeys[$i] = $indexKey;
  144. }
  145. $indexesNeed = array_combine($indexKeys, $indexProps);
  146. /** @var $table \Magento\Framework\DB\Ddl\Table */
  147. $table = $this->_connection->newTable(
  148. $this->_getTemporaryTableName($this->_productIndexerHelper->getFlatTableName($storeId))
  149. );
  150. foreach ($columns as $fieldName => $fieldProp) {
  151. $columnLength = isset($fieldProp['length']) ? $fieldProp['length'] : null;
  152. $columnDefinition = [
  153. 'nullable' => isset($fieldProp['nullable']) ? (bool)$fieldProp['nullable'] : false,
  154. 'unsigned' => isset($fieldProp['unsigned']) ? (bool)$fieldProp['unsigned'] : false,
  155. 'default' => isset($fieldProp['default']) ? $fieldProp['default'] : false,
  156. 'primary' => false,
  157. ];
  158. $columnComment = isset($fieldProp['comment']) ? $fieldProp['comment'] : $fieldName;
  159. $table->addColumn($fieldName, $fieldProp['type'], $columnLength, $columnDefinition, $columnComment);
  160. }
  161. foreach ($indexesNeed as $indexProp) {
  162. $table->addIndex(
  163. $indexProp['KEY_NAME'],
  164. $indexProp['COLUMNS_LIST'],
  165. ['type' => $indexProp['INDEX_TYPE']]
  166. );
  167. }
  168. $table->setComment("Catalog Product Flat (Store {$storeId})");
  169. $this->_connection->dropTable(
  170. $this->_getTemporaryTableName($this->_productIndexerHelper->getFlatTableName($storeId))
  171. );
  172. $this->_connection->createTable($table);
  173. }
  174. /**
  175. * Fill temporary flat table by data from temporary flat table parts
  176. *
  177. * @param array $tables
  178. * @param int|string $storeId
  179. * @param string $valueFieldSuffix
  180. * @return void
  181. */
  182. protected function _fillTemporaryFlatTable(array $tables, $storeId, $valueFieldSuffix)
  183. {
  184. $linkField = $this->getMetadataPool()->getMetadata(ProductInterface::class)->getLinkField();
  185. $select = $this->_connection->select();
  186. $temporaryFlatTableName = $this->_getTemporaryTableName(
  187. $this->_productIndexerHelper->getFlatTableName($storeId)
  188. );
  189. $flatColumns = $this->_productIndexerHelper->getFlatColumns();
  190. $entityTableName = $this->_productIndexerHelper->getTable('catalog_product_entity');
  191. $entityTemporaryTableName = $this->_getTemporaryTableName($entityTableName);
  192. $columnsList = array_keys($tables[$entityTableName]);
  193. $websiteId = (int)$this->_storeManager->getStore($storeId)->getWebsiteId();
  194. unset($tables[$entityTableName]);
  195. $allColumns = array_merge(['entity_id', 'type_id', 'attribute_set_id'], $columnsList);
  196. /* @var $status \Magento\Eav\Model\Entity\Attribute */
  197. $status = $this->_productIndexerHelper->getAttribute('status');
  198. $statusTable = $this->_getTemporaryTableName($status->getBackendTable());
  199. $statusConditions = [
  200. sprintf('e.%s = dstatus.%s', $linkField, $linkField),
  201. 'dstatus.store_id = ' . (int)$storeId,
  202. 'dstatus.attribute_id = ' . (int)$status->getId(),
  203. ];
  204. $statusExpression = $this->_connection->getIfNullSql(
  205. 'dstatus.value',
  206. $this->_connection->quoteIdentifier("{$statusTable}.status")
  207. );
  208. $select->from(
  209. ['et' => $entityTemporaryTableName],
  210. $allColumns
  211. )->joinInner(
  212. ['e' => $this->resource->getTableName('catalog_product_entity')],
  213. 'e.entity_id = et.entity_id',
  214. []
  215. )->joinInner(
  216. ['wp' => $this->_productIndexerHelper->getTable('catalog_product_website')],
  217. 'wp.product_id = e.entity_id AND wp.website_id = ' . $websiteId,
  218. []
  219. )->joinLeft(
  220. ['dstatus' => $status->getBackend()->getTable()],
  221. implode(' AND ', $statusConditions),
  222. []
  223. )->where(
  224. $statusExpression . ' = ' . \Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED
  225. );
  226. foreach ($tables as $tableName => $columns) {
  227. $columnValueNames = [];
  228. $temporaryTableName = $this->_getTemporaryTableName($tableName);
  229. $temporaryValueTableName = $temporaryTableName . $valueFieldSuffix;
  230. $columnsNames = array_keys($columns);
  231. $select->joinLeft(
  232. $temporaryTableName,
  233. "e.entity_id = " . $temporaryTableName . ".entity_id",
  234. $columnsNames
  235. );
  236. $allColumns = array_merge($allColumns, $columnsNames);
  237. foreach ($columnsNames as $name) {
  238. $columnValueName = $name . $valueFieldSuffix;
  239. if (isset($flatColumns[$columnValueName])) {
  240. $columnValueNames[] = $columnValueName;
  241. }
  242. }
  243. if (!empty($columnValueNames)) {
  244. $select->joinLeft(
  245. $temporaryValueTableName,
  246. "e.${linkField} = " . $temporaryValueTableName . ".entity_id",
  247. $columnValueNames
  248. );
  249. $allColumns = array_merge($allColumns, $columnValueNames);
  250. }
  251. }
  252. $sql = $select->insertFromSelect($temporaryFlatTableName, $allColumns, false);
  253. $this->_connection->query($sql);
  254. }
  255. /**
  256. * Apply diff. between 0 store and current store to temporary flat table
  257. *
  258. * @param array $tables
  259. * @param array $changedIds
  260. * @param int|string $storeId
  261. * @param string $valueFieldSuffix
  262. * @return void
  263. */
  264. protected function _updateTemporaryTableByStoreValues(
  265. array $tables,
  266. array $changedIds,
  267. $storeId,
  268. $valueFieldSuffix
  269. ) {
  270. $flatColumns = $this->_productIndexerHelper->getFlatColumns();
  271. $temporaryFlatTableName = $this->_getTemporaryTableName(
  272. $this->_productIndexerHelper->getFlatTableName($storeId)
  273. );
  274. $linkField = $this->getMetadataPool()->getMetadata(ProductInterface::class)->getLinkField();
  275. foreach ($tables as $tableName => $columns) {
  276. foreach ($columns as $attribute) {
  277. /* @var $attribute \Magento\Eav\Model\Entity\Attribute */
  278. $attributeCode = $attribute->getAttributeCode();
  279. if ($attribute->getBackend()->getType() != 'static') {
  280. $joinCondition = sprintf('t.%s = e.%s', $linkField, $linkField) .
  281. ' AND t.attribute_id=' .
  282. $attribute->getId() .
  283. ' AND t.store_id = ' .
  284. $storeId .
  285. ' AND t.value IS NOT NULL';
  286. /** @var $select \Magento\Framework\DB\Select */
  287. $select = $this->_connection->select()
  288. ->joinInner(
  289. ['e' => $this->resource->getTableName('catalog_product_entity')],
  290. 'e.entity_id = et.entity_id',
  291. []
  292. )->joinInner(
  293. ['t' => $tableName],
  294. $joinCondition,
  295. [$attributeCode => 't.value']
  296. );
  297. if (!empty($changedIds)) {
  298. $select->where($this->_connection->quoteInto('et.entity_id IN (?)', $changedIds));
  299. }
  300. $sql = $select->crossUpdateFromSelect(['et' => $temporaryFlatTableName]);
  301. $this->_connection->query($sql);
  302. }
  303. //Update not simple attributes (eg. dropdown)
  304. if (isset($flatColumns[$attributeCode . $valueFieldSuffix])) {
  305. $select = $this->_connection->select()->joinInner(
  306. ['t' => $this->_productIndexerHelper->getTable('eav_attribute_option_value')],
  307. 't.option_id = et.' . $attributeCode . ' AND t.store_id=' . $storeId,
  308. [$attributeCode . $valueFieldSuffix => 't.value']
  309. );
  310. if (!empty($changedIds)) {
  311. $select->where($this->_connection->quoteInto('et.entity_id IN (?)', $changedIds));
  312. }
  313. $sql = $select->crossUpdateFromSelect(['et' => $temporaryFlatTableName]);
  314. $this->_connection->query($sql);
  315. }
  316. }
  317. }
  318. }
  319. /**
  320. * Retrieve temporary table name by regular table name
  321. *
  322. * @param string $tableName
  323. * @return string
  324. */
  325. protected function _getTemporaryTableName($tableName)
  326. {
  327. return sprintf('%s_tmp_indexer', $tableName);
  328. }
  329. /**
  330. * @return \Magento\Framework\EntityManager\MetadataPool
  331. */
  332. private function getMetadataPool()
  333. {
  334. if (null === $this->metadataPool) {
  335. $this->metadataPool = \Magento\Framework\App\ObjectManager::getInstance()
  336. ->get(\Magento\Framework\EntityManager\MetadataPool::class);
  337. }
  338. return $this->metadataPool;
  339. }
  340. }