Magento Product – Load Inside Loop Behavior Explained

product

After reading Marius' answer on this post:

Product loop giving incorrect images

I am concerned about how exactly Magento's load() function works on product / other models. Marius mentions:

If you use the same variable $obj for loading products you get issues
like yours, because the objects are passed as reference.

However, the behavior I experienced when using ->load(); outside a loop was not exactly as mentioned there. I was trying to get the product final pricing within a loop.

$product = Mage::getModel('catalog/product');
foreach ($product_id_array as $product_id){
    $product = $product->load($product_id);
    echo $product->getName() . " - " . $product->getFinalPrice . "<br/>";
}

When declaring the model outside the loop, name would display correctly, but final pricing would not. If I put it inside the loop, it would function as intended.

What is the reasoning behind this? I understand that it can be a performance kill using load within a loop, but:

  1. How else are you meant to retrieve product final pricing? (Special pricing / special to-&from- dates taken into account), and
  2. Howcome only some variables malfunction like that? (I.E ->getFinalPrice(); and not ->getName();)

Best Answer

First of all let me give you a load-less solution for your problem. I will give an explanation after that.
Here is how you can get the price attributes correctly.
Let's say you have the product ids you need.
You can retrieve the products via a collection.

$collection = Mage::getModel('catalog/product')->getCollection();
$collection
            ->addAttributToFilter('entity_id', array('in'=>$product_id_array)) //filter only for needed ids
            ->addAttributeToSelect(Mage::getSingleton('catalog/config')->getProductAttributes()) //or any other attributes you need.
            ->addMinimalPrice()
            ->addFinalPrice()
            ->addTaxPercents();

Now you can loop through the $collection and you should be able to call getFinalPrice or getSpecialPrice and get a correct result.

Now here is why it doesn't work in your approach.
final_price is not a product attribute. It is calculated.
But take a look at the getFinalPrice method.

public function getFinalPrice($qty=null)
{
    $price = $this->_getData('final_price');
    if ($price !== null) {
        return $price;
    }
    return $this->getPriceModel()->getFinalPrice($qty, $this);
}

the code above means that if there is a vaue for _data['final_price'] it is returned. Otherwise it is calculated via the price model.

Now, when you execute your code:

$product = Mage::getModel('catalog/product');
foreach ($product_id_array as $product_id){
    $product = $product->load($product_id);
    echo $product->getName() . " - " . $product->getFinalPrice . "<br/>";
}

This happens.
in the first iteration, the product with the first id is loaded, as expected.
And the _data will look something like this:

array(
    'name' => 'Some name', 
    'entity_id' => 44,
    .....
);

When you call getFinalPrice for the same product, there is no _data['final_price'] so it's calculated via the price model and added to the data array that will look like this:

array(
    'name' => 'Some name', 
    'entity_id' => 44,
    .....
    'final_price' => 99.99,
);

So far so good.
But when you call load in the second iteration, because you call the same instance or the product model as for the first loop, the existing data is merged with what comes from the db.

Let's say you get this from the db.

array(
    'name' => 'Other name', 
    'entity_id' => 55,
    .....
);

Merging the 2 arrays will result in

array(
    'name' => 'Other name', 
    'entity_id' => 55,
    .....
    'final_price' => 99.99,
);

See how the final_price is already there, because it was already on the product instance.
And when you call getFinalPrice you get the value set in the _data.

It doesn't happen for the name because you get a new name from the db that overrides the one you already had on the product instance.

Everything works as expected when you add the model instantiation in the loop because each time you call load the data from the db is merged with an empty array.

I hope this clears your doubts.

Related Topic