Accroître les performances par la mise en cache des blocs

  • De le 10 octobre 2010
  • Difficulté : 2/4

Accroître les performances par la mise en cache des blocs Mettre en cache le contenu d'un bloc est une opération extrêmement simple. Un moyen efficace de diminuer le temps d'affichage des pages par la suppression de requêtes souvent lourdes. Exemple pratique avec un module chargé d'afficher les meilleures ventes par catégorie.

Magento offre un système de cache puissant. L'ensemble des blocs initialisés depuis les modules ont la capacité de mettre en cache le code HTML résultant de leur exécution.

Dans cet exemple nous utiliserons ce système de cache pour un bloc chargé d'afficher sur les pages catégories les meilleures ventes des produits qu'elles contiennent.

Meilleures ventes

Pour cet article nous allons simplifier les méthodes du module au maximum. Une version complète est disponible : télécharger le module d'exemple.

Architecture du module

  • app/code/local/Magentix/BestSellers/Block/
  • Bestsellers.php
  • app/code/local/Magentix/BestSellers/etc/
  • config.xml
  • app/design/frontend/base/default/layout/
  • bestsellers.xml
  • app/design/frontend/base/default/template/bestsellers/
  • bestsellers.phtml
  • app/etc/modules/
  • Magentix_Bestsellers.xml

Développement du module

Il s'agit donc d'un bloc classique. La classe Magentix_BestSellers_Block_Bestsellers dispose pour le moment d'une simple méthode chargée d'initialiser une collection de produits :

app/code/local/Magentix/BestSellers/Block/Bestsellers.php

<?php

class Magentix_BestSellers_Block_Bestsellers extends Mage_Catalog_Block_Product_Abstract {

     protected function getItems() {
          $collection = Mage::getResourceModel('reports/product_collection')
               ->addAttributeToSelect('name')
               ->addOrderedQty()
               ->addAttributeToSort('ordered_qty','desc')
               ->setPageSize(5);
                
          if($category = Mage::registry('current_category')) {
               $collection->addCategoryFilter($category);
          }
                
          return $collection;
     }
}

Nous souhaitons que le bloc apparaisse sur les pages catalogue, dans la colonne de droite. Nous le spécifions dans le layout :

app/design/frontend/base/default/layout/bestsellers.xml

<?xml version="1.0"?>
<layout version="0.1.0">
        <catalog_category_default>
                <reference name="right">
                        <block type="bestsellers/bestsellers" name="products.bestsellers" template="bestsellers/bestsellers.phtml"/>
                </reference>
        </catalog_category_default>
        <catalog_category_layered>
                <reference name="right">
                        <block type="bestsellers/bestsellers" name="products.bestsellers" template="bestsellers/bestsellers.phtml"/>
                </reference>
        </catalog_category_layered>
</layout>

Il reste enfin à structurer les données dans le phtml du template :

app/design/frontend/base/default/template/bestsellers/bestsellers.phtml

<div class="block">
     <h2>Bestsellers</h2>
     <ul>
     <?php foreach($this->getItems() as $_item): ?>
          <li><a href="<?php echo $_item->getProductUrl() ?>"><?php echo $this->htmlEscape($_item->getName()) ?></a></li>
     <?php endforeach; ?>
     </ul>
</div>

Mise en cache

Le mise en cache de ce bloc, même s'il semble simpliste, nous sera extrêmement bénéfique. Les données que nous proposons aux clients n'ont aucunement besoin d'un affichage temps réel. Une mise à jour quotidienne est suffisante.

A ce stade, pour chaque exécution de la page, une requête plutôt lourde (modèle EAV oblige) sera exécutée :

Requête

SELECT SUM(order_items.qty_ordered) AS `ordered_qty`, `e`.*, `cat_index`.`position` AS `cat_index_position`, `price_index`.`price`, `price_index`.`final_price`,
IF(`price_index`.`tier_price`, LEAST(`price_index`.`min_price`, `price_index`.`tier_price`), `price_index`.`min_price`) AS `minimal_price`, `price_index`.`min_price`, `price_index`.`max_price`, `price_index`.`tier_price`
FROM `sales_flat_order_item` AS `order_items`
INNER JOIN `sales_order` AS `order` ON order.entity_id = order_items.order_id AND order.state<>'canceled'
INNER JOIN `catalog_product_entity` AS `e` ON e.entity_id = order_items.product_id AND e.entity_type_id = 4 AND (e.type_id NOT IN ('grouped', 'configurable', 'bundle'))
INNER JOIN `catalog_category_product_index` AS `cat_index` ON cat_index.product_id=e.entity_id AND cat_index.store_id='1' AND cat_index.visibility IN(2, 4) AND cat_index.category_id='3'
INNER JOIN `catalog_product_index_price` AS `price_index` ON price_index.entity_id = e.entity_id AND price_index.website_id = '1' AND price_index.customer_group_id = 0
GROUP BY `e`.`entity_id`
HAVING (ordered_qty > 0)
LIMIT 5

Puisqu'il n'est pas nécessaire d'exécuter systématiquement cette requête, nous pouvons facilement nous en passer.

La mise en cache d'un bloc requière 3 éléments :

  • cache_lifetime : la durée de vie du cache en seconde
  • cache_tags : l'identifiant du type, utilisé principalement pour la suppression du cache quand cela est nécessaire
  • cache_key : l'identifiant du cache

Il nous suffit alors de spécifier ces informations dans le constructeur du bloc :

app/code/local/Magentix/BestSellers/Block/Bestsellers.php

<?php

class Magentix_BestSellers_Block_Bestsellers extends Mage_Catalog_Block_Product_Abstract {

     protected function _construct() {
          $category = Mage::registry('current_category');

          $this->addData(array(
               'cache_lifetime' => 86400,
               'cache_tags'     => array(Mage_Catalog_Model_Category::CACHE_TAG."_".$category->getId()),
               'cache_key'      => $category->getId(),
          ));
     }

     protected function getItems() {
          /* ... */
     }
}

Si l'on place la valeur cache_lifetime à false, la valeur par défaut sera de 7200 secondes. Pour une durée de vie infinie, indiquez 9999999999, soit la valeur maximal que Zend_Cache peut supporter. Ici nous spécifions un rafraîchissement du cache de 24 heures.

Les fichiers générés se situent dans le dossier var/cache. 2 fichiers sont présents, le premier contient les données du cache sérialisées, le second le code HTML (dans notre exemple "mage—--internal-metadatas—ID_CATEGORY_ID" et "mage---ID_CATEGORY_ID") :

mage---ID_CATEGORY_ID

<div class="block">
     <h2>Bestsellers</h2>
     <ul>
          <li><a href="#">Plateau aromatique</a></li>
          <li><a href="#">Sac de jardinage en chanvre</a></li>
          <li><a href="#">Caisse à outils de jardinage</a></li>
     </ul>
</div>

Avant la mise en cache d'un bloc, il est nécessaire de s'interroger sur les paramètres influant les résultats de la requête : store, template, package... Si la clé ne prends pas en compte l'ensemble de ces données, l'affichage du bloc sera systématiquement identique.

Nous allons modifier la clé de notre exemple afin d'y inclure un maximum d'informations :

app/code/local/Magentix/BestSellers/Block/Bestsellers.php

<?php

class Magentix_BestSellers_Block_Bestsellers extends Mage_Catalog_Block_Product_Abstract {

     protected function _construct() {
          $category = Mage::registry('current_category');

          $this->addData(array(
               'cache_lifetime' => 86400,
               'cache_tags'     => array(Mage_Catalog_Model_Category::CACHE_TAG."_".$category->getId()),
               'cache_key'      => $this->getCacheKey(),
          ));
     }

     public function getCacheKey() {
          return 'BESTSELLERS_' . Mage::app()->getStore()->getId()
                    . '_' . (int)Mage::app()->getStore()->isCurrentlySecure()
                    . '_' . Mage::getDesign()->getPackageName()
                    . '_' . Mage::getDesign()->getTheme('template')
                    . '_' . Mage::registry('current_category')->getId();
     }

     protected function getItems() {
          /* ... */
     }
}

Le cache sera alors généré selon le template, le store, le protocole, le thème et l'identifiant de la catégorie.

Autres exemples

Voici d'autres exemples issus d'un très bon article disponible sur le wiki Magento : How to use HTML output cache in Magento

Exemple 1

{NS}_{Module}_Block_{View}

class {NS}_{Module}_Block_{View} extends Mage_Core_Block_Template {

     protected function _construct() {
          $this->addData(array(
               'cache_lifetime' => 120,
               'cache_tags'     => array(Mage_Catalog_Model_Product::CACHE_TAG),
          ));
     }   
}

Dans cet exemple, la durée de vie est de 120 secondes. Les données seront conservées jusqu'à ce que le cache "produit" soit supprimé.

Conséquences :

  • L'enregistrement ou la mise à jour d'un produit supprimera systématiquement ce cache.
  • Ce code est correct si et seulement si le résultat ne dépend pas d'un produit spécifique. Le résultat sera identique quelque soit le produit consulté.
Exemple 2

{NS}_{Module}_Block_{View}

class {NS}_{Module}_Block_{View} extends Mage_Core_Block_Template {
 
     protected function _construct() {
          $this->addData(array(
               'cache_lifetime' => 120,
               'cache_tags'     => array(Mage_Catalog_Model_Product::CACHE_TAG),
               'cache_key'      => $this->getProduct()->getId(),
          ));
     }   
}

Conséquences :

  • Le cache intègre l'identifiant du produit, il sera donc différent pour chaque produit.
  • L'enregistrement ou la mise à jour de n'importe quel produit supprimera ce cache.
Exemple 3

{NS}_{Module}_Block_{View}

class {NS}_{Module}_Block_{View} extends Mage_Core_Block_Template {
 
     protected function _construct() {
          $this->addData(array(
               'cache_lifetime' => 120,
               'cache_tags'     => array(Mage_Catalog_Model_Product::CACHE_TAG . "_" . $this->getProduct()->getId()),
               'cache_key'      => $this->getProduct()->getId(),
          ));
     }
   
}

Conséquences :

  • Le cache sera différent pour chaque produit.
  • Seule la mise à jour du produit concerné supprimera le cache.
commentaires

Commentez cet article : Accroître les performances par la mise en cache des blocs