A frustrating issue appeared on a site I manage, where the related and upsell products appear as the same product.

This is directly related to cache, as it appeared as soon as cache gets enabled.

Testing, I find that if I clear the BLOCK_HTML cache, then they work once, then stick to the first product. Clearly a cache issue

 

Looking at the html in the page, I see that each element is encased in a cache block tag as such:

 <!--{CATALOG_PRODUCT_ITEM_UPSELL_5b4b0b281925d61864f26beea8781aab}-->    
BLOCK HTML HERE <!--/{CATALOG_PRODUCT_ITEM_UPSELL_5b4b0b281925d61864f26beea8781aab}-->

<!--{CATALOG_PRODUCT_ITEM_UPSELL_5b4b0b281925d61864f26beea8781aab}-->
BLOCK HTML HERE <!--/{CATALOG_PRODUCT_ITEM_UPSELL_5b4b0b281925d61864f26beea8781aab}-->
<!--{CATALOG_PRODUCT_ITEM_UPSELL_5b4b0b281925d61864f26beea8781aab}-->
BLOCK HTML HERE <!--/{CATALOG_PRODUCT_ITEM_UPSELL_5b4b0b281925d61864f26beea8781aab}-->

Clearly the issue is that the cache key is the same for each block of html, thus magento faithufully pulls in the same html block from the cache.

Looking at the html block that generates it (upsell.phtml) I find: (can you see the problem?)

<?php $i=0; foreach ($this->getItemCollection() as $_item): ?>   
                <li class="imagearea">
                    <?php
                        $itemBlock = $this->getChild('catalog.product.upsell.item')->setItem($_item)->setPosition($i);
                        echo $itemBlock->toHtml();
                    ?>
                </li>

Ok, so what is the cacheKey, and how is it calculated, thus I add

$cacheKey = $itemBlock->getCacheKey(); 

to the call thus I now have:

<?php
                        $itemBlock = $this->getChild('catalog.product.upsell.item')->setItem($_item)->setPosition($i);
                       $cacheKey = $itemBlock->getCacheKey();
                        echo $itemBlock->toHtml();
                    ?>

and tracing into the $cacheKey = $itemBlock->getCacheKey(); call I find:

/**
     * Get Key for caching block content
     *
     * @return string
     */
    public function getCacheKey()
    {
        if ($this->hasData('cache_key')) {
            return $this->getData('cache_key');
        }
        /**
         * don't prevent recalculation by saving generated cache key
         * because of ability to render single block instance with different data
         */
        $key = $this->getCacheKeyInfo();
        //ksort($key);  // ignore order
        $key = array_values($key);  // ignore array keys
        $key = implode('|', $key);
        $key = sha1($key);
        return $key;
    }

in abstract class Mage_Core_Block_Abstract

tracing into the call

$key = $this->getCacheKeyInfo();

I find

/**
     * Get cache key informative items with the position number to differentiate
     *
     * @return array
     */
    public function getCacheKeyInfo()
    {
        $cacheKeyInfo = parent::getCacheKeyInfo();
        $cacheKeyInfo[] = $this->getPosition();
        return $cacheKeyInfo;
    }

in class Enterprise_TargetRule_Block_Catalog_Product_Item extends Mage_Catalog_Block_Product_Abstract

and tracing one level more using

$cacheKeyInfo = parent::getCacheKeyInfo();

I find

/**
     * Get cache key informative items
     *
     * @return array
     */
    public function getCacheKeyInfo()
    {
        return array(
            'BLOCK_TPL',
            Mage::app()->getStore()->getCode(),
            $this->getTemplateFile(),
            'template' => $this->getTemplate()
        );
    }

 

Thus, the cacheKey (before it is processed by)

 $key = sha1($key);

is

"BLOCK_TPL|default|frontend/enterprise/wl/template/targetrule/catalog/product/list/upsell/item.phtml|targetrule/catalog/product/list/upsell/item.phtml|0"
"BLOCK_TPL|default|frontend/enterprise/wl/template/targetrule/catalog/product/list/upsell/item.phtml|targetrule/catalog/product/list/upsell/item.phtml|0"
"BLOCK_TPL|default|frontend/enterprise/wl/template/targetrule/catalog/product/list/upsell/item.phtml|targetrule/catalog/product/list/upsell/item.phtml|0"

The first noticed issue here isthat the item position, which is the last element in that string. This clearly points to the fact that the position is not actually incremented.

I thus changed the initial block of code to

<?php
                        $itemBlock = $this->getChild('catalog.product.upsell.item')->setItem($_item)->setPosition($i);
                       $cacheKey = $itemBlock->getCacheKey();
$i++;
                        echo $itemBlock->toHtml();
                    ?>

 

and now I get

"BLOCK_TPL|default|frontend/enterprise/wl/template/targetrule/catalog/product/list/upsell/item.phtml|targetrule/catalog/product/list/upsell/item.phtml|0"
"BLOCK_TPL|default|frontend/enterprise/wl/template/targetrule/catalog/product/list/upsell/item.phtml|targetrule/catalog/product/list/upsell/item.phtml|1"
"BLOCK_TPL|default|frontend/enterprise/wl/template/targetrule/catalog/product/list/upsell/item.phtml|targetrule/catalog/product/list/upsell/item.phtml|2"

Great, but there is still an issue!

Imagine product X and product Y. They both have 3 related items.
The cache string generated above is not unique enough to guarantee that the blocks will not clash.

The fix for this is pretty simple: Just add the product_id for each item to the key that is generated.

The ending block of code thus looks like this:

<?php
                        $itemBlock = $this->getChild('catalog.product.upsell.item')->setItem($_item)->setPosition($i);
                        $cacheKey = $itemBlock->getCacheKey();
                        $itemBlock->setCacheKey($cacheKey.  $_item->getId());
                        $i++;
                        echo $itemBlock->toHtml();
                    ?>

 

and in the html we end up with:

<!--{CATALOG_PRODUCT_ITEM_UPSELL_e7dca3730940fadf2ff711ad783b4982}-->    
  BLOCK HTML HERE
<!--/{CATALOG_PRODUCT_ITEM_UPSELL_e7dca3730940fadf2ff711ad783b4982}-->             
       
<!--{CATALOG_PRODUCT_ITEM_UPSELL_462f4ea32a228c6bfb367f657fdb7b79}-->
BLOCK HTML HERE
<!--/{CATALOG_PRODUCT_ITEM_UPSELL_462f4ea32a228c6bfb367f657fdb7b79}--> <!--{CATALOG_PRODUCT_ITEM_UPSELL_6fccf8853a17b7ba5da3aae2c32ea748}-->
BLOCK HTML HERE
<!--/{CATALOG_PRODUCT_ITEM_UPSELL_6fccf8853a17b7ba5da3aae2c32ea748}-->

 

The end result is unique enough cache tags for each item, per product.