Skip to content

Commit 7d90c6c

Browse files
committed
ACP2E-3410: Configurable product edit form load causes timeout and memory exhaustion
1 parent bbecf3e commit 7d90c6c

File tree

4 files changed

+355
-9
lines changed

4 files changed

+355
-9
lines changed

app/code/Magento/ConfigurableProduct/Block/Adminhtml/Product/Edit/Tab/Variations/Config/Matrix.php

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use Magento\Catalog\Api\ProductRepositoryInterface;
99
use Magento\Catalog\Model\Locator\LocatorInterface;
1010
use Magento\Catalog\Model\Product;
11+
use Magento\Eav\Model\Entity\Attribute\AbstractAttribute;
1112
use Magento\Framework\Exception\NoSuchEntityException;
1213

1314
/**
@@ -242,6 +243,28 @@ protected function getAssociatedProducts()
242243
return $productByUsedAttributes;
243244
}
244245

246+
/**
247+
* Retrieves attributes that are used for configurable product variations
248+
*
249+
* @return array
250+
*/
251+
private function getVariantAttributeComposition(): array
252+
{
253+
$variants = [];
254+
foreach ($this->_getAssociatedProducts() as $product) {
255+
/* @var $attribute AbstractAttribute */
256+
foreach ($this->getUsedAttributes() as $attribute) {
257+
$variants[$product->getId()][$attribute->getAttributeCode()] =
258+
[
259+
'value_id' => $product->getData($attribute->getAttributeCode()),
260+
'attribute' => $attribute
261+
];
262+
}
263+
}
264+
265+
return $variants;
266+
}
267+
245268
/**
246269
* Retrieve actual list of associated products (i.e. if product contains variations matrix form data
247270
* - previously saved in database relations are not considered)
@@ -332,6 +355,121 @@ public function getProductAttributes()
332355
return $this->productAttributes;
333356
}
334357

358+
/**
359+
* Get configurable product existing setup
360+
*
361+
* @return array
362+
*/
363+
public function getExistingVariantConfiguration(): array
364+
{
365+
$productMatrix = $attributes = [];
366+
$variants = $this->getVariantAttributeComposition();
367+
foreach ($this->getAssociatedProducts() as $product) {
368+
$childProductOptions = [];
369+
foreach ($variants[$product->getId()] as $attributeComposition) {
370+
$childProductOptions[] = $this->buildChildProductOption($attributeComposition);
371+
372+
/** @var AbstractAttribute $attribute */
373+
$attribute = $attributeComposition['attribute'];
374+
if (!isset($attributes[$attribute->getAttributeId()])) {
375+
$attributes[$attribute->getAttributeId()] = $this->buildAttributeDetails($attribute);
376+
}
377+
$variationOption = [
378+
'attribute_code' => $attribute->getAttributeCode(),
379+
'attribute_label' => $attribute->getStoreLabel(0),
380+
'id' => $attributeComposition['value_id'],
381+
'label' => $this->extractAttributeValueLabel(
382+
$attribute,
383+
$attributeComposition['value_id']
384+
),
385+
'value' => $attributeComposition['value_id'],
386+
'__disableTmpl' => true,
387+
];
388+
$attributes[$attribute->getAttributeId()]['chosen'][] = $variationOption;
389+
}
390+
$productMatrix[] = $this->buildChildProductDetails($product, $childProductOptions);
391+
}
392+
return [
393+
'product_matrix' => $productMatrix,
394+
'attributes' => array_values($attributes)
395+
];
396+
}
397+
398+
/**
399+
* Prepare attribute details for child product configuration
400+
*
401+
* @param AbstractAttribute $attribute
402+
* @return array
403+
*/
404+
private function buildAttributeDetails(AbstractAttribute $attribute): array
405+
{
406+
$configurableAttributes = $this->getAttributes();
407+
$details = [
408+
'code' => $attribute->getAttributeCode(),
409+
'label' => $attribute->getStoreLabel(),
410+
'id' => $attribute->getAttributeId(),
411+
'position' => $configurableAttributes[$attribute->getAttributeId()]['position'],
412+
'chosen' => [],
413+
'__disableTmpl' => true
414+
];
415+
416+
foreach ($attribute->getOptions() as $option) {
417+
if (!empty($option['value'])) {
418+
$details['options'][] = [
419+
'attribute_code' => $attribute->getAttributeCode(),
420+
'attribute_label' => $attribute->getStoreLabel(0),
421+
'id' => $option['value'],
422+
'label' => $option['label'],
423+
'value' => $option['value'],
424+
'__disableTmpl' => true,
425+
];
426+
}
427+
}
428+
429+
return $details;
430+
}
431+
432+
/**
433+
* Generate configurable product child option
434+
*
435+
* @param array $attributeDetails
436+
* @return array
437+
*/
438+
private function buildChildProductOption(array $attributeDetails): array
439+
{
440+
$label = $this->extractAttributeValueLabel(
441+
$attributeDetails['attribute'],
442+
$attributeDetails['value_id']
443+
);
444+
445+
return [
446+
'attribute_code' => $attributeDetails['attribute']->getAttributeCode(),
447+
'attribute_label' => $attributeDetails['attribute']->getStoreLabel(0),
448+
'id' => $attributeDetails['value_id'],
449+
'label' => $label,
450+
'value' => $attributeDetails['value_id'],
451+
'__disableTmpl' => true,
452+
];
453+
}
454+
455+
/**
456+
* Get label for a specific value of an attribute.
457+
*
458+
* @param $attribute
459+
* @param int $valueId
460+
* @return string
461+
*/
462+
private function extractAttributeValueLabel($attribute, int $valueId): string
463+
{
464+
foreach ($attribute->getOptions() as $attributeOption) {
465+
if ($attributeOption->getValue() == $valueId) {
466+
return $attributeOption->getLabel();
467+
}
468+
}
469+
470+
return '';
471+
}
472+
335473
/**
336474
* Prepare product variations.
337475
*
@@ -443,4 +581,29 @@ private function prepareAttributes(
443581

444582
return [$attributes, $variationOptions];
445583
}
584+
585+
/**
586+
* Create child product details
587+
*
588+
* @param Product $product
589+
* @param array $childProductOptions
590+
* @return array
591+
*/
592+
private function buildChildProductDetails(Product $product, array $childProductOptions): array
593+
{
594+
return [
595+
'productId' => $product->getId(),
596+
'images' => [
597+
'preview' => $this->image->init($product, 'product_thumbnail_image')->getUrl()
598+
],
599+
'sku' => $product->getSku(),
600+
'name' => $product->getName(),
601+
'quantity' => $this->getProductStockQty($product),
602+
'price' => $product->getPrice(),
603+
'options' => $childProductOptions,
604+
'weight' => $product->getWeight(),
605+
'status' => $product->getStatus(),
606+
'__disableTmpl' => true,
607+
];
608+
}
446609
}

app/code/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/Composite.php

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,12 +95,18 @@ public function modifyData(array $data)
9595
$productId = $model->getId();
9696
$data[$productId]['affect_configurable_product_attributes'] = '1';
9797

98+
$variantConfiguration = $this->associatedProducts->getExistingVariantConfiguration();
9899
if ($productTypeId === ConfigurableType::TYPE_CODE) {
99-
$data[$productId]['configurable-matrix'] = $this->associatedProducts->getProductMatrix();
100-
$data[$productId]['attributes'] = $this->associatedProducts->getProductAttributesIds();
101-
$data[$productId]['attribute_codes'] = $this->associatedProducts->getProductAttributesCodes();
100+
//$data[$productId]['configurable-matrix'] = $this->associatedProducts->getProductMatrix();
101+
$data[$productId]['configurable-matrix'] = $variantConfiguration['product_matrix'];
102+
//$data[$productId]['attributes'] = $this->associatedProducts->getProductAttributesIds();
103+
$data[$productId]['attributes'] = $variantConfiguration['attributes'];
104+
//$data[$productId]['attribute_codes'] = $this->associatedProducts->getProductAttributesCodes();
105+
foreach ($variantConfiguration['attributes'] as $attribute) {
106+
$data[$productId]['attribute_codes'][] = $attribute['code'];
107+
}
102108
$data[$productId]['product']['configurable_attributes_data'] =
103-
$this->associatedProducts->getConfigurableAttributesData();
109+
$this->associatedProducts->getConfigurableAttributesData($variantConfiguration['attributes']);
104110
}
105111
}
106112

0 commit comments

Comments
 (0)