Skip to content

Commit bc9df94

Browse files
authored
Merge pull request #496 from magento-performance/MAGETWO-56986
[Performance] MAGETWO-55299: Fast Save of Product Variations
2 parents 073f227 + 7981447 commit bc9df94

File tree

20 files changed

+327
-94
lines changed

20 files changed

+327
-94
lines changed

app/code/Magento/Catalog/Model/Product/Attribute/Backend/Sku.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,12 @@ protected function _generateUniqueSku($object)
7373
{
7474
$attribute = $this->getAttribute();
7575
$entity = $attribute->getEntity();
76-
$increment = $this->_getLastSimilarAttributeValueIncrement($attribute, $object);
7776
$attributeValue = $object->getData($attribute->getAttributeCode());
77+
$increment = null;
7878
while (!$entity->checkAttributeUniqueValue($attribute, $object)) {
79+
if ($increment === null) {
80+
$increment = $this->_getLastSimilarAttributeValueIncrement($attribute, $object);
81+
}
7982
$sku = trim($attributeValue);
8083
if (strlen($sku . '-' . ++$increment) > self::SKU_MAX_LENGTH) {
8184
$sku = substr($sku, 0, -strlen($increment) - 1);

app/code/Magento/Catalog/Model/Product/Gallery/CreateHandler.php

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,11 @@ class CreateHandler implements ExtensionInterface
5858
*/
5959
protected $fileStorageDb;
6060

61+
/**
62+
* @var array
63+
*/
64+
private $mediaAttributeCodes;
65+
6166
/**
6267
* @param \Magento\Framework\EntityManager\MetadataPool $metadataPool
6368
* @param \Magento\Catalog\Api\ProductAttributeRepositoryInterface $attributeRepository
@@ -145,9 +150,11 @@ public function execute($product, $arguments = [])
145150
}
146151

147152
/* @var $mediaAttribute \Magento\Catalog\Api\Data\ProductAttributeInterface */
148-
foreach ($this->mediaConfig->getMediaAttributeCodes() as $mediaAttrCode) {
153+
foreach ($this->getMediaAttributeCodes() as $mediaAttrCode) {
149154
$attrData = $product->getData($mediaAttrCode);
150-
155+
if (empty($attrData) && empty($clearImages) && empty($newImages) && empty($existImages)) {
156+
continue;
157+
}
151158
if (in_array($attrData, $clearImages)) {
152159
$product->setData($mediaAttrCode, 'no_selection');
153160
}
@@ -394,4 +401,17 @@ protected function copyImage($file)
394401
);
395402
}
396403
}
404+
405+
/**
406+
* Get Media Attribute Codes cached value
407+
*
408+
* @return array
409+
*/
410+
private function getMediaAttributeCodes()
411+
{
412+
if ($this->mediaAttributeCodes === null) {
413+
$this->mediaAttributeCodes = $this->mediaConfig->getMediaAttributeCodes();
414+
}
415+
return $this->mediaAttributeCodes;
416+
}
397417
}

app/code/Magento/Catalog/Model/Product/Link/SaveHandler.php

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ public function __construct(
4040
Link $linkResource,
4141
ProductLinkRepositoryInterface $productLinkRepository
4242
) {
43-
4443
$this->metadataPool = $metadataPool;
4544
$this->linkResource = $linkResource;
4645
$this->productLinkRepository = $productLinkRepository;
@@ -54,12 +53,18 @@ public function __construct(
5453
*/
5554
public function execute($entityType, $entity)
5655
{
57-
/** @var \Magento\Catalog\Api\Data\ProductInterface $entity*/
58-
foreach ($this->productLinkRepository->getList($entity) as $link) {
59-
$this->productLinkRepository->delete($link);
56+
$link = $entity->getData($this->metadataPool->getMetadata($entityType)->getLinkField());
57+
if ($this->linkResource->hasProductLinks($link)) {
58+
/** @var \Magento\Catalog\Api\Data\ProductInterface $entity*/
59+
foreach ($this->productLinkRepository->getList($entity) as $link) {
60+
$this->productLinkRepository->delete($link);
61+
}
6062
}
61-
foreach ($entity->getProductLinks() as $link) {
62-
$this->productLinkRepository->save($link);
63+
$productLinks = $entity->getProductLinks();
64+
if (count($productLinks) > 0) {
65+
foreach ($entity->getProductLinks() as $link) {
66+
$this->productLinkRepository->save($link);
67+
}
6368
}
6469
return $entity;
6570
}

app/code/Magento/Catalog/Model/ResourceModel/Category.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,9 @@ protected function _beforeSave(\Magento\Framework\DataObject $object)
238238
if (!$object->getChildrenCount()) {
239239
$object->setChildrenCount(0);
240240
}
241-
241+
$object->setAttributeSetId(
242+
$object->getAttributeSetId() ?: $this->getEntityType()->getDefaultAttributeSetId()
243+
);
242244
if ($object->isObjectNew()) {
243245
if ($object->getPosition() === null) {
244246
$object->setPosition($this->_getMaxPosition($object->getPath()) + 1);

app/code/Magento/Catalog/Model/ResourceModel/Eav/Attribute.php

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -351,14 +351,11 @@ public function getStoreId()
351351
*/
352352
public function getApplyTo()
353353
{
354-
if ($this->getData(self::APPLY_TO)) {
355-
if (is_array($this->getData(self::APPLY_TO))) {
356-
return $this->getData(self::APPLY_TO);
357-
}
358-
return explode(',', $this->getData(self::APPLY_TO));
359-
} else {
360-
return [];
354+
$applyTo = $this->_getData(self::APPLY_TO) ?: [];
355+
if (!is_array($applyTo)) {
356+
$applyTo = explode(',', $applyTo);
361357
}
358+
return $applyTo;
362359
}
363360

364361
/**

app/code/Magento/Catalog/Model/ResourceModel/Product/Link.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,30 @@ public function getProductLinkId($parentId, $linkedProductId, $typeId)
9696
return $connection->fetchOne($select, $bind);
9797
}
9898

99+
/**
100+
* Check if product has links.
101+
*
102+
* @param int $parentId ID of product
103+
* @return bool
104+
*/
105+
public function hasProductLinks($parentId)
106+
{
107+
$connection = $this->getConnection();
108+
$select = $connection->select()->from(
109+
$this->getMainTable(),
110+
['count' => new \Zend_Db_Expr('COUNT(*)')]
111+
)->where(
112+
'product_id = :product_id'
113+
) ;
114+
115+
return $connection->fetchOne(
116+
$select,
117+
[
118+
'product_id' => $parentId
119+
]
120+
) > 0;
121+
}
122+
99123
/**
100124
* Save Product Links process
101125
*

app/code/Magento/CatalogInventory/Model/Stock/StockItemRepository.php

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,14 @@
1515
use Magento\CatalogInventory\Model\ResourceModel\Stock\Item as StockItemResource;
1616
use Magento\CatalogInventory\Model\Spi\StockStateProviderInterface;
1717
use Magento\CatalogInventory\Model\StockRegistryStorage;
18+
use Magento\Framework\App\ObjectManager;
1819
use Magento\Framework\DB\MapperFactory;
1920
use Magento\Framework\DB\QueryBuilderFactory;
2021
use Magento\Framework\Exception\CouldNotDeleteException;
2122
use Magento\Framework\Exception\CouldNotSaveException;
2223
use Magento\Framework\Exception\NoSuchEntityException;
23-
use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
2424
use Magento\Framework\Stdlib\DateTime\DateTime;
25+
use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
2526

2627
/**
2728
* Class StockItemRepository
@@ -89,6 +90,9 @@ class StockItemRepository implements StockItemRepositoryInterface
8990
*/
9091
protected $stockRegistryStorage;
9192

93+
/** @var \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory */
94+
protected $productCollectionFactory;
95+
9296
/**
9397
* @param StockConfigurationInterface $stockConfiguration
9498
* @param StockStateProviderInterface $stockStateProvider
@@ -129,15 +133,34 @@ public function __construct(
129133
$this->dateTime = $dateTime;
130134
}
131135

136+
/**
137+
* @deprecated
138+
* @return \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory
139+
*/
140+
private function getProductCollectionFactory()
141+
{
142+
if ($this->productCollectionFactory === null) {
143+
$this->productCollectionFactory = ObjectManager::getInstance()->get(
144+
\Magento\Catalog\Model\ResourceModel\Product\CollectionFactory::class
145+
);
146+
}
147+
148+
return $this->productCollectionFactory;
149+
}
150+
132151
/**
133152
* @inheritdoc
134153
*/
135154
public function save(\Magento\CatalogInventory\Api\Data\StockItemInterface $stockItem)
136155
{
137156
try {
138157
/** @var \Magento\Catalog\Model\Product $product */
139-
$product = $this->productFactory->create();
140-
$product->load($stockItem->getProductId());
158+
$product = $this->getProductCollectionFactory()->create()
159+
->setFlag('has_stock_status_filter')
160+
->addIdFilter($stockItem->getProductId())
161+
->addFieldToSelect('type_id')
162+
->getFirstItem();
163+
141164
if (!$product->getId()) {
142165
return $stockItem;
143166
}

app/code/Magento/CatalogInventory/Test/Unit/Model/Stock/StockItemRepositoryTest.php

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
*/
66
namespace Magento\CatalogInventory\Test\Unit\Model\Stock;
77

8+
use Magento\Catalog\Model\ResourceModel\Product\Collection;
9+
use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory;
810
use Magento\CatalogInventory\Model\Stock\StockItemRepository;
911
use Magento\CatalogInventory\Api\Data as InventoryApiData;
1012
use Magento\CatalogInventory\Model\StockRegistryStorage;
@@ -153,9 +155,8 @@ protected function setUp()
153155
->disableOriginalConstructor()
154156
->setMethods(['load', 'getId', 'getTypeId', '__wakeup'])
155157
->getMock();
156-
$this->productFactoryMock->expects($this->any())
157-
->method('create')
158-
->willReturn($this->productMock);
158+
159+
$this->productFactoryMock->expects($this->any())->method('create')->willReturn($this->productMock);
159160

160161
$this->queryBuilderFactoryMock = $this->getMockBuilder(\Magento\Framework\DB\QueryBuilderFactory::class)
161162
->setMethods(['create'])
@@ -185,6 +186,22 @@ protected function setUp()
185186
->disableOriginalConstructor()
186187
->getMock();
187188

189+
$productCollection = $this->getMockBuilder(
190+
\Magento\Catalog\Model\ResourceModel\Product\Collection::class
191+
)->disableOriginalConstructor()->getMock();
192+
193+
$productCollection->expects($this->any())->method('setFlag')->willReturnSelf();
194+
$productCollection->expects($this->any())->method('addIdFilter')->willReturnSelf();
195+
$productCollection->expects($this->any())->method('addFieldToSelect')->willReturnSelf();
196+
$productCollection->expects($this->any())->method('getFirstItem')->willReturn($this->productMock);
197+
198+
$productCollectionFactory = $this->getMockBuilder(CollectionFactory::class)
199+
->setMethods(['create'])
200+
->disableOriginalConstructor()
201+
->getMock();
202+
203+
$productCollectionFactory->expects($this->any())->method('create')->willReturn($productCollection);
204+
188205
$this->model = (new ObjectManager($this))->getObject(
189206
StockItemRepository::class,
190207
[
@@ -200,6 +217,7 @@ protected function setUp()
200217
'indexProcessor' => $this->indexProcessorMock,
201218
'dateTime' => $this->dateTime,
202219
'stockRegistryStorage' => $this->stockRegistryStorage,
220+
'productCollectionFactory' => $productCollectionFactory,
203221
]
204222
);
205223
}
@@ -263,7 +281,6 @@ public function testSave()
263281
$productId = 1;
264282

265283
$this->stockItemMock->expects($this->any())->method('getProductId')->willReturn($productId);
266-
$this->productMock->expects($this->once())->method('load')->with($productId)->willReturnSelf();
267284
$this->productMock->expects($this->once())->method('getId')->willReturn($productId);
268285
$this->productMock->expects($this->once())->method('getTypeId')->willReturn('typeId');
269286
$this->stockConfigurationMock->expects($this->once())->method('isQty')->with('typeId')->willReturn(true);
@@ -309,7 +326,6 @@ public function testSaveWithoutProductId()
309326
$productId = 1;
310327

311328
$this->stockItemMock->expects($this->any())->method('getProductId')->willReturn($productId);
312-
$this->productMock->expects($this->once())->method('load')->with($productId)->willReturnSelf();
313329
$this->productMock->expects($this->once())->method('getId')->willReturn(null);
314330
$this->stockRegistryStorage->expects($this->never())->method('removeStockItem');
315331
$this->stockRegistryStorage->expects($this->never())->method('removeStockStatus');
@@ -325,7 +341,6 @@ public function testSaveException()
325341
$productId = 1;
326342

327343
$this->stockItemMock->expects($this->any())->method('getProductId')->willReturn($productId);
328-
$this->productMock->expects($this->once())->method('load')->with($productId)->willReturnSelf();
329344
$this->productMock->expects($this->once())->method('getId')->willReturn($productId);
330345
$this->productMock->expects($this->once())->method('getTypeId')->willReturn('typeId');
331346
$this->stockConfigurationMock->expects($this->once())->method('isQty')->with('typeId')->willReturn(false);

app/code/Magento/ConfigurableProduct/Controller/Adminhtml/Product/Initialization/Helper/Plugin/UpdateConfigurations.php

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ class UpdateConfigurations
3232
'swatch_image',
3333
'small_image',
3434
'thumbnail',
35-
'image'
35+
'image',
3636
];
3737

3838
/**
@@ -65,13 +65,15 @@ public function afterInitialize(
6565
) {
6666
$configurations = $this->getConfigurations();
6767
$configurations = $this->variationHandler->duplicateImagesForVariations($configurations);
68-
foreach ($configurations as $productId => $productData) {
69-
/** @var \Magento\Catalog\Model\Product $product */
70-
$product = $this->productRepository->getById($productId, false, $this->request->getParam('store', 0));
71-
$productData = $this->variationHandler->processMediaGallery($product, $productData);
72-
$product->addData($productData);
73-
if ($product->hasDataChanges()) {
74-
$product->save();
68+
if (count($configurations)) {
69+
foreach ($configurations as $productId => $productData) {
70+
/** @var \Magento\Catalog\Model\Product $product */
71+
$product = $this->productRepository->getById($productId, false, $this->request->getParam('store', 0));
72+
$productData = $this->variationHandler->processMediaGallery($product, $productData);
73+
$product->addData($productData);
74+
if ($product->hasDataChanges()) {
75+
$product->save();
76+
}
7577
}
7678
}
7779
return $configurableProduct;
@@ -91,6 +93,12 @@ protected function getConfigurations()
9193
}
9294

9395
foreach ($configurableMatrix as $item) {
96+
if (empty($item['was_changed'])) {
97+
continue;
98+
} else {
99+
unset($item['was_changed']);
100+
}
101+
94102
if (!$item['newProduct']) {
95103
$result[$item['id']] = $this->mapData($item);
96104

app/code/Magento/ConfigurableProduct/Model/Product/VariationHandler.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ class VariationHandler
2929
/** @var \Magento\Catalog\Model\ProductFactory */
3030
protected $productFactory;
3131

32+
/** @var \Magento\Eav\Model\Entity\Attribute\AbstractAttribute[] */
33+
private $attributes;
34+
3235
/**
3336
* @var \Magento\CatalogInventory\Api\StockConfigurationInterface
3437
* @deprecated
@@ -70,6 +73,7 @@ public function __construct(
7073
public function generateSimpleProducts($parentProduct, $productsData)
7174
{
7275
$generatedProductIds = [];
76+
$this->attributes = null;
7377
$productsData = $this->duplicateImagesForVariations($productsData);
7478
foreach ($productsData as $simpleProductData) {
7579
$newSimpleProduct = $this->productFactory->create();
@@ -160,7 +164,10 @@ protected function fillSimpleProductData(
160164
$parentProduct->getNewVariationsAttributeSetId()
161165
);
162166

163-
foreach ($product->getTypeInstance()->getSetAttributes($product) as $attribute) {
167+
if ($this->attributes === null) {
168+
$this->attributes = $product->getTypeInstance()->getSetAttributes($product);
169+
}
170+
foreach ($this->attributes as $attribute) {
164171
if ($attribute->getIsUnique() ||
165172
$attribute->getAttributeCode() == 'url_key' ||
166173
$attribute->getFrontend()->getInputType() == 'gallery' ||

0 commit comments

Comments
 (0)