Magento 2 – Good Practices for Saving Many Objects

databaseentitiesmagento-2.1magento2

I have a question regarding a good practice: how to correctly save many objects?

Let's assume I modified many entities in a collection, let's say a collection of Products. This is what I could do:

...

foreach($productCollection as $product{
    $this->productRepository->save($product);
}

...

However using the method save() in a loop is not only a bad practice but also highlighted by PHP CS. Of course, if the collection only contains 4 or 5 products it won't change the face of the world, but sometimes it is clearly most than that.

I know that if I only modify an attribute, I don't need to use the repository and can use the built-in Magento component to update attributes, however sometimes I also need to do some logic and really need the product entity.

So anyone has a good practice to share in this context?

Best Answer

If you have to do a mass update of one (or more attributes) to the same value, you can use:

$this->_objectManager->get('Magento\Catalog\Model\Product\Action')
    ->updateAttributes($productIds, ['status' => $status], $storeId);

In this example, we're updating all products matched by id ($productIds is array of product IDs), by setting their status attribute to the $status value, for store $storeId.

If you want just to update single attribute in the collection of products, you can use (in your case):

...
foreach($productCollection as $product{
    $product->addAttributeUpdate($attributeCode, $value, $store);
}
...

$attributeCode represents attribute name which has to be saved

$value represents the value which you want to set on the entity

$store represents store id, it can be $product->getStoreId()

Problem with addAttributeUpdate() is that updates attribute, but it doesn't update currently loaded product model. Take a look at the implementation in Magento\Catalog\Model\Product:

/**
 * Save current attribute with code $code and assign a new value
 *
 * @param string $code  Attribute code
 * @param mixed  $value New attribute value
 * @param int    $store Store ID
 * @return void
 */
public function addAttributeUpdate($code, $value, $store)
{
    $oldValue = $this->getData($code);
    $oldStore = $this->getStoreId();

    $this->setData($code, $value);
    $this->setStoreId($store);
    $this->getResource()->saveAttribute($this, $code);

    $this->setData($code, $oldValue);
    $this->setStoreId($oldStore);
}

In that case it's better to use something like, productResource is Magento\Catalog\Model\ResourceModel\Product :

...
foreach($productCollection as $product{
    // its sugessted to inject resource as dependency than using deprecated getResource() method. 
    // check reference for more information.
    // $product->getResource()->saveAttribute($product, $attributeCode);
       $this->productResource->saveAttribute($product, $attributeCode);
}
...

Lack of this solution becomes visible when you're working on large collections. That's why we have iterators which grabs data from database, one by one, and on that way, it prevents memory exhaustion.

...
$this->_dataObject = $this->_objectManager->get('Magento\Catalog\Model\Product');

$iterator = $this->_objectManager->get('Magento\Framework\Model\ResourceModel\Iterator')

$iterator->walk($productCollection->getSelect(), [[$this, 'walkCallback']]);
...

public function walkCallback($data)
{
    $this->_dataObject->reset(); // clean up shared object
    $this->_dataObject->setData($data['row']); // map data to product model
    $this->_dataObject->setDescription('test');
    $this->_dataObject->getResource()->saveAttribute($this->_dataObject, 'description'); // save only changed attribute instead of whole object
}

Those are ideas which you can use for manipulating collections.

Reference: https://github.com/magento/magento-coding-standard/pull/189

Related Topic