OptionRepository.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. <?php
  2. /**
  3. *
  4. * Copyright © Magento, Inc. All rights reserved.
  5. * See COPYING.txt for license details.
  6. */
  7. namespace Magento\ConfigurableProduct\Model;
  8. use Magento\Catalog\Api\Data\ProductInterface;
  9. use Magento\Catalog\Model\Product;
  10. use Magento\Catalog\Model\Product\Type as ProductType;
  11. use Magento\ConfigurableProduct\Api\Data\OptionInterface;
  12. use Magento\ConfigurableProduct\Helper\Product\Options\Loader;
  13. use Magento\ConfigurableProduct\Model\Product\Type\Configurable as ConfigurableType;
  14. use Magento\Framework\App\ObjectManager;
  15. use Magento\Framework\Exception\CouldNotSaveException;
  16. use Magento\Framework\Exception\InputException;
  17. use Magento\Framework\Exception\NoSuchEntityException;
  18. use Magento\Framework\Exception\StateException;
  19. use Magento\Framework\EntityManager\MetadataPool;
  20. use Magento\Store\Model\Store;
  21. /**
  22. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  23. */
  24. class OptionRepository implements \Magento\ConfigurableProduct\Api\OptionRepositoryInterface
  25. {
  26. /**
  27. * @var \Magento\Catalog\Api\ProductRepositoryInterface
  28. */
  29. protected $productRepository;
  30. /**
  31. * @var \Magento\ConfigurableProduct\Api\Data\OptionValueInterfaceFactory
  32. */
  33. protected $optionValueFactory;
  34. /**
  35. * @var Product\Type\Configurable
  36. */
  37. protected $configurableType;
  38. /**
  39. * @var \Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Attribute
  40. */
  41. protected $optionResource;
  42. /**
  43. * @var \Magento\Store\Model\StoreManagerInterface
  44. */
  45. protected $storeManager;
  46. /**
  47. * @var \Magento\Catalog\Api\ProductAttributeRepositoryInterface
  48. */
  49. protected $productAttributeRepository;
  50. /**
  51. * @var ConfigurableType\AttributeFactory
  52. */
  53. protected $configurableAttributeFactory;
  54. /**
  55. * @var \Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable
  56. */
  57. private $configurableTypeResource;
  58. /**
  59. * @var MetadataPool
  60. */
  61. private $metadataPool;
  62. /**
  63. * @var Loader
  64. */
  65. private $optionLoader;
  66. /**
  67. * @param \Magento\Catalog\Api\ProductRepositoryInterface $productRepository
  68. * @param \Magento\ConfigurableProduct\Api\Data\OptionValueInterfaceFactory $optionValueFactory
  69. * @param ConfigurableType $configurableType
  70. * @param \Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Attribute $optionResource
  71. * @param \Magento\Store\Model\StoreManagerInterface $storeManager
  72. * @param \Magento\Catalog\Api\ProductAttributeRepositoryInterface $productAttributeRepository
  73. * @param ConfigurableType\AttributeFactory $configurableAttributeFactory
  74. * @param \Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable $configurableTypeResource
  75. * @param Loader $optionLoader
  76. *
  77. * @SuppressWarnings(PHPMD.ExcessiveParameterList)
  78. */
  79. public function __construct(
  80. \Magento\Catalog\Api\ProductRepositoryInterface $productRepository,
  81. \Magento\ConfigurableProduct\Api\Data\OptionValueInterfaceFactory $optionValueFactory,
  82. \Magento\ConfigurableProduct\Model\Product\Type\Configurable $configurableType,
  83. \Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Attribute $optionResource,
  84. \Magento\Store\Model\StoreManagerInterface $storeManager,
  85. \Magento\Catalog\Api\ProductAttributeRepositoryInterface $productAttributeRepository,
  86. \Magento\ConfigurableProduct\Model\Product\Type\Configurable\AttributeFactory $configurableAttributeFactory,
  87. \Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable $configurableTypeResource,
  88. Loader $optionLoader
  89. ) {
  90. $this->productRepository = $productRepository;
  91. $this->optionValueFactory = $optionValueFactory;
  92. $this->configurableType = $configurableType;
  93. $this->optionResource = $optionResource;
  94. $this->storeManager = $storeManager;
  95. $this->productAttributeRepository = $productAttributeRepository;
  96. $this->configurableAttributeFactory = $configurableAttributeFactory;
  97. $this->configurableTypeResource = $configurableTypeResource;
  98. $this->optionLoader = $optionLoader;
  99. }
  100. /**
  101. * {@inheritdoc}
  102. */
  103. public function get($sku, $id)
  104. {
  105. $product = $this->getProduct($sku);
  106. $options = $this->optionLoader->load($product);
  107. foreach ($options as $option) {
  108. if ($option->getId() == $id) {
  109. return $option;
  110. }
  111. }
  112. throw new NoSuchEntityException(
  113. __('The "%1" entity that was requested doesn\'t exist. Verify the entity and try again.', $id)
  114. );
  115. }
  116. /**
  117. * {@inheritdoc}
  118. */
  119. public function getList($sku)
  120. {
  121. $product = $this->getProduct($sku);
  122. return (array) $this->optionLoader->load($product);
  123. }
  124. /**
  125. * {@inheritdoc}
  126. */
  127. public function delete(OptionInterface $option)
  128. {
  129. $entityId = $this->configurableTypeResource->getEntityIdByAttribute($option);
  130. $product = $this->getProductById($entityId);
  131. try {
  132. $this->configurableTypeResource->saveProducts($product, []);
  133. $this->configurableType->resetConfigurableAttributes($product);
  134. } catch (\Exception $exception) {
  135. throw new StateException(
  136. __('The variations from the "%1" product can\'t be deleted.', $entityId)
  137. );
  138. }
  139. try {
  140. $this->optionResource->delete($option);
  141. } catch (\Exception $exception) {
  142. throw new StateException(
  143. __('The option with "%1" ID can\'t be deleted.', $option->getId())
  144. );
  145. }
  146. return true;
  147. }
  148. /**
  149. * {@inheritdoc}
  150. */
  151. public function deleteById($sku, $id)
  152. {
  153. $product = $this->getProduct($sku);
  154. $attributeCollection = $this->configurableType->getConfigurableAttributeCollection($product);
  155. /** @var \Magento\ConfigurableProduct\Model\Product\Type\Configurable\Attribute $option */
  156. $option = $attributeCollection->getItemById($id);
  157. if ($option === null) {
  158. throw new NoSuchEntityException(
  159. __("The option that was requested doesn't exist. Verify the entity and try again.")
  160. );
  161. }
  162. return $this->delete($option);
  163. }
  164. /**
  165. * {@inheritdoc}
  166. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  167. */
  168. public function save($sku, OptionInterface $option)
  169. {
  170. $metadata = $this->getMetadataPool()->getMetadata(ProductInterface::class);
  171. if ($option->getId()) {
  172. /** @var Product $product */
  173. $product = $this->getProduct($sku);
  174. $data = $option->getData();
  175. $option->load($option->getId());
  176. $option->setData(array_replace_recursive($option->getData(), $data));
  177. if (!$option->getId() || $option->getProductId() != $product->getData($metadata->getLinkField())) {
  178. throw new NoSuchEntityException(
  179. __(
  180. 'Option with id "%1" not found',
  181. $option->getId()
  182. )
  183. );
  184. }
  185. } else {
  186. /** @var Product $product */
  187. $product = $this->productRepository->get($sku);
  188. $this->validateNewOptionData($option);
  189. $allowedTypes = [ProductType::TYPE_SIMPLE, ProductType::TYPE_VIRTUAL, ConfigurableType::TYPE_CODE];
  190. if (!in_array($product->getTypeId(), $allowedTypes)) {
  191. throw new \InvalidArgumentException('Incompatible product type');
  192. }
  193. $option->setProductId($product->getData($metadata->getLinkField()));
  194. }
  195. try {
  196. $option->save();
  197. } catch (\Exception $e) {
  198. throw new CouldNotSaveException(__('An error occurred while saving the option. Please try to save again.'));
  199. }
  200. if (!$option->getId()) {
  201. throw new CouldNotSaveException(__('An error occurred while saving the option. Please try to save again.'));
  202. }
  203. return $option->getId();
  204. }
  205. /**
  206. * Retrieve product instance by sku
  207. *
  208. * @param string $sku
  209. * @return ProductInterface
  210. * @throws InputException
  211. */
  212. private function getProduct($sku)
  213. {
  214. $product = $this->productRepository->get($sku);
  215. if (\Magento\ConfigurableProduct\Model\Product\Type\Configurable::TYPE_CODE !== $product->getTypeId()) {
  216. throw new InputException(
  217. __('This is implemented for the "%1" configurable product only.', $sku)
  218. );
  219. }
  220. return $product;
  221. }
  222. /**
  223. * Retrieve product instance by id
  224. *
  225. * @param int $id
  226. * @return ProductInterface
  227. * @throws InputException
  228. */
  229. private function getProductById($id)
  230. {
  231. $product = $this->productRepository->getById($id);
  232. if (\Magento\ConfigurableProduct\Model\Product\Type\Configurable::TYPE_CODE !== $product->getTypeId()) {
  233. throw new InputException(
  234. __('This is implemented for the "%1" configurable product only.', $id)
  235. );
  236. }
  237. return $product;
  238. }
  239. /**
  240. * Ensure that all necessary data is available for a new option creation.
  241. *
  242. * @param OptionInterface $option
  243. * @return void
  244. * @throws InputException
  245. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  246. */
  247. public function validateNewOptionData(OptionInterface $option)
  248. {
  249. $inputException = new InputException();
  250. if (!$option->getAttributeId()) {
  251. $inputException->addError(__('Option attribute ID is not specified.'));
  252. }
  253. if (!$option->getLabel()) {
  254. $inputException->addError(__('Option label is not specified.'));
  255. }
  256. if (!$option->getValues()) {
  257. $inputException->addError(__('Option values are not specified.'));
  258. } else {
  259. foreach ($option->getValues() as $optionValue) {
  260. if (null === $optionValue->getValueIndex()) {
  261. $inputException->addError(__('Value index is not specified for an option.'));
  262. }
  263. }
  264. }
  265. if ($inputException->wasErrorAdded()) {
  266. throw $inputException;
  267. }
  268. }
  269. /**
  270. * Get MetadataPool instance
  271. * @return MetadataPool
  272. */
  273. private function getMetadataPool()
  274. {
  275. if (!$this->metadataPool) {
  276. $this->metadataPool = ObjectManager::getInstance()->get(MetadataPool::class);
  277. }
  278. return $this->metadataPool;
  279. }
  280. }