Magento – Magento 2 is there a faster way to change product media gallery entries programmatically

gallerymagento2mediaproductproduct-images

I need to do a massive update of product data but what I need to do can't be achieved with product import. In example I need to update media gallery and categories for given products, but the solution I came out with takes too long.

A little recap:
I added a command to the Magento 2 CLI that, given a json configuration file, removes, adds, updates or sorts media gallery entries for a given product like this. Here I paste an excerpt of the code:

/* $product is of type Magento\Catalog\Model\Product */

//get existing media gallery
$existingMediaGallery = $product->getMediaGallery();

/* 
   do stuff with media gallery (alter $existingMediaGallery)
   (add, remove, sort, ...)
*/

//set media gallery again
$product->setMediaGallery($existingMediaGallery);

//process media gallery
$mediaGalleryEntries = $product->getMediaGalleryEntries();
$this->getMediaGalleryProcessor()->clearMediaAttribute($product, array_keys($product->getMediaAttributes()));
if ($mediaGalleryEntries) {
  foreach ($mediaGalleryEntries as $k => $entry) {
    if (!isset($entry['removed']) && !empty($entry['types'])) {
      $this->getMediaGalleryProcessor()->setMediaAttribute($product, $entry['types'], $entry['file']);
    }
  }
}

//save product
$product->save();

Since it's a massive update the "$product->save()" line is called many times and always takes from 2 to 4 seconds. Since I need to launch the code for thousands of products I need a faster way to do that.

I tried with

$product->getResource()->saveAttribute($product, 'media_gallery');

and

$product->addAttributeUpdate('media_gallery', $mediaGallery, $storeId);

but that doesn't work for media gallery (only works for eav I think).

Is there a way to save only media gallery and persisting these changes faster?

(What I look for is something like Magento\Catalog\Api\CategoryLinkManagementInterface::assignProductToCategories method that saves category/product association faster than a full product save)

Best Answer

As far as I know there isn't a way to save media entries without saving product. but the save product shouldn't take very long at all with the productRepositoryInterface.

I created a similar module and I don't have the same issue with media save.

Here is my solution for save media:

 /**
     * Add Product media from folder.
     * @param $product
     * @param $productData
     * @return mixed
     */
    protected function setProductMedia($product, $productData)
    {
        $medias = [];
        $files = scandir(self::MEDIA_PATH);
        $mediaPath = '';
        foreach ($files as $file) {
            if ($file !== '.' && $file !== '..') {
                $path = realpath(self::MEDIA_PATH . $file);
                if ($path) {
                    if (basename($path, '.jpg') == trim($productData['productCode'])) {
                        $mediaPath = self::MEDIA_PATH . $file;
                        break;
                    }
                }
            }
        }

        if ((bool)$mediaPath) {
            $image = $this->_imageContent
               ->setBase64EncodedData(base64_encode(file_get_contents($mediaPath)))
               ->setType(image_type_to_mime_type(exif_imagetype($mediaPath)))
               ->setName(basename($mediaPath));

            $media = $this->_productAttributeMediaGalleryEntry
               ->setFile($mediaPath)
               ->setTypes(['thumbnail', 'small_image', 'image'])
               ->setLabel($productData['description'])
               ->setPosition(0)
               ->setMediaType('image')
               ->setContent($image)
               ->setDisabled(false)
               ->setPosition(0);

            $medias[] = $media;
            $product->setMediaGalleryEntries($medias);
       }

        return $product;
    }

I then save the product with the productRepositoryInterface.

   try {
        $product = $this->_productRepository->get($productData['productCode']);
    } catch (NoSuchEntityException $e) {
        throw new NoSuchEntityException(__('Could Not Update Product Error: %1', $e->getMessage()));
    }

    $product = $this->setProductMedia($product, $productData);

    try {
       $this->_productRepository->save($product);
    } catch (InputException $exception) {
       $this->_logger->critical(__("Could not save product Error: %1", $exception->getMessage()));
    } catch (StateException $exception) {
       $this->_logger->critical(__("Could not save product, Error: %1",$exception->getMessage()));
    } catch (CouldNotSaveException $exception) {
       $this->_logger->critical(__('Could Not Save Product Error: %1' ,$exception->getMessage()));
    }

It saves pretty quickly. I can run 10,000 products within a few minutes. That includes the search for the media that is named as the {sku}.jpg and all stuffed in a single folder.