Magento 2 Documentation  2.3
Documentation for Magento 2 CMS v2.3 (December 2018)
FlatTableBuilder.php
Go to the documentation of this file.
1 <?php
7 
11 
17 {
21  protected $metadataPool;
22 
26  const XML_NODE_MAX_INDEX_COUNT = 'catalog/product/flat/max_index_count';
27 
32 
36  protected $_connection;
37 
41  protected $_config;
42 
46  protected $_storeManager;
47 
51  protected $_tableData;
52 
56  protected $resource;
57 
65  public function __construct(
66  \Magento\Catalog\Helper\Product\Flat\Indexer $productIndexerHelper,
70  \Magento\Catalog\Model\Indexer\Product\Flat\TableDataInterface $tableData
71  ) {
72  $this->_productIndexerHelper = $productIndexerHelper;
73  $this->resource = $resource;
74  $this->_connection = $resource->getConnection();
75  $this->_config = $config;
76  $this->_storeManager = $storeManager;
77  $this->_tableData = $tableData;
78  }
79 
90  public function build($storeId, $changedIds, $valueFieldSuffix, $tableDropSuffix, $fillTmpTables)
91  {
92  $attributes = $this->_productIndexerHelper->getAttributes();
93  $eavAttributes = $this->_productIndexerHelper->getTablesStructure($attributes);
94 
95  $this->_createTemporaryFlatTable($storeId);
96 
97  if ($fillTmpTables) {
98  $this->_fillTemporaryFlatTable($eavAttributes, $storeId, $valueFieldSuffix);
99  //Update zero based attributes by values from current store
100  $this->_updateTemporaryTableByStoreValues($eavAttributes, $changedIds, $storeId, $valueFieldSuffix);
101  }
102 
103  $flatTable = $this->_productIndexerHelper->getFlatTableName($storeId);
104  $flatDropName = $flatTable . $tableDropSuffix;
105  $temporaryFlatTableName = $this->_getTemporaryTableName(
106  $this->_productIndexerHelper->getFlatTableName($storeId)
107  );
108  $this->_tableData->move($flatTable, $flatDropName, $temporaryFlatTableName);
109  }
110 
120  protected function _createTemporaryFlatTable($storeId)
121  {
122  $columns = $this->_productIndexerHelper->getFlatColumns();
123 
124  $indexesNeed = $this->_productIndexerHelper->getFlatIndexes();
125 
126  $maxIndex = $this->_config->getValue(
127  self::XML_NODE_MAX_INDEX_COUNT
128  );
129  if ($maxIndex && count($indexesNeed) > $maxIndex) {
130  throw new \Magento\Framework\Exception\LocalizedException(
131  __(
132  'The Flat Catalog module has a limit of %2$d filterable and/or sortable attributes.'
133  . 'Currently there are %1$d of them.'
134  . 'Please reduce the number of filterable/sortable attributes in order to use this module',
135  count($indexesNeed),
136  $maxIndex
137  )
138  );
139  }
140 
141  $indexKeys = [];
142  $indexProps = array_values($indexesNeed);
143  $upperPrimaryKey = strtoupper(\Magento\Framework\DB\Adapter\AdapterInterface::INDEX_TYPE_PRIMARY);
144  foreach ($indexProps as $i => $indexProp) {
145  $indexName = $this->_connection->getIndexName(
146  $this->_getTemporaryTableName($this->_productIndexerHelper->getFlatTableName($storeId)),
147  $indexProp['fields'],
148  $indexProp['type']
149  );
150  $indexProp['type'] = strtoupper($indexProp['type']);
151  if ($indexProp['type'] == $upperPrimaryKey) {
152  $indexKey = $upperPrimaryKey;
153  } else {
154  $indexKey = $indexName;
155  }
156 
157  $indexProps[$i] = [
158  'KEY_NAME' => $indexName,
159  'COLUMNS_LIST' => $indexProp['fields'],
160  'INDEX_TYPE' => strtolower($indexProp['type']),
161  ];
162  $indexKeys[$i] = $indexKey;
163  }
164  $indexesNeed = array_combine($indexKeys, $indexProps);
165 
167  $table = $this->_connection->newTable(
168  $this->_getTemporaryTableName($this->_productIndexerHelper->getFlatTableName($storeId))
169  );
170  foreach ($columns as $fieldName => $fieldProp) {
171  $columnLength = isset($fieldProp['length']) ? $fieldProp['length'] : null;
172 
173  $columnDefinition = [
174  'nullable' => isset($fieldProp['nullable']) ? (bool)$fieldProp['nullable'] : false,
175  'unsigned' => isset($fieldProp['unsigned']) ? (bool)$fieldProp['unsigned'] : false,
176  'default' => isset($fieldProp['default']) ? $fieldProp['default'] : false,
177  'primary' => false,
178  ];
179 
180  $columnComment = isset($fieldProp['comment']) ? $fieldProp['comment'] : $fieldName;
181 
182  if ($fieldName == 'created_at') {
183  $columnDefinition['nullable'] = true;
184  $columnDefinition['default'] = null;
185  }
186 
187  $table->addColumn($fieldName, $fieldProp['type'], $columnLength, $columnDefinition, $columnComment);
188  }
189 
190  foreach ($indexesNeed as $indexProp) {
191  $table->addIndex(
192  $indexProp['KEY_NAME'],
193  $indexProp['COLUMNS_LIST'],
194  ['type' => $indexProp['INDEX_TYPE']]
195  );
196  }
197 
198  $table->setComment("Catalog Product Flat (Store {$storeId})");
199 
200  $this->_connection->dropTable(
201  $this->_getTemporaryTableName($this->_productIndexerHelper->getFlatTableName($storeId))
202  );
203  $this->_connection->createTable($table);
204  }
205 
214  protected function _fillTemporaryFlatTable(array $tables, $storeId, $valueFieldSuffix)
215  {
216  $linkField = $this->getMetadataPool()->getMetadata(ProductInterface::class)->getLinkField();
217  $select = $this->_connection->select();
218  $temporaryFlatTableName = $this->_getTemporaryTableName(
219  $this->_productIndexerHelper->getFlatTableName($storeId)
220  );
221  $flatColumns = $this->_productIndexerHelper->getFlatColumns();
222  $entityTableName = $this->_productIndexerHelper->getTable('catalog_product_entity');
223  $entityTemporaryTableName = $this->_getTemporaryTableName($entityTableName);
224  $columnsList = array_keys($tables[$entityTableName]);
225  $websiteId = (int)$this->_storeManager->getStore($storeId)->getWebsiteId();
226 
227  unset($tables[$entityTableName]);
228 
229  $allColumns = array_values(
230  array_unique(
231  array_merge(['entity_id', $linkField, 'type_id', 'attribute_set_id'], $columnsList)
232  )
233  );
234 
235  /* @var $status \Magento\Eav\Model\Entity\Attribute */
236  $status = $this->_productIndexerHelper->getAttribute('status');
237  $statusTable = $this->_getTemporaryTableName($status->getBackendTable());
238  $statusConditions = [
239  sprintf('e.%s = dstatus.%s', $linkField, $linkField),
240  'dstatus.store_id = ' . (int)$storeId,
241  'dstatus.attribute_id = ' . (int)$status->getId(),
242  ];
243  $statusExpression = $this->_connection->getIfNullSql(
244  'dstatus.value',
245  $this->_connection->quoteIdentifier("{$statusTable}.status")
246  );
247 
248  $select->from(
249  ['et' => $entityTemporaryTableName],
250  $allColumns
251  )->joinInner(
252  ['e' => $this->resource->getTableName('catalog_product_entity')],
253  'e.entity_id = et.entity_id',
254  []
255  )->joinInner(
256  ['wp' => $this->_productIndexerHelper->getTable('catalog_product_website')],
257  'wp.product_id = e.entity_id AND wp.website_id = ' . $websiteId,
258  []
259  )->joinLeft(
260  ['dstatus' => $status->getBackend()->getTable()],
261  implode(' AND ', $statusConditions),
262  []
263  )->where(
264  $statusExpression . ' = ' . \Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED
265  );
266 
267  foreach ($tables as $tableName => $columns) {
268  $columnValueNames = [];
269  $temporaryTableName = $this->_getTemporaryTableName($tableName);
270  $temporaryValueTableName = $temporaryTableName . $valueFieldSuffix;
271  $columnsNames = array_keys($columns);
272 
273  $select->joinLeft(
274  $temporaryTableName,
275  sprintf('e.%1$s = %2$s.%1$s', $linkField, $temporaryTableName),
276  $columnsNames
277  );
278  $allColumns = array_merge($allColumns, $columnsNames);
279 
280  foreach ($columnsNames as $name) {
281  $columnValueName = $name . $valueFieldSuffix;
282  if (isset($flatColumns[$columnValueName])) {
283  $columnValueNames[] = $columnValueName;
284  }
285  }
286  if (!empty($columnValueNames)) {
287  $select->joinLeft(
288  $temporaryValueTableName,
289  sprintf('e.%1$s = %2$s.%1$s', $linkField, $temporaryValueTableName),
290  $columnValueNames
291  );
292  $allColumns = array_merge($allColumns, $columnValueNames);
293  }
294  }
295  $sql = $select->insertFromSelect($temporaryFlatTableName, $allColumns, false);
296  $this->_connection->query($sql);
297  }
298 
308  protected function _updateTemporaryTableByStoreValues(
309  array $tables,
310  array $changedIds,
311  $storeId,
312  $valueFieldSuffix
313  ) {
314  $flatColumns = $this->_productIndexerHelper->getFlatColumns();
315  $temporaryFlatTableName = $this->_getTemporaryTableName(
316  $this->_productIndexerHelper->getFlatTableName($storeId)
317  );
318  $linkField = $this->getMetadataPool()->getMetadata(ProductInterface::class)->getLinkField();
319  foreach ($tables as $tableName => $columns) {
320  foreach ($columns as $attribute) {
321  /* @var $attribute \Magento\Eav\Model\Entity\Attribute */
322  $attributeCode = $attribute->getAttributeCode();
323  if ($attribute->getBackend()->getType() != 'static') {
324  $joinCondition = sprintf('t.%s = e.%s', $linkField, $linkField) .
325  ' AND t.attribute_id=' .
326  $attribute->getId() .
327  ' AND t.store_id = ' .
328  $storeId .
329  ' AND t.value IS NOT NULL';
331  $select = $this->_connection->select()
332  ->joinInner(
333  ['e' => $this->resource->getTableName('catalog_product_entity')],
334  'e.entity_id = et.entity_id',
335  []
336  )->joinInner(
337  ['t' => $tableName],
338  $joinCondition,
339  [$attributeCode => 't.value']
340  );
341  if (!empty($changedIds)) {
342  $select->where($this->_connection->quoteInto('et.entity_id IN (?)', $changedIds));
343  }
344  $sql = $select->crossUpdateFromSelect(['et' => $temporaryFlatTableName]);
345  $this->_connection->query($sql);
346  }
347 
348  //Update not simple attributes (eg. dropdown)
349  if (isset($flatColumns[$attributeCode . $valueFieldSuffix])) {
350  $select = $this->_connection->select()->joinInner(
351  ['t' => $this->_productIndexerHelper->getTable('eav_attribute_option_value')],
352  't.option_id = et.' . $attributeCode . ' AND t.store_id=' . $storeId,
353  [$attributeCode . $valueFieldSuffix => 't.value']
354  );
355  if (!empty($changedIds)) {
356  $select->where($this->_connection->quoteInto('et.entity_id IN (?)', $changedIds));
357  }
358  $sql = $select->crossUpdateFromSelect(['et' => $temporaryFlatTableName]);
359  $this->_connection->query($sql);
360  }
361  }
362  }
363  }
364 
371  protected function _getTemporaryTableName($tableName)
372  {
373  return sprintf('%s_tmp_indexer', $tableName);
374  }
375 
379  private function getMetadataPool()
380  {
381  if (null === $this->metadataPool) {
383  ->get(\Magento\Framework\EntityManager\MetadataPool::class);
384  }
385  return $this->metadataPool;
386  }
387 }
$tableName
Definition: trigger.php:13
_fillTemporaryFlatTable(array $tables, $storeId, $valueFieldSuffix)
__construct(\Magento\Catalog\Helper\Product\Flat\Indexer $productIndexerHelper, \Magento\Framework\App\ResourceConnection $resource, \Magento\Framework\App\Config\ScopeConfigInterface $config, \Magento\Store\Model\StoreManagerInterface $storeManager, \Magento\Catalog\Model\Indexer\Product\Flat\TableDataInterface $tableData)
$config
Definition: fraud_order.php:17
$storeManager
__()
Definition: __.php:13
$columns
Definition: default.phtml:15
$attributeCode
Definition: extend.phtml:12
$status
Definition: order_status.php:8
build($storeId, $changedIds, $valueFieldSuffix, $tableDropSuffix, $fillTmpTables)
$attributes
Definition: matrix.phtml:13
$table
Definition: trigger.php:14
$i
Definition: gallery.phtml:31
if(!isset($_GET['name'])) $name
Definition: log.php:14