Squeezing Magento’s cache

There are quite few ways to optimise and speed up Magento. If you run a Google search you'll get different solutions from minifying js to some nice server settings. There is one thing though I couldn't see being mentioned anywhere - built-in image caching system.

By default Magento captures all resize method calls from phtml templates and saves processed images to /media/catalog/product/cache/{store_id}/{source_image_type:original, small_image, thumbnail}/{image_dimensions}/... which sounds perfectly reasonable, except for one tiny thing which I lately discovered. It may be different for your shop, but I don't need different images for thumbnails and catalogue view - I prefer to be consistent with product representation and so I thought I'd use original images as a source for resizing throughout the site. Unfortunately that's where Magento turns on its annoying 'no you won't!' mask. Apparently, depending on what are you trying to edit, different classes allow you to use only thumbnails or small images as a source for processing! Why is it bad?

addImagesToCollection mmodule file structureImagine that you have a product image present on your site in few different sizes. Because of {source_image_type} bit in Magento's cache path, it's very likely you'll end up with duplicates of the exact same file, e.g:
/media/catalog/product/cache/1/small_image/100x100/{file...} and
/media/catalog/product/cache/1/image/100x100/{file...}. Now if you assume that on average each product image is 10KB and you have 200 unique product views per day, 30 days a month - it gives you nearly 60MB - for some it's not a lot but I personally don't like wasting my bandwidth.

With that in mind I started digging for classes I could extend to unify image caching and I found few of them (I'm sure there are more so feel free to udpate this list): Mage_Catalog_Model_Resource_Eav_Mysql4_Category
Mage_Catalog_Block_Product_List_Crosssell
Mage_Catalog_Block_Product_List_Related
Mage_Catalog_Block_Product_List_Upsell
So without further delay, let's start preparing our module. I called myself addImagesToCollection (d'oh) and you can figure out the file structure from the image on your right.

All modifications I made are as follows:


/* in Category.php */
class Criography_AddImagesToCollection_Model_Resource_Eav_Mysql4_Category extends Mage_Catalog_Model_Resource_Eav_Mysql4_Category
{

    /**
     * Enter description here...
     *
     * @param Mage_Catalog_Model_Category $category
     * @return unknown
     */
    public function getChildrenCategories($category)
    {
        $collection = $category->getCollection();
        /* @var $collection Mage_Catalog_Model_Resource_Eav_Mysql4_Category_Collection */
        $collection->addAttributeToSelect('url_key')
            ->addAttributeToSelect('name')
            ->addAttributeToSelect('all_children')
            ->addAttributeToSelect('is_anchor') 
            ->addAttributeToSelect('image')
            ->addAttributeToSelect('description')
            ->addAttributeToFilter('is_active', 1)
            ->addIdFilter($category->getChildren())
            ->setOrder('position', 'ASC')
            ->joinUrlRewrite()
            ->load();
        return $collection;
    }    
        
}

/* in Crosssell.php */
class Criography_AddImagesToCollection_Block_Product_List_Crosssell extends Mage_Catalog_Block_Product_List_Crosssell
{
    /**
     * Prepare crosssell items data
     *
     * @return Mage_Catalog_Block_Product_List_Crosssell
     */
    protected function _prepareData()
    {
        $product = Mage::registry('product');
        /* @var $product Mage_Catalog_Model_Product */

        $this->_itemCollection = $product->getCrossSellProductCollection()
            ->addAttributeToSelect(Mage::getSingleton('catalog/config')->getProductAttributes())
            ->addAttributeToSelect('image')
            ->addAttributeToSort('position', 'asc')
            ->addStoreFilter();

        Mage::getSingleton('catalog/product_visibility')->addVisibleInCatalogFilterToCollection($this->_itemCollection);

        $this->_itemCollection->load();

        foreach ($this->_itemCollection as $product) {
            $product->setDoNotUseCategoryId(true);
        }

        return $this;
    }

}

/* in Related.php */
class Criography_AddImagesToCollection_Block_Product_List_Related extends Mage_Catalog_Block_Product_List_Related
{
  
  
    protected function _prepareData()
    {
        $product = Mage::registry('product');
        /* @var $product Mage_Catalog_Model_Product */

        $this->_itemCollection = $product->getRelatedProductCollection()
            ->addAttributeToSelect('required_options')
            ->addAttributeToSelect('image')
            ->addAttributeToSort('position', 'asc')
            ->addStoreFilter()
        ;
        Mage::getResourceSingleton('checkout/cart')->addExcludeProductFilter($this->_itemCollection,
            Mage::getSingleton('checkout/session')->getQuoteId()
        );
        $this->_addProductAttributesAndPrices($this->_itemCollection);

        Mage::getSingleton('catalog/product_visibility')->addVisibleInCatalogFilterToCollection($this->_itemCollection);

        $this->_itemCollection->load();

        foreach ($this->_itemCollection as $product) {
            $product->setDoNotUseCategoryId(true);
        }

        return $this;
    }
}

/* in Upsell.php */
class Criography_AddImagesToCollection_Block_Product_List_Upsell extends Mage_Catalog_Block_Product_List_Upsell
{  
  
    protected function _prepareData()
    {
        $product = Mage::registry('product');
        /* @var $product Mage_Catalog_Model_Product */
        
        $this->_itemCollection = $product->getUpSellProductCollection()
            ->addAttributeToSelect('image')
            ->addAttributeToSort('position', 'asc')
            ->addStoreFilter()
        ;
        Mage::getResourceSingleton('checkout/cart')->addExcludeProductFilter($this->_itemCollection,
            Mage::getSingleton('checkout/session')->getQuoteId()
        );
        $this->_addProductAttributesAndPrices($this->_itemCollection);

        Mage::getSingleton('catalog/product_visibility')->addVisibleInCatalogFilterToCollection($this->_itemCollection);

        if ($this->getItemLimit('upsell') > 0) {
            $this->_itemCollection->setPageSize($this->getItemLimit('upsell'));
        }

        $this->_itemCollection->load();

        /**
         * Updating collection with desired items
         */
        Mage::dispatchEvent('catalog_product_upsell', array(
            'product'       => $product,
            'collection'    => $this->_itemCollection,
            'limit'         => $this->getItemLimit()
        ));

        foreach ($this->_itemCollection as $product) {
            $product->setDoNotUseCategoryId(true);
        }

        return $this;
    }
}

Finally you edit config.xml in your module etc folder


<?xml version="1.0"?>

  <config>
    <modules>
      <Criography_AddImagesToCollection>
        <version>0.0.1</version>
        <depends>
          <Mage_Reports />
          <Mage_Catalog />
        </depends>
      </Criography_AddImagesToCollection>
    </modules>
    
    <global>
        <models>
            <catalog_resource_eav_mysql4>
                <rewrite>
                    <category>Criography_AddImagesToCollection_Model_Resource_Eav_Mysql4_Category</category>
                </rewrite>
            </catalog_resource_eav_mysql4>
        </models>
        <blocks>
            <catalog>
                <rewrite>
                    <product_list_crosssell>Criography_AddImagesToCollection_Block_Product_List_Crosssell</product_list_crosssell>
                    <product_list_upsell>Criography_AddImagesToCollection_Block_Product_List_Upsell</product_list_upsell>
                    <product_list_related>Criography_AddImagesToCollection_Block_Product_List_Related</product_list_related>
                </rewrite>                     
            </catalog>
        </blocks>
    </global> 

    <frontend>
      <routers>
        <Criography_AddImagesToCollection>
          <use>standard</use>
          <args>
            <module>Criography_AddImagesToCollection</module>
            <frontName>addimagestocollection</frontName>
          </args>
        </Criography_AddImagesToCollection>
      </routers>
    </frontend>
  </config>

and create Criography_AddImagesToCollection.xml in /app/etc/modules/


<?xml version="1.0"?>
<config>
    <modules>
        <Criography_AddImagesToCollection>
            <active>true</active>
            <codePool>local</codePool>
        </Criography_AddImagesToCollection>
    </modules>
</config>

Now you should be able to use something like this:


<img src="<?php echo $this->helper('catalog/image')->init($this->getProduct(), 'image', $_image->getFile())->resize(100); ?>" alt="my resized image" />

and if you stay consistent with calling certain group of images you'll save a lot of space, bandwidth and make your site load faster as all files will be cached by visitor's browser!

To sum up - I'm not saying it's the only way to do it and I'm sure there are some Magento gurus that will prove my approach insanely inefficient, but it doesn't change the fact that this solution did work for me! So if you have any suggestions or ideas how to improve it - let me know! You may also be interested in reading kind of a follow up on cropping images in Magento, which helped me to reduce cache size by over 50%! I'll publish it in few days, so stay tuned!