How is the \Magento\Catalog\Api\ProductRepositoryInterface::save() implemented and used?

Context:

Interface

Implementation: \Magento\Catalog\Model\ProductRepository

6a377bb/app/code/Magento/Catalog/Model/ProductRepository.php#L483-L555

/**
 * {@inheritdoc}
 * @SuppressWarnings(PHPMD.CyclomaticComplexity)
 * @SuppressWarnings(PHPMD.NPathComplexity)
 */
public function save(\Magento\Catalog\Api\Data\ProductInterface $product, $saveOptions = false)
{
	$tierPrices = $product->getData('tier_price');

	try {
		$existingProduct = $this->get($product->getSku());

		$product->setData(
			$this->resourceModel->getLinkField(),
			$existingProduct->getData($this->resourceModel->getLinkField())
		);
	} catch (NoSuchEntityException $e) {
		$existingProduct = null;
	}

	$productDataArray = $this->extensibleDataObjectConverter
		->toNestedArray($product, [], 'Magento\Catalog\Api\Data\ProductInterface');
	$productDataArray = array_replace($productDataArray, $product->getData());
	unset($productDataArray['media_gallery']);

	$ignoreLinksFlag = $product->getData('ignore_links_flag');
	$productLinks = null;
	if (!$ignoreLinksFlag && $ignoreLinksFlag !== null) {
		$productLinks = $product->getProductLinks();
	}
	$productDataArray['store_id'] = (int)$this->storeManager->getStore()->getId();
	$product = $this->initializeProductData($productDataArray, empty($existingProduct));

	$this->processLinks($product, $productLinks);
	if (isset($productDataArray['media_gallery_entries'])) {
		$this->processMediaGallery($product, $productDataArray['media_gallery_entries']);
	}

	if (!$product->getOptionsReadonly()) {
		$product->setCanSaveCustomOptions(true);
	}

	$validationResult = $this->resourceModel->validate($product);
	if (true !== $validationResult) {
		throw new \Magento\Framework\Exception\CouldNotSaveException(
			__('Invalid product data: %1', implode(',', $validationResult))
		);
	}

	try {
		if ($tierPrices !== null) {
			$product->setData('tier_price', $tierPrices);
		}
		unset($this->instances[$product->getSku()]);
		unset($this->instancesById[$product->getId()]);
		$this->resourceModel->save($product);
	} catch (\Magento\Eav\Model\Entity\Attribute\Exception $exception) {
		throw \Magento\Framework\Exception\InputException::invalidFieldValue(
			$exception->getAttributeCode(),
			$product->getData($exception->getAttributeCode()),
			$exception
		);
	} catch (ValidatorException $e) {
		throw new CouldNotSaveException(__($e->getMessage()));
	} catch (LocalizedException $e) {
		throw $e;
	} catch (\Exception $e) {
		throw new \Magento\Framework\Exception\CouldNotSaveException(__('Unable to save product'));
	}
	unset($this->instances[$product->getSku()]);
	unset($this->instancesById[$product->getId()]);
	return $this->get($product->getSku());
}

Usage

1. From the /V1/products Web API:

Details: How to create a product programmatically with the /V1/products Web API?

2. \Magento\Catalog\Model\Product\PriceModifier::removeTierPrice()

3. \Magento\Catalog\Model\Product\TierPriceManagement::add()

4. \Magento\Catalog\Model\Product\Gallery\GalleryManagement::create()

5. \Magento\Catalog\Model\Product\Gallery\GalleryManagement::update()

6. \Magento\Catalog\Model\Product\Gallery\GalleryManagement::remove()

7. \Magento\Catalog\Model\Product\Option\Repository::deleteByIdentifier()

8. \Magento/Catalog/Model/ProductLink/Management::setProductLinks()

9. \Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\SourceTest::testReindexEntitiesForConfigurableProduct()

10.

11.

12. \Magento\ConfigurableProduct\Model\Product\Type\ConfigurableTest::testSaveProductRelationsOneChild()

13. \Magento\ConfigurableProduct\Model\Product\Type\ConfigurableTest::testSaveProductRelationsNoChildren()

14. \Magento\ConfigurableProduct\Model\Product\Type\Configurable\PriceTest::testGetFinalPriceWithCustomOption()

15.

6a377bb/dev/tests/integration/testsuite/Magento/Sales/_files/quote_with_bundle.php#L36-L150

$productRepository = $objectManager->get(Magento\Catalog\Api\ProductRepositoryInterface::class);
/**
 * @var \Magento\Catalog\Model\Product $product
 */
$product = $objectManager->create('Magento\Catalog\Model\Product');
$product
    ->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_BUNDLE)
    ->setAttributeSetId(4)
    ->setWebsiteIds([1])
    ->setName('Bundle Product')
    ->setSku('bundle-product')
    ->setDescription('Description with <b>html tag</b>')
    ->setShortDescription('Bundle')
    ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH)
    ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED)
    ->setStockData(
        [
            'use_config_manage_stock' => 0,
            'manage_stock' => 0,
            'use_config_enable_qty_increments' => 1,
            'use_config_qty_increments' => 1,
            'is_in_stock' => 0,
        ]
    )
    ->setBundleOptionsData(
        [
            [
                'title' => 'Bundle Product Items',
                'default_title' => 'Bundle Product Items',
                'type' => 'checkbox',
                'required' => 1,
                'delete' => '',
                'position' => 0,
                'option_id' => '',
            ],
        ]
    )
    ->setBundleSelectionsData(
        [
            [
                [
                    'product_id' => $simpleProducts[0]->getId(),
                    'selection_qty' => 1,
                    'selection_can_change_qty' => 1,
                    'delete' => '',
                    'position' => 0,
                    'selection_price_type' => 0,
                    'selection_price_value' => 0.0,
                    'option_id' => '',
                    'selection_id' => '',
                    'is_default' => 1,
                ],
                [
                    'product_id' => $simpleProducts[1]->getId(),
                    'selection_qty' => 1,
                    'selection_can_change_qty' => 1,
                    'delete' => '',
                    'position' => 0,
                    'selection_price_type' => 0,
                    'selection_price_value' => 0.0,
                    'option_id' => '',
                    'selection_id' => '',
                    'is_default' => 1,
                ]
            ],
        ]
    )->setCustomAttributes([
        "price_type" => [
            'attribute_code' => 'price_type',
            'value' => \Magento\Bundle\Model\Product\Price::PRICE_TYPE_DYNAMIC
        ],
        "price_view" => [
            "attribute_code" => "price_view",
            "value" => "1",
        ],
    ])
    ->setCanSaveBundleSelections(true)
    ->setHasOptions(false)
    ->setAffectBundleProductSelections(true);
if ($product->getBundleOptionsData()) {
    $options = [];
    foreach ($product->getBundleOptionsData() as $key => $optionData) {
        if (!(bool)$optionData['delete']) {
            $option = $objectManager->create('Magento\Bundle\Api\Data\OptionInterfaceFactory')
                ->create(['data' => $optionData]);
            $option->setSku($product->getSku());
            $option->setOptionId(null);
            $links = [];
            $bundleLinks = $product->getBundleSelectionsData();
            if (!empty($bundleLinks[$key])) {
                foreach ($bundleLinks[$key] as $linkData) {
                    if (!(bool)$linkData['delete']) {
                        /** @var \Magento\Bundle\Api\Data\LinkInterface$link */
                        $link = $objectManager->create('Magento\Bundle\Api\Data\LinkInterfaceFactory')
                            ->create(['data' => $linkData]);
                        $linkProduct = $productRepository->getById($linkData['product_id']);
                        $link->setSku($linkProduct->getSku());
                        $link->setQty($linkData['selection_qty']);
                        if (isset($linkData['selection_can_change_qty'])) {
                            $link->setCanChangeQuantity($linkData['selection_can_change_qty']);
                        }
                        $links[] = $link;
                    }
                }
                $option->setProductLinks($links);
                $options[] = $option;
            }
        }
    }
    $extension = $product->getExtensionAttributes();
    $extension->setBundleProductOptions($options);
    $product->setExtensionAttributes($extension);
}
$productRepository->save($product);

See also: