Skip to content

Commit d42b74d

Browse files
author
Mastiuhin Olexandr
committed
MAGETWO-95838: Custom product attribute does not update when using Catalog Product Flat tables
1 parent 7d6b0d7 commit d42b74d

File tree

9 files changed

+310
-67
lines changed

9 files changed

+310
-67
lines changed

app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Eraser.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,9 +118,10 @@ public function removeDisabledProducts(array &$ids, $storeId)
118118
private function getSelectForProducts(array $ids)
119119
{
120120
$productTable = $this->productIndexerHelper->getTable('catalog_product_entity');
121-
$select = $this->connection->select()->from($productTable)
121+
$select = $this->connection->select()
122+
->from(['product_table' => $productTable])
122123
->columns('entity_id')
123-
->where('entity_id IN(?)', $ids);
124+
->where('product_table.entity_id IN(?)', $ids);
124125
return $select;
125126
}
126127

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

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use Magento\Catalog\Api\Data\ProductInterface;
1010
use Magento\Framework\App\ResourceConnection;
1111
use Magento\Framework\EntityManager\MetadataPool;
12+
use Magento\Store\Model\Store;
1213

1314
/**
1415
* Class Indexer
@@ -84,7 +85,7 @@ public function write($storeId, $productId, $valueFieldSuffix = '')
8485
[
8586
'entity_id' => 'e.entity_id',
8687
'attribute_id' => 't.attribute_id',
87-
'value' => $this->_connection->getIfNullSql('`t2`.`value`', '`t`.`value`'),
88+
'value' => 't.value'
8889
]
8990
);
9091

@@ -99,32 +100,30 @@ public function write($storeId, $productId, $valueFieldSuffix = '')
99100
sprintf('e.%s = t.%s ', $linkField, $linkField) . $this->_connection->quoteInto(
100101
' AND t.attribute_id IN (?)',
101102
array_keys($ids)
102-
) . ' AND t.store_id = 0',
103-
[]
104-
)->joinLeft(
105-
['t2' => $tableName],
106-
sprintf('t.%s = t2.%s ', $linkField, $linkField) .
107-
' AND t.attribute_id = t2.attribute_id ' .
108-
$this->_connection->quoteInto(
109-
' AND t2.store_id = ?',
110-
$storeId
111-
),
103+
) . ' AND ' . $this->_connection->quoteInto('t.store_id IN(?)', [
104+
Store::DEFAULT_STORE_ID,
105+
$storeId
106+
]),
112107
[]
113108
)->where(
114109
'e.entity_id = ' . $productId
115-
);
110+
)->order('t.store_id ASC');
116111
$cursor = $this->_connection->query($select);
117112
while ($row = $cursor->fetch(\Zend_Db::FETCH_ASSOC)) {
118113
$updateData[$ids[$row['attribute_id']]] = $row['value'];
119114
$valueColumnName = $ids[$row['attribute_id']] . $valueFieldSuffix;
120115
if (isset($describe[$valueColumnName])) {
121-
$valueColumns[$row['value']] = $valueColumnName;
116+
$valueColumns[$row['attribute_id']] = [
117+
'value' => $row['value'],
118+
'column_name' => $valueColumnName
119+
];
122120
}
123121
}
124122

125123
//Update not simple attributes (eg. dropdown)
126124
if (!empty($valueColumns)) {
127-
$valueIds = array_keys($valueColumns);
125+
$valueIds = array_column($valueColumns, 'value');
126+
$optionIdToAttributeName = array_column($valueColumns, 'column_name', 'value');
128127

129128
$select = $this->_connection->select()->from(
130129
['t' => $this->_productIndexerHelper->getTable('eav_attribute_option_value')],
@@ -133,14 +132,14 @@ public function write($storeId, $productId, $valueFieldSuffix = '')
133132
$this->_connection->quoteInto('t.option_id IN (?)', $valueIds)
134133
)->where(
135134
$this->_connection->quoteInto('t.store_id IN(?)', [
136-
\Magento\Store\Model\Store::DEFAULT_STORE_ID,
135+
Store::DEFAULT_STORE_ID,
137136
$storeId
138137
])
139138
)
140139
->order('t.store_id ASC');
141140
$cursor = $this->_connection->query($select);
142141
while ($row = $cursor->fetch(\Zend_Db::FETCH_ASSOC)) {
143-
$valueColumnName = $valueColumns[$row['option_id']];
142+
$valueColumnName = $optionIdToAttributeName[$row['option_id']];
144143
if (isset($describe[$valueColumnName])) {
145144
$updateData[$valueColumnName] = $row['value'];
146145
}
@@ -150,6 +149,7 @@ public function write($storeId, $productId, $valueFieldSuffix = '')
150149
$columnNames = array_keys($columns);
151150
$columnNames[] = 'attribute_set_id';
152151
$columnNames[] = 'type_id';
152+
$columnNames[] = $linkField;
153153
$select->from(
154154
['e' => $entityTableName],
155155
$columnNames
@@ -159,6 +159,7 @@ public function write($storeId, $productId, $valueFieldSuffix = '')
159159
$cursor = $this->_connection->query($select);
160160
$row = $cursor->fetch(\Zend_Db::FETCH_ASSOC);
161161
if (!empty($row)) {
162+
$linkFieldId = $linkField;
162163
foreach ($row as $columnName => $value) {
163164
$updateData[$columnName] = $value;
164165
}
@@ -170,7 +171,7 @@ public function write($storeId, $productId, $valueFieldSuffix = '')
170171
if (!empty($updateData)) {
171172
$updateData += ['entity_id' => $productId];
172173
if ($linkField !== $metadata->getIdentifierField()) {
173-
$updateData += [$linkField => $productId];
174+
$updateData += [$linkField => $linkFieldId];
174175
}
175176
$updateFields = [];
176177
foreach ($updateData as $key => $value) {

app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Row.php

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -96,15 +96,17 @@ public function execute($id = null)
9696
/* @var $status \Magento\Eav\Model\Entity\Attribute */
9797
$status = $this->_productIndexerHelper->getAttribute(ProductInterface::STATUS);
9898
$statusTable = $status->getBackend()->getTable();
99+
$catalogProductEntityTable = $this->_productIndexerHelper->getTable('catalog_product_entity');
99100
$statusConditions = [
100-
'store_id IN(0,' . (int)$store->getId() . ')',
101-
'attribute_id = ' . (int)$status->getId(),
102-
$linkField . ' = ' . (int)$id,
101+
's.store_id IN(0,' . (int)$store->getId() . ')',
102+
's.attribute_id = ' . (int)$status->getId(),
103+
'e.entity_id = ' . (int)$id,
103104
];
104105
$select = $this->_connection->select();
105-
$select->from($statusTable, ['value'])
106+
$select->from(['e' => $catalogProductEntityTable], ['s.value'])
106107
->where(implode(' AND ', $statusConditions))
107-
->order('store_id DESC')
108+
->joinLeft(['s' => $statusTable], "e.{$linkField} = s.{$linkField}", [])
109+
->order('s.store_id DESC')
108110
->limit(1);
109111
$result = $this->_connection->query($select);
110112
$status = $result->fetchColumn(0);

app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/Action/EraserTest.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,14 @@ public function testRemoveDeletedProducts()
5353
{
5454
$productsToDeleteIds = [1, 2];
5555
$select = $this->createMock(\Magento\Framework\DB\Select::class);
56-
$select->expects($this->once())->method('from')->with('catalog_product_entity')->will($this->returnSelf());
56+
$select->expects($this->once())
57+
->method('from')
58+
->with(['product_table' => 'catalog_product_entity'])
59+
->will($this->returnSelf());
5760
$select->expects($this->once())->method('columns')->with('entity_id')->will($this->returnSelf());
58-
$select->expects($this->once())->method('where')->with('entity_id IN(?)', $productsToDeleteIds)
61+
$select->expects($this->once())
62+
->method('where')
63+
->with('product_table.entity_id IN(?)', $productsToDeleteIds)
5964
->will($this->returnSelf());
6065
$products = [['entity_id' => 2]];
6166
$statement = $this->createMock(\Zend_Db_Statement_Interface::class);

app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/Action/RowTest.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -100,10 +100,10 @@ protected function setUp()
100100
->disableOriginalConstructor()
101101
->getMock();
102102
$this->connection->expects($this->any())->method('select')->willReturn($selectMock);
103-
$selectMock->expects($this->any())->method('from')->with(
104-
$attributeTable,
105-
['value']
106-
)->willReturnSelf();
103+
$selectMock->method('from')
104+
->willReturnSelf();
105+
$selectMock->method('joinLeft')
106+
->willReturnSelf();
107107
$selectMock->expects($this->any())->method('where')->willReturnSelf();
108108
$selectMock->expects($this->any())->method('order')->willReturnSelf();
109109
$selectMock->expects($this->any())->method('limit')->willReturnSelf();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Catalog\Model\Indexer\Product\Flat\Action;
9+
10+
use Magento\TestFramework\Indexer\TestCase as IndexerTestCase;
11+
use Magento\TestFramework\Helper\Bootstrap;
12+
use Magento\Catalog\Api\ProductRepositoryInterface;
13+
use Magento\Catalog\Model\Indexer\Product\Flat\Processor;
14+
use Magento\Catalog\Model\ResourceModel\Product\Flat;
15+
use Magento\Framework\Api\SearchCriteriaBuilder;
16+
17+
/**
18+
* Custom Flat Attribute Test
19+
*/
20+
class CustomFlatAttributeTest extends IndexerTestCase
21+
{
22+
/**
23+
* @var Processor
24+
*/
25+
private $processor;
26+
27+
/**
28+
* @var \Magento\Framework\ObjectManagerInterface
29+
*/
30+
private $objectManager;
31+
32+
/**
33+
* @var ProductRepositoryInterface
34+
*/
35+
private $productRepository;
36+
37+
/**
38+
* @inheritdoc
39+
*/
40+
protected function setUp(): void
41+
{
42+
$this->objectManager = Bootstrap::getObjectManager();
43+
$this->processor = $this->objectManager->get(Processor::class);
44+
$this->productRepository = $this->objectManager->get(ProductRepositoryInterface::class);
45+
}
46+
47+
/**
48+
* Tests that custom product attribute will appear in flat table and can be updated in it.
49+
*
50+
* @magentoDbIsolation disabled
51+
* @magentoAppArea frontend
52+
* @magentoConfigFixture current_store catalog/frontend/flat_catalog_product 1
53+
* @magentoDataFixture Magento/Catalog/_files/product_simple_with_custom_attribute_in_flat.php
54+
*
55+
* @return void
56+
* @throws \Magento\Framework\Exception\CouldNotSaveException
57+
* @throws \Magento\Framework\Exception\InputException
58+
* @throws \Magento\Framework\Exception\NoSuchEntityException
59+
* @throws \Magento\Framework\Exception\StateException
60+
*/
61+
public function testProductUpdateCustomAttribute(): void
62+
{
63+
$product = $this->productRepository->get('simple_with_custom_flat_attribute');
64+
$product->setCustomAttribute('flat_attribute', 'changed flat attribute');
65+
$this->productRepository->save($product);
66+
67+
/** @var SearchCriteriaBuilder $searchCriteriaBuilder */
68+
$searchCriteriaBuilder = $this->objectManager->create(SearchCriteriaBuilder::class);
69+
/** @var \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria */
70+
$searchCriteria = $searchCriteriaBuilder->addFilter('sku', 'simple_with_custom_flat_attribute')
71+
->create();
72+
73+
$items = $this->productRepository->getList($searchCriteria)
74+
->getItems();
75+
$product = reset($items);
76+
$resourceModel = $product->getResourceCollection()
77+
->getEntity();
78+
79+
self::assertInstanceOf(
80+
Flat::class,
81+
$resourceModel,
82+
'Product should be received from flat resource'
83+
);
84+
85+
self::assertEquals(
86+
'changed flat attribute',
87+
$product->getFlatAttribute(),
88+
'Product flat attribute should be able to change.'
89+
);
90+
}
91+
}

0 commit comments

Comments
 (0)