Skip to content

Commit d7a6f80

Browse files
committed
ACP2E-1347: Bundle product save slow
1 parent 84e7dfa commit d7a6f80

File tree

4 files changed

+261
-7
lines changed

4 files changed

+261
-7
lines changed

app/code/Magento/Bundle/Model/Option/SaveAction.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ public function save(ProductInterface $bundleProduct, OptionInterface $option)
118118
foreach ($linksToAdd as $linkedProduct) {
119119
$this->linkManagement->addChild($bundleProduct, $option->getOptionId(), $linkedProduct);
120120
}
121+
}
121122

122123
$bundleProduct->setIsRelationsChanged(true);
123124

@@ -149,6 +150,7 @@ private function updateOptionSelection(ProductInterface $product, OptionInterfac
149150
}
150151
/** @var LinkInterface[] $linksToDelete */
151152
$linksToDelete = $this->compareLinks($existingLinks, $linksToUpdate);
153+
$linksToUpdate = $this->verifyLinksToUpdate($existingLinks, $linksToUpdate);
152154
}
153155
foreach ($linksToUpdate as $linkedProduct) {
154156
$this->linkManagement->saveChild($product->getSku(), $linkedProduct);

app/code/Magento/Bundle/Model/Product/SaveHandler.php

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,7 @@ public function execute($entity, $arguments = [])
103103
$existingOptionsIds = !empty($existingBundleProductOptions)
104104
? $this->getOptionIds($existingBundleProductOptions)
105105
: [];
106-
$optionIds = !empty($bundleProductOptions)
107-
? $this->getOptionIds($bundleProductOptions)
108-
: [];
106+
$optionIds = $this->getOptionIds($bundleProductOptions);
109107

110108
if (!$entity->getCopyFromView()) {
111109
$this->processRemovedOptions($entity, $existingOptionsIds, $optionIds);
@@ -161,12 +159,11 @@ protected function removeOptionLinks($entitySku, $option)
161159
private function saveOptions($entity, array $options, array $newOptionsIds = []): void
162160
{
163161
foreach ($options as $option) {
164-
if (in_array($option->getOptionId(), $newOptionsIds, true)) {
162+
if (in_array($option->getOptionId(), $newOptionsIds)) {
165163
$option->setOptionId(null);
166164
}
167-
168-
$this->optionSave->save($entity, $option);
169165
}
166+
$this->optionSave->saveBulk($entity, $options);
170167
}
171168

172169
/**
@@ -184,7 +181,7 @@ private function getOptionIds(array $options): array
184181
/** @var OptionInterface $option */
185182
foreach ($options as $option) {
186183
if ($option->getOptionId()) {
187-
$optionIds[] = $option->getOptionId();
184+
$optionIds[] = (int)$option->getOptionId();
188185
}
189186
}
190187
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
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\Bundle\Test\Unit\Model\Option;
9+
10+
use Magento\Bundle\Api\ProductLinkManagementInterface;
11+
use Magento\Bundle\Model\Option;
12+
use Magento\Bundle\Model\Option\SaveAction;
13+
use Magento\Bundle\Model\Product\Type;
14+
use Magento\Bundle\Model\ResourceModel\Option as OptionResource;
15+
use Magento\Bundle\Model\ResourceModel\Option\Collection;
16+
use Magento\Catalog\Api\Data\ProductInterface;
17+
use Magento\Framework\EntityManager\MetadataPool;
18+
use Magento\Framework\EntityManager\EntityMetadataInterface;
19+
use PHPUnit\Framework\MockObject\MockObject;
20+
use PHPUnit\Framework\TestCase;
21+
22+
class SaveActionTest extends TestCase
23+
{
24+
/**
25+
* @var Option|MockObject
26+
*/
27+
private $optionResource;
28+
29+
/**
30+
* @var MetadataPool|MockObject
31+
*/
32+
private $metadataPool;
33+
34+
/**
35+
* @var Type|MockObject
36+
*/
37+
private $type;
38+
39+
/**
40+
* @var ProductLinkManagementInterface|MockObject
41+
*/
42+
private $linkManagement;
43+
44+
/**
45+
* @var SaveAction
46+
*/
47+
private $saveAction;
48+
49+
protected function setUp(): void
50+
{
51+
$this->linkManagement = $this->getMockBuilder(ProductLinkManagementInterface::class)
52+
->disableOriginalConstructor()
53+
->getMock();
54+
$this->metadataPool = $this->getMockBuilder(MetadataPool::class)
55+
->disableOriginalConstructor()
56+
->getMock();
57+
$this->type = $this->getMockBuilder(Type::class)
58+
->disableOriginalConstructor()
59+
->getMock();
60+
$this->optionResource = $this->getMockBuilder(OptionResource::class)
61+
->disableOriginalConstructor()
62+
->getMock();
63+
$this->product = $this->getMockBuilder(ProductInterface::class)
64+
->addMethods(['getStoreId', 'getData', 'setIsRelationsChanged'])
65+
->getMockForAbstractClass();
66+
67+
$this->saveAction = new SaveAction(
68+
$this->optionResource,
69+
$this->metadataPool,
70+
$this->type,
71+
$this->linkManagement
72+
);
73+
}
74+
75+
public function testSaveBulk()
76+
{
77+
$option = $this->getMockBuilder(Option::class)
78+
->onlyMethods(['getOptionId', 'setData', 'getData'])
79+
->addMethods(['setStoreId', 'setParentId', 'getParentId'])
80+
->disableOriginalConstructor()
81+
->getMock();
82+
$option->expects($this->any())
83+
->method('getOptionId')
84+
->willReturn(1);
85+
$option->expects($this->any())
86+
->method('getData')
87+
->willReturn([]);
88+
$bundleOptions = [$option];
89+
90+
$collection = $this->getMockBuilder(Collection::class)
91+
->disableOriginalConstructor()
92+
->getMock();
93+
$collection->expects($this->once())
94+
->method('getItemById')
95+
->with(1)
96+
->willReturn($option);
97+
$this->type->expects($this->once())
98+
->method('getOptionsCollection')
99+
->willReturn($collection);
100+
101+
$metadata = $this->getMockBuilder(EntityMetadataInterface::class)
102+
->getMockForAbstractClass();
103+
$this->metadataPool->expects($this->once())
104+
->method('getMetadata')
105+
->willReturn($metadata);
106+
107+
$this->linkManagement->expects($this->once())
108+
->method('getChildren')
109+
->willReturn([]);
110+
$this->product->expects($this->once())
111+
->method('setIsRelationsChanged')
112+
->with(true);
113+
114+
$this->saveAction->saveBulk($this->product, $bundleOptions);
115+
}
116+
}
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
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\Bundle\Test\Unit\Model\Product;
9+
10+
use Magento\Bundle\Api\ProductLinkManagementInterface;
11+
use Magento\Bundle\Api\ProductOptionRepositoryInterface as OptionRepository;
12+
use Magento\Bundle\Api\Data\OptionInterface;
13+
use Magento\Bundle\Model\Option\SaveAction;
14+
use Magento\Bundle\Model\Product\Type;
15+
use Magento\Bundle\Model\Product\SaveHandler;
16+
use Magento\Bundle\Model\Product\CheckOptionLinkIfExist;
17+
use Magento\Bundle\Model\ProductRelationsProcessorComposite;
18+
use Magento\Catalog\Api\Data\ProductInterface;
19+
use Magento\Catalog\Api\Data\ProductExtensionInterface;
20+
use Magento\Framework\EntityManager\MetadataPool;
21+
use Magento\Framework\EntityManager\EntityMetadataInterface;
22+
use PHPUnit\Framework\MockObject\MockObject;
23+
use PHPUnit\Framework\TestCase;
24+
class SaveHandlerTest extends TestCase
25+
{
26+
/**
27+
* @var ProductLinkManagementInterface|MockObject
28+
*/
29+
private $productLinkManagement;
30+
31+
/**
32+
* @var OptionRepository|MockObject
33+
*/
34+
private $optionRepository;
35+
36+
/**
37+
* @var SaveAction|MockObject
38+
*/
39+
private $optionSave;
40+
41+
/**
42+
* @var MetadataPool|MockObject
43+
*/
44+
private $metadataPool;
45+
46+
/**
47+
* @var CheckOptionLinkIfExist|MockObject
48+
*/
49+
private $checkOptionLinkIfExist;
50+
51+
/**
52+
* @var ProductRelationsProcessorComposite|MockObject
53+
*/
54+
private $productRelationsProcessorComposite;
55+
56+
/**
57+
* @var ProductInterface|MockObject
58+
*/
59+
private $entity;
60+
61+
/**
62+
* @var SaveHandler
63+
*/
64+
private $saveHandler;
65+
66+
protected function setUp(): void
67+
{
68+
$this->productLinkManagement = $this->getMockBuilder(ProductLinkManagementInterface::class)
69+
->disableOriginalConstructor()
70+
->getMock();
71+
$this->optionRepository = $this->getMockBuilder(OptionRepository::class)
72+
->disableOriginalConstructor()
73+
->getMock();
74+
$this->optionSave = $this->getMockBuilder(SaveAction::class)
75+
->disableOriginalConstructor()
76+
->getMock();
77+
$this->metadataPool = $this->getMockBuilder(MetadataPool::class)
78+
->disableOriginalConstructor()
79+
->getMock();
80+
$this->checkOptionLinkIfExist = $this->getMockBuilder(CheckOptionLinkIfExist::class)
81+
->disableOriginalConstructor()
82+
->getMock();
83+
$this->productRelationsProcessorComposite = $this->getMockBuilder(ProductRelationsProcessorComposite::class)
84+
->disableOriginalConstructor()
85+
->getMock();
86+
$this->entity = $this->getMockBuilder(ProductInterface::class)
87+
->addMethods(['getCopyFromView', 'getData'])
88+
->getMockForAbstractClass();
89+
$this->entity->expects($this->any())
90+
->method('getTypeId')
91+
->willReturn(Type::TYPE_CODE);
92+
93+
$this->saveHandler = new SaveHandler(
94+
$this->optionRepository,
95+
$this->productLinkManagement,
96+
$this->optionSave,
97+
$this->metadataPool,
98+
$this->checkOptionLinkIfExist,
99+
$this->productRelationsProcessorComposite
100+
);
101+
}
102+
103+
/**
104+
* @return void
105+
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
106+
*/
107+
public function testExecuteWithBulkOptionsProcessing(): void
108+
{
109+
$option = $this->getMockBuilder(OptionInterface::class)
110+
->onlyMethods(['getOptionId'])
111+
->getMockForAbstractClass();
112+
$option->expects($this->any())
113+
->method('getOptionId')
114+
->willReturn(1);
115+
$bundleOptions = [$option];
116+
117+
$extensionAttributes = $this->getMockBuilder(ProductExtensionInterface::class)
118+
->addMethods(['getBundleProductOptions'])
119+
->getMockForAbstractClass();
120+
$extensionAttributes->expects($this->any())
121+
->method('getBundleProductOptions')
122+
->willReturn($bundleOptions);
123+
$this->entity->expects($this->once())
124+
->method('getExtensionAttributes')
125+
->willReturn($extensionAttributes);
126+
$metadata = $this->getMockBuilder(EntityMetadataInterface::class)
127+
->getMockForAbstractClass();
128+
$this->metadataPool->expects($this->once())
129+
->method('getMetadata')
130+
->willReturn($metadata);
131+
$this->optionRepository->expects($this->any())
132+
->method('getList')
133+
->willReturn($bundleOptions);
134+
135+
$this->optionSave->expects($this->once())
136+
->method('saveBulk');
137+
$this->saveHandler->execute($this->entity);
138+
}
139+
}

0 commit comments

Comments
 (0)