5

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?

7

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 value which you want to set on 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 implementation in Magento\Catalog\Modul\Product:

/**
 * Save current attribute with code $code and assign 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:

...
foreach($productCollection as $product{
    $product->getResource()->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.

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.