Skip to content

Commit c8a979a

Browse files
Merge branch '2.4-develop' into ACQE-7152-mainline-functional-tests-deployment
2 parents 68416cb + d50f6b5 commit c8a979a

File tree

20 files changed

+731
-137
lines changed

20 files changed

+731
-137
lines changed

app/code/Magento/Catalog/Model/Product/Price/PricePersistence.php

+12-15
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?php
22
/**
3-
* Copyright © Magento, Inc. All rights reserved.
4-
* See COPYING.txt for license details.
3+
* Copyright 2016 Adobe
4+
* All Rights Reserved.
55
*/
66

77
namespace Magento\Catalog\Model\Product\Price;
@@ -10,6 +10,7 @@
1010
use Magento\Catalog\Api\ProductAttributeRepositoryInterface;
1111
use Magento\Catalog\Model\ProductIdLocatorInterface;
1212
use Magento\Catalog\Model\ResourceModel\Attribute;
13+
use Magento\Catalog\Model\ResourceModel\Product\Price\BasePriceFactory;
1314
use Magento\Framework\App\ObjectManager;
1415
use Magento\Framework\EntityManager\MetadataPool;
1516
use Magento\Framework\Exception\CouldNotDeleteException;
@@ -88,14 +89,16 @@ class PricePersistence
8889
* @param MetadataPool $metadataPool
8990
* @param string $attributeCode
9091
* @param DateTime|null $dateTime
92+
* @param BasePriceFactory|null $basePriceFactory
9193
*/
9294
public function __construct(
9395
Attribute $attributeResource,
9496
ProductAttributeRepositoryInterface $attributeRepository,
9597
ProductIdLocatorInterface $productIdLocator,
9698
MetadataPool $metadataPool,
9799
$attributeCode = '',
98-
?DateTime $dateTime = null
100+
?DateTime $dateTime = null,
101+
private ?BasePriceFactory $basePriceFactory = null
99102
) {
100103
$this->attributeResource = $attributeResource;
101104
$this->attributeRepository = $attributeRepository;
@@ -104,6 +107,8 @@ public function __construct(
104107
$this->metadataPool = $metadataPool;
105108
$this->dateTime = $dateTime ?: ObjectManager::getInstance()
106109
->get(DateTime::class);
110+
$this->basePriceFactory = $this->basePriceFactory ?: ObjectManager::getInstance()
111+
->get(BasePriceFactory::class);
107112
}
108113

109114
/**
@@ -134,21 +139,13 @@ public function get(array $skus)
134139
public function update(array $prices)
135140
{
136141
array_walk($prices, function (&$price) {
137-
return $price['attribute_id'] = $this->getAttributeId();
142+
return $price['attribute_id'] = (int)$this->getAttributeId();
138143
});
139-
$connection = $this->attributeResource->getConnection();
140-
$connection->beginTransaction();
144+
145+
$basePrice = $this->basePriceFactory->create(['attributeId' => (int)$this->getAttributeId()]);
141146
try {
142-
foreach (array_chunk($prices, $this->itemsPerOperation) as $pricesBunch) {
143-
$this->attributeResource->getConnection()->insertOnDuplicate(
144-
$this->attributeResource->getTable($this->table),
145-
$pricesBunch,
146-
['value']
147-
);
148-
}
149-
$connection->commit();
147+
$basePrice->update($prices);
150148
} catch (\Exception $e) {
151-
$connection->rollBack();
152149
throw new CouldNotSaveException(
153150
__('Could not save Prices.'),
154151
$e
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
<?php
2+
/**
3+
* Copyright 2024 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Catalog\Model\ResourceModel\Product\Price;
9+
10+
use Magento\Catalog\Api\Data\ProductInterface;
11+
use Magento\Catalog\Model\ResourceModel\Attribute;
12+
use Magento\Framework\DataObject;
13+
use Magento\Framework\EntityManager\MetadataPool;
14+
use Magento\Framework\Exception\CouldNotSaveException;
15+
16+
class BasePrice
17+
{
18+
/**
19+
* Price storage table.
20+
*
21+
* @var string
22+
*/
23+
private $table = 'catalog_product_entity_decimal';
24+
25+
/**
26+
* @param Attribute $attributeResource
27+
* @param MetadataPool $metadataPool
28+
* @param int $attributeId
29+
*/
30+
public function __construct(
31+
private readonly Attribute $attributeResource,
32+
private readonly MetadataPool $metadataPool,
33+
private readonly int $attributeId = 0,
34+
) {
35+
}
36+
37+
/**
38+
* Get prices that will need update
39+
*
40+
* @param array $priceBunch
41+
* @return array
42+
* @throws \Exception
43+
*/
44+
private function getExistingPrices(array $priceBunch): array
45+
{
46+
$linkField = $this->getEntityLinkField();
47+
$connection = $this->attributeResource->getConnection();
48+
49+
return $connection->fetchAll(
50+
$connection->select()
51+
->from($this->attributeResource->getTable($this->table))
52+
->where('attribute_id = ?', $this->attributeId)
53+
->where('store_id IN (?)', array_unique(array_column($priceBunch, 'store_id')))
54+
->where($linkField . ' IN (?)', array_unique(array_column($priceBunch, $linkField)))
55+
);
56+
}
57+
58+
/**
59+
* Get prices that will need update
60+
*
61+
* @param DataObject $priceBunchesObject
62+
* @param array $existingPrices
63+
* @return array
64+
* @throws \Exception
65+
*/
66+
private function getUpdatablePrices(DataObject $priceBunchesObject, array $existingPrices): array
67+
{
68+
$updateData = [];
69+
$priceBunches = $priceBunchesObject->getPrices();
70+
$linkField = $this->getEntityLinkField();
71+
foreach ($existingPrices as $existingPrice) {
72+
foreach ($priceBunches as $key => $price) {
73+
if ($price[$linkField] == $existingPrice[$linkField] &&
74+
$price['store_id'] == $existingPrice['store_id'] &&
75+
$existingPrice['attribute_id'] == $price['attribute_id']
76+
) {
77+
$priceBunches[$key]['value_id'] = $existingPrice['value_id'];
78+
$uniqueKey = $price[$linkField].'_'.$price['attribute_id'].'_'.$price['store_id'];
79+
$updateData[$uniqueKey] = $priceBunches[$key];
80+
}
81+
}
82+
}
83+
84+
$priceBunchesObject->setPrices($priceBunches);
85+
86+
return $updateData;
87+
}
88+
89+
/**
90+
* Get prices that will need insert
91+
*
92+
* @param DataObject $priceBunchesObject
93+
* @return array
94+
* @throws \Exception
95+
*/
96+
private function getInsertablePrices(DataObject $priceBunchesObject): array
97+
{
98+
$insertData = [];
99+
$priceBunches = $priceBunchesObject->getPrices();
100+
$linkField = $this->getEntityLinkField();
101+
foreach ($priceBunches as $price) {
102+
if (!isset($price['value_id'])) {
103+
$uniqueKey = $price[$linkField].'_'.$price['attribute_id'].'_'.$price['store_id'];
104+
$insertData[$uniqueKey] = $price;
105+
}
106+
}
107+
return $insertData;
108+
}
109+
110+
/**
111+
* Update existing prices
112+
*
113+
* @param array $priceBunches
114+
* @return void
115+
* @throws \Exception
116+
*/
117+
public function update(array $priceBunches): void
118+
{
119+
$priceBunchesObject = new DataObject(['prices' => $priceBunches]);
120+
$existingPrices = $this->getExistingPrices($priceBunches);
121+
$updateData = $this->getUpdatablePrices($priceBunchesObject, $existingPrices);
122+
$insertData = $this->getInsertablePrices($priceBunchesObject);
123+
124+
$connection = $this->attributeResource->getConnection();
125+
$connection->beginTransaction();
126+
try {
127+
if (!empty($updateData)) {
128+
foreach ($updateData as $row) {
129+
$this->attributeResource->getConnection()->update(
130+
$this->attributeResource->getTable($this->table),
131+
['value' => $row['value']],
132+
['value_id = ?' => (int)$row['value_id']]
133+
);
134+
}
135+
}
136+
if (!empty($insertData)) {
137+
$this->attributeResource->getConnection()->insertMultiple(
138+
$this->attributeResource->getTable($this->table),
139+
$insertData
140+
);
141+
}
142+
$connection->commit();
143+
} catch (\Exception $e) {
144+
$connection->rollBack();
145+
throw new CouldNotSaveException(
146+
__('Could not save Prices.'),
147+
$e
148+
);
149+
}
150+
}
151+
152+
/**
153+
* Get link field
154+
*
155+
* @return string
156+
* @throws \Exception
157+
*/
158+
private function getEntityLinkField(): string
159+
{
160+
return $this->metadataPool->getMetadata(ProductInterface::class)
161+
->getLinkField();
162+
}
163+
}

app/code/Magento/Catalog/Test/Unit/Model/Product/Price/PricePersistenceTest.php

+41-61
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?php
22
/**
3-
* Copyright © Magento, Inc. All rights reserved.
4-
* See COPYING.txt for license details.
3+
* Copyright 2016 Adobe
4+
* All Rights Reserved.
55
*/
66
declare(strict_types=1);
77

@@ -13,13 +13,17 @@
1313
use Magento\Catalog\Model\Product\Type;
1414
use Magento\Catalog\Model\ProductIdLocatorInterface;
1515
use Magento\Catalog\Model\ResourceModel\Attribute;
16+
use Magento\Catalog\Model\ResourceModel\Product\Price\BasePrice;
17+
use Magento\Catalog\Model\ResourceModel\Product\Price\BasePriceFactory;
1618
use Magento\Framework\DB\Adapter\AdapterInterface;
1719
use Magento\Framework\DB\Select;
1820
use Magento\Framework\EntityManager\MetadataPool;
19-
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
2021
use PHPUnit\Framework\MockObject\MockObject;
2122
use PHPUnit\Framework\TestCase;
2223

24+
/**
25+
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
26+
*/
2327
class PricePersistenceTest extends TestCase
2428
{
2529
/**
@@ -52,6 +56,11 @@ class PricePersistenceTest extends TestCase
5256
*/
5357
private $metadataPool;
5458

59+
/**
60+
* @var BasePriceFactory|MockObject
61+
*/
62+
private $basePriceFactory;
63+
5564
/**
5665
* @var PricePersistence
5766
*/
@@ -86,16 +95,18 @@ protected function setUp(): void
8695
$this->productAttribute = $this->getMockBuilder(ProductAttributeInterface::class)
8796
->disableOriginalConstructor()
8897
->getMockForAbstractClass();
98+
$this->basePriceFactory = $this->getMockBuilder(BasePriceFactory::class)
99+
->disableOriginalConstructor()
100+
->getMock();
89101

90-
$objectManager = new ObjectManager($this);
91-
$this->model = $objectManager->getObject(
92-
PricePersistence::class,
93-
[
94-
'attributeResource' => $this->attributeResource,
95-
'attributeRepository' => $this->attributeRepository,
96-
'productIdLocator' => $this->productIdLocator,
97-
'metadataPool' => $this->metadataPool,
98-
]
102+
$this->model = new PricePersistence(
103+
$this->attributeResource,
104+
$this->attributeRepository,
105+
$this->productIdLocator,
106+
$this->metadataPool,
107+
'price',
108+
null,
109+
$this->basePriceFactory
99110
);
100111
}
101112

@@ -161,34 +172,21 @@ public function testUpdate()
161172
'store_id' => 1,
162173
'row_id' => 1,
163174
'value' => 15
175+
],
176+
[
177+
'store_id' => 0,
178+
'row_id' => 2,
179+
'value' => 20
164180
]
165181
];
182+
$basePrice = $this->createMock(BasePrice::class);
183+
$basePrice->expects($this->once())
184+
->method('update');
166185
$this->attributeRepository->expects($this->once())->method('get')->willReturn($this->productAttribute);
167186
$this->productAttribute->expects($this->once())->method('getAttributeId')->willReturn($attributeId);
168-
$this->attributeResource->expects($this->atLeastOnce())->method('getConnection')->willReturn($this->connection);
169-
$this->connection->expects($this->once())->method('beginTransaction')->willReturnSelf();
170-
$this->attributeResource
171-
->expects($this->once())
172-
->method('getTable')
173-
->with('catalog_product_entity_decimal')
174-
->willReturn('catalog_product_entity_decimal');
175-
$this->connection
176-
->expects($this->once())
177-
->method('insertOnDuplicate')
178-
->with(
179-
'catalog_product_entity_decimal',
180-
[
181-
[
182-
'store_id' => 1,
183-
'row_id' => 1,
184-
'value' => 15,
185-
'attribute_id' => 5,
186-
]
187-
],
188-
['value']
189-
)
190-
->willReturnSelf();
191-
$this->connection->expects($this->once())->method('commit')->willReturnSelf();
187+
$this->basePriceFactory->expects($this->once())
188+
->method('create')
189+
->willReturn($basePrice);
192190
$this->model->update($prices);
193191
}
194192

@@ -207,33 +205,15 @@ public function testUpdateWithException()
207205
'value' => 15
208206
]
209207
];
208+
$basePrice = $this->createMock(BasePrice::class);
209+
$basePrice->expects($this->once())
210+
->method('update')
211+
->willThrowException(new \Exception());
210212
$this->attributeRepository->expects($this->once())->method('get')->willReturn($this->productAttribute);
211213
$this->productAttribute->expects($this->once())->method('getAttributeId')->willReturn($attributeId);
212-
$this->attributeResource->expects($this->atLeastOnce())->method('getConnection')->willReturn($this->connection);
213-
$this->connection->expects($this->once())->method('beginTransaction')->willReturnSelf();
214-
$this->attributeResource
215-
->expects($this->once())
216-
->method('getTable')
217-
->with('catalog_product_entity_decimal')
218-
->willReturn('catalog_product_entity_decimal');
219-
$this->connection
220-
->expects($this->once())
221-
->method('insertOnDuplicate')
222-
->with(
223-
'catalog_product_entity_decimal',
224-
[
225-
[
226-
'store_id' => 1,
227-
'row_id' => 1,
228-
'value' => 15,
229-
'attribute_id' => 5,
230-
]
231-
],
232-
['value']
233-
)
234-
->willReturnSelf();
235-
$this->connection->expects($this->once())->method('commit')->willThrowException(new \Exception());
236-
$this->connection->expects($this->once())->method('rollback')->willReturnSelf();
214+
$this->basePriceFactory->expects($this->once())
215+
->method('create')
216+
->willReturn($basePrice);
237217
$this->model->update($prices);
238218
}
239219

0 commit comments

Comments
 (0)