Skip to content

Commit 8bde202

Browse files
author
Oleksii Korshenko
authored
Merge pull request #542 from magento-okapis/MAGETWO-58348-Cannot-create-configurable-product-with-child-by-REST-API-2.2
Fixed issue: - MAGETWO-58348 Cannot create configurable product with child by REST API
2 parents d0688f2 + 6ae1fce commit 8bde202

File tree

5 files changed

+357
-24
lines changed

5 files changed

+357
-24
lines changed

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

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
* @method Product setHasError(bool $value)
2323
* @method \Magento\Catalog\Model\ResourceModel\Product getResource()
2424
* @method null|bool getHasError()
25-
* @method Product setAssociatedProductIds(array $productIds)
2625
* @method array getAssociatedProductIds()
2726
* @method Product setNewVariationsAttributeSetId(int $value)
2827
* @method int getNewVariationsAttributeSetId()
@@ -2614,4 +2613,16 @@ private function getMediaGalleryProcessor()
26142613
}
26152614
return $this->mediaGalleryProcessor;
26162615
}
2616+
2617+
/**
2618+
* Set the associated products
2619+
*
2620+
* @param array $productIds
2621+
* @return $this
2622+
*/
2623+
public function setAssociatedProductIds(array $productIds)
2624+
{
2625+
$this->getExtensionAttributes()->setConfigurableProductLinks($productIds);
2626+
return $this;
2627+
}
26172628
}

app/code/Magento/ConfigurableProduct/Model/LinkManagement.php

Lines changed: 99 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,16 @@ class LinkManagement implements \Magento\ConfigurableProduct\Api\LinkManagementI
3232
*/
3333
private $dataObjectHelper;
3434

35+
/**
36+
* @var \Magento\ConfigurableProduct\Helper\Product\Options\Factory;
37+
*/
38+
private $optionsFactory;
39+
40+
/**
41+
* @var \Magento\Catalog\Model\ResourceModel\Eav\AttributeFactory
42+
*/
43+
private $attributeFactory;
44+
3545
/**
3646
* @param \Magento\Catalog\Api\ProductRepositoryInterface $productRepository
3747
* @param \Magento\Catalog\Api\Data\ProductInterfaceFactory $productFactory
@@ -102,9 +112,28 @@ public function addChild($sku, $childSku)
102112
throw new StateException(__('Product has been already attached'));
103113
}
104114

115+
$configurableProductOptions = $product->getExtensionAttributes()->getConfigurableProductOptions();
116+
if (empty($configurableProductOptions)) {
117+
throw new StateException(__('Parent product does not have configurable product options'));
118+
}
119+
120+
$attributeIds = [];
121+
foreach ($configurableProductOptions as $configurableProductOption) {
122+
$attributeCode = $configurableProductOption->getProductAttribute()->getAttributeCode();
123+
if (!$child->getData($attributeCode)) {
124+
throw new StateException(__('Child product does not have attribute value %1', $attributeCode));
125+
}
126+
$attributeIds[] = $configurableProductOption->getAttributeId();
127+
}
128+
$configurableOptionData = $this->getConfigurableAttributesData($attributeIds);
129+
130+
/** @var \Magento\ConfigurableProduct\Helper\Product\Options\Factory $optionFactory */
131+
$optionFactory = $this->getOptionsFactory();
132+
$options = $optionFactory->create($configurableOptionData);
105133
$childrenIds[] = $child->getId();
134+
$product->getExtensionAttributes()->setConfigurableProductOptions($options);
106135
$product->getExtensionAttributes()->setConfigurableProductLinks($childrenIds);
107-
$product->save();
136+
$this->productRepository->save($product);
108137
return true;
109138
}
110139

@@ -133,7 +162,75 @@ public function removeChild($sku, $childSku)
133162
throw new NoSuchEntityException(__('Requested option doesn\'t exist'));
134163
}
135164
$product->getExtensionAttributes()->setConfigurableProductLinks($ids);
136-
$product->save();
165+
$this->productRepository->save($product);
137166
return true;
138167
}
168+
169+
/**
170+
* Get Options Factory
171+
*
172+
* @return \Magento\ConfigurableProduct\Helper\Product\Options\Factory
173+
*
174+
* @deprecated
175+
*/
176+
private function getOptionsFactory()
177+
{
178+
if (!$this->optionsFactory) {
179+
$this->optionsFactory = \Magento\Framework\App\ObjectManager::getInstance()
180+
->get(\Magento\ConfigurableProduct\Helper\Product\Options\Factory::class);
181+
}
182+
return $this->optionsFactory;
183+
}
184+
185+
/**
186+
* Get Attribute Factory
187+
*
188+
* @return \Magento\Catalog\Model\ResourceModel\Eav\AttributeFactory
189+
*
190+
* @deprecated
191+
*/
192+
private function getAttributeFactory()
193+
{
194+
if (!$this->attributeFactory) {
195+
$this->attributeFactory = \Magento\Framework\App\ObjectManager::getInstance()
196+
->get(\Magento\Catalog\Model\ResourceModel\Eav\AttributeFactory::class);
197+
}
198+
return $this->attributeFactory;
199+
}
200+
201+
/**
202+
* Get Configurable Attribute Data
203+
*
204+
* @param int[] $attributeIds
205+
* @return array
206+
*/
207+
private function getConfigurableAttributesData($attributeIds)
208+
{
209+
$configurableAttributesData = [];
210+
$attributeValues = [];
211+
$attributes = $this->getAttributeFactory()->create()
212+
->getCollection()
213+
->addFieldToFilter('attribute_id', $attributeIds)
214+
->getItems();
215+
foreach ($attributes as $attribute) {
216+
foreach ($attribute->getOptions() as $option) {
217+
if ($option->getValue()) {
218+
$attributeValues[] = [
219+
'label' => $option->getLabel(),
220+
'attribute_id' => $attribute->getId(),
221+
'value_index' => $option->getValue(),
222+
];
223+
}
224+
}
225+
$configurableAttributesData[] =
226+
[
227+
'attribute_id' => $attribute->getId(),
228+
'code' => $attribute->getAttributeCode(),
229+
'label' => $attribute->getStoreLabel(),
230+
'values' => $attributeValues,
231+
];
232+
}
233+
234+
return $configurableAttributesData;
235+
}
139236
}

app/code/Magento/ConfigurableProduct/Test/Unit/Model/LinkManagementTest.php

Lines changed: 73 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66

77
namespace Magento\ConfigurableProduct\Test\Unit\Model;
88

9+
use Magento\ConfigurableProduct\Model\LinkManagement;
910
use Magento\ConfigurableProduct\Model\Product\Type\Configurable;
10-
use Magento\ConfigurableProduct\Test\Unit\Model\Product\ProductExtensionAttributes;
1111

1212
/**
1313
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
@@ -146,15 +146,59 @@ public function testAddChild()
146146

147147
$configurable = $this->getMockBuilder(\Magento\Catalog\Model\Product::class)
148148
->disableOriginalConstructor()
149+
->setMethods(['getId', 'getExtensionAttributes'])
150+
->getMock();
151+
$simple = $this->getMockBuilder(\Magento\Catalog\Model\Product::class)
152+
->disableOriginalConstructor()
153+
->setMethods(['getId', 'getData'])
149154
->getMock();
150155

151-
$configurable->expects($this->any())->method('getId')->will($this->returnValue(666));
156+
$extensionAttributesMock = $this->getMockBuilder(\Magento\Catalog\Api\Data\ProductExtension::class)
157+
->disableOriginalConstructor()
158+
->setMethods([
159+
'getConfigurableProductOptions', 'setConfigurableProductOptions', 'setConfigurableProductLinks'
160+
])
161+
->getMock();
162+
$optionMock = $this->getMockBuilder(\Magento\ConfigurableProduct\Api\Data\Option::class)
163+
->disableOriginalConstructor()
164+
->setMethods(['getProductAttribute', 'getAttributeId'])
165+
->getMock();
166+
$productAttributeMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute\AbstractAttribute::class)
167+
->disableOriginalConstructor()
168+
->setMethods(['getAttributeCode'])
169+
->getMock();
170+
$optionsFactoryMock = $this->getMockBuilder(\Magento\ConfigurableProduct\Helper\Product\Options\Factory::class)
171+
->disableOriginalConstructor()
172+
->setMethods(['create'])
173+
->getMock();
174+
$reflectionClass = new \ReflectionClass(\Magento\ConfigurableProduct\Model\LinkManagement::class);
175+
$optionsFactoryReflectionProperty = $reflectionClass->getProperty('optionsFactory');
176+
$optionsFactoryReflectionProperty->setAccessible(true);
177+
$optionsFactoryReflectionProperty->setValue($this->object, $optionsFactoryMock);
152178

153-
$simple = $this->getMockBuilder(\Magento\Catalog\Model\Product::class)
179+
$attributeFactoryMock = $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Eav\AttributeFactory::class)
154180
->disableOriginalConstructor()
181+
->setMethods(['create'])
155182
->getMock();
183+
$attributeFactoryReflectionProperty = $reflectionClass->getProperty('attributeFactory');
184+
$attributeFactoryReflectionProperty->setAccessible(true);
185+
$attributeFactoryReflectionProperty->setValue($this->object, $attributeFactoryMock);
156186

157-
$simple->expects($this->any())->method('getId')->will($this->returnValue(999));
187+
$attributeMock = $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Eav\Attribute::class)
188+
->disableOriginalConstructor()
189+
->setMethods(['getCollection', 'getOptions', 'getId', 'getAttributeCode', 'getStoreLabel'])
190+
->getMock();
191+
$attributeOptionMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute\Option::class)
192+
->disableOriginalConstructor()
193+
->setMethods(['getValue', 'getLabel'])
194+
->getMock();
195+
196+
$attributeCollectionMock = $this->getMockBuilder(
197+
\Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Attribute\Collection::class
198+
)
199+
->disableOriginalConstructor()
200+
->setMethods(['addFieldToFilter', 'getItems'])
201+
->getMock();
158202

159203
$this->productRepository->expects($this->at(0))->method('get')->with($productSku)->willReturn($configurable);
160204
$this->productRepository->expects($this->at(1))->method('get')->with($childSku)->willReturn($simple);
@@ -164,15 +208,30 @@ public function testAddChild()
164208
$this->returnValue([0 => [1, 2, 3]])
165209
);
166210

167-
$extensionAttributes = $this->getMockBuilder(ProductExtensionAttributes::class)
168-
->setMethods(['setConfigurableProductLinks'])
169-
->disableOriginalConstructor()
170-
->getMockForAbstractClass();
211+
$configurable->expects($this->any())->method('getId')->will($this->returnValue(666));
212+
$simple->expects($this->any())->method('getId')->will($this->returnValue(999));
213+
214+
$configurable->expects($this->any())->method('getExtensionAttributes')->willReturn($extensionAttributesMock);
215+
$extensionAttributesMock->expects($this->any())
216+
->method('getConfigurableProductOptions')
217+
->willReturn([$optionMock]);
218+
$optionMock->expects($this->any())->method('getProductAttribute')->willReturn($productAttributeMock);
219+
$productAttributeMock->expects($this->any())->method('getAttributeCode')->willReturn('color');
220+
$simple->expects($this->any())->method('getData')->willReturn('color');
221+
$optionMock->expects($this->any())->method('getAttributeId')->willReturn('1');
171222

172-
$configurable->expects($this->once())->method('getExtensionAttributes')->willReturn($extensionAttributes);
173-
$extensionAttributes->expects($this->once())->method('setConfigurableProductLinks')->willReturnSelf();
223+
$optionsFactoryMock->expects($this->any())->method('create')->willReturn([$optionMock]);
224+
$attributeFactoryMock->expects($this->any())->method('create')->willReturn($attributeMock);
225+
$attributeMock->expects($this->any())->method('getCollection')->willReturn($attributeCollectionMock);
226+
$attributeCollectionMock->expects($this->any())->method('addFieldToFilter')->willReturnSelf();
227+
$attributeCollectionMock->expects($this->any())->method('getItems')->willReturn([$attributeMock]);
174228

175-
$configurable->expects($this->once())->method('save');
229+
$attributeMock->expects($this->any())->method('getOptions')->willReturn([$attributeOptionMock]);
230+
231+
$extensionAttributesMock->expects($this->any())->method('setConfigurableProductOptions');
232+
$extensionAttributesMock->expects($this->any())->method('setConfigurableProductLinks');
233+
234+
$this->productRepository->expects($this->once())->method('save');
176235

177236
$this->assertTrue(true, $this->object->addChild($productSku, $childSku));
178237
}
@@ -243,15 +302,13 @@ public function testRemoveChild()
243302
$productType->expects($this->once())->method('getUsedProducts')
244303
->will($this->returnValue([$option]));
245304

246-
$extensionAttributes = $this->getMockBuilder(ProductExtensionAttributes::class)
305+
$extensionAttributesMock = $this->getMockBuilder(\Magento\Framework\Api\ExtensionAttributesInterface::class)
247306
->setMethods(['setConfigurableProductLinks'])
248307
->disableOriginalConstructor()
249308
->getMockForAbstractClass();
250309

251-
$product->expects($this->once())->method('getExtensionAttributes')->willReturn($extensionAttributes);
252-
$extensionAttributes->expects($this->once())->method('setConfigurableProductLinks')->willReturnSelf();
253-
254-
$product->expects($this->once())->method('save');
310+
$product->expects($this->once())->method('getExtensionAttributes')->willReturn($extensionAttributesMock);
311+
$this->productRepository->expects($this->once())->method('save');
255312
$this->assertTrue($this->object->removeChild($productSku, $childSku));
256313
}
257314

0 commit comments

Comments
 (0)