Jacques Bodin-Hullin Développeur d'applications Web

La perfection est atteinte non quand il ne reste rien à ajouter, mais quand il ne reste rien à enlever.

Antoine de Saint Exupéry

Le cache des blocks sur Magento

Le cache block sur Magento... Quelle galère !

Enfin... à ce qu'on dit.

Je comprends rien... c'est quoi cache_tags ?

C'est comme cache_key ?

Je ne suis pas de cet avis en fait. Le cache c'est easy, il suffit juste de savoir où on met les pieds !

Vous me suivez ?


Les bases du cache sur Magento

Avant d'attaquer le cache sur les blocks il vous faut savoir quelques petites choses sur le cache en général sur Magento.

La clé, ou cache_key

Une clé le plus souvent c'est unique ! Bah là, c'est pareil.

Un cache est identifié par sa clé. Un changement minime dans le contenu de votre cache doit faire varier sa clé !

Sauf bien sûr si vous ne souhaitez pas avoir les deux versions.

Par exemple on peut mettre en cache la réponse d'une requête SQL, à condition de faire en sorte que la clé soit liée à l'identifiant dans la clause WHERE de votre requête !

Sinon c'est la loose... Prenons un exemple bidon :

<?php
namespace Jbh;
class Counter
{
use \Jbh\Db;
protected $_cache = [];
protected $_cacheKey = 'jbh_counter_sql_result';
public function getCountLessThan($id)
{
$sql = "SELECT COUNT(`id`) AS n FROM `table` WHERE `id` < %d;";
$key = sprintf($this->_cacheKey, $id);
if (!isset($this->_cache[$key])) {
$this->_cache[$key] = $this->query(sprintf($sql, $id))->fetchOne('n');
}
return $this->_cache[$key];
}
}
$counter = new \Jbh\Counter;
echo $counter->getCountLessThan(3), ",";
echo $counter->getCountLessThan(10);
// Affiche : 2,2
view raw example_01.php hosted with ❤ by GitHub

On peut voir que la clé ne change pas car elle dépend de la variable $_cacheKey qui ne change pas entre le premier et le second appel à la méthode getCountLessThan.

Maintenant si on change la valeur de $_cacheKey à jbh_counter_sql_result_%d le résultat n'est plus le même !

<?php
$counter = new \Jbh\Counter;
echo $counter->getCountLessThan(3), ",";
echo $counter->getCountLessThan(10);
// Affiche : 2,9
view raw example_02.php hosted with ❤ by GitHub

La clé est donc vraiment importante dans la gestion du cache !

La durée de vie, ou cache_lifetime

Là c'est simple ! On peut décider de donner une durée de vie à notre cache (en secondes).

En gros si notre cache est vieux de plus de cache_lifetime secondes, alors on le supprime et il est généré à nouveau.

Les valeurs de cache_lifetime :

  • null : pas de cache
  • false : 7200 secondes de cache, soit 2 heures
  • (int) : nombre de secondes de cache
  • -(int) : équivaut à pas de cache (date d'expiration du cache dépassée dès la prochaine demande)

Les tags, ou cache_tags

Tagguer un cache... Wait... WHAT?

En toute logique quand on veut vider un cache on indique sa clé et on supprime le cache concerné.

Sauf qu'en pratique... bah c'est franchement la merde. Et c'est là que les tags font leur entrée !

Imaginons plusieurs caches avec chacun une liste de tags :

Cache A:
key => cache_a
tags => foo, bar, baz
lifetime => false
Cache B:
key => cache_b
tags => foo, bar
lifetime => 10
Cache C:
key => cache_c
tags => baz, qux
lifetime => 62400
view raw example_03.txt hosted with ❤ by GitHub

Maintenant, si on veut vider les caches A et B on fait tout simplement :

<?php
$tags = ['foo'];
Mage::app()->cleanCache($tags);
view raw example_04.php hosted with ❤ by GitHub

Par défaut Magento fait en sorte que si un tag match un cache celui-ci soit supprimé. Si vous avez renseigné plusieurs tags à vider, tous les cache concernés par un ou plusieurs de ces tags seront supprimés.

Si vous souhaitez supprimer un cache (sur le cache frontend, pas le cache backend) qui match tous les tags renseignés il faut faire :

<?php
$tags = ['baz', 'qux'];
Mage::app()
->getCache()
->getFrontend()
->clean(Zend_Cache::CLEANING_MODE_MATCHING_TAG, $tags);
// Constantes à utiliser pour nettoyer le cache de manière conditionnelle :
// Zend_Cache::CLEANING_MODE_ALL = 'all';
// Zend_Cache::CLEANING_MODE_OLD = 'old';
// Zend_Cache::CLEANING_MODE_MATCHING_TAG = 'matchingTag';
// Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG = 'notMatchingTag';
// Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG = 'matchingAnyTag'; <=== Par défaut sur Magento
view raw example_05.php hosted with ❤ by GitHub

Ici on a vidé le cache du C.

Le cache sur les blocks

Par défaut un block n'a pas de cache car la méthode getCacheLifetime du block Mage_Core_Block_Abstract retourne null si aucune data cache_lifetime n'est renseignée.

La gestion du cache sur les blocks utilise le fait que chaque block est un Varien_Object pour stocker les valeurs de cache_key, cache_lifetime et cache_tags.

Sauf que qui dit Varien_Object dit __call... non ?

Donc en fait les variables du cache sont récupérées de la manière suivante :

<?php
$block->getCacheKey();
$block->getCacheLifetime();
$block->getCacheTags();
view raw example_06.php hosted with ❤ by GitHub

Donc ? Bah donc on peut surcharger sur nos blocks. Et oui !

La clé avec getCacheKey()

Comme on le sait la clé doit être unique et dépendre du contexte. On a donc plusieurs paramètres à prendre en compte :

  • Le store (donc la locale en passant)
  • Le design (package & thème)
  • Le template phtml utilisé
  • Le nom du block dans le Layout
  • Un identifiant (identifiant d'un produit par exemple)

On peut bien sûr ajouter des paramètres dans notre clé.

Bon, mais en gros, quand on a un tableau de paramètres et qu'on veut une chaîne de caractères on fait comment ? implode ? Avec un hash ?

Soyons fous !

Mais en fait... Magento le fait pour nous ; avec l'appel à getCacheKeyInfo() dans getCacheKey() :

<?php
abstract class Mage_Core_Block_Abstract extends Varien_Object
{
/* ... */
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;
}
/* ... */
}
view raw example_07.php hosted with ❤ by GitHub

Si on y regarde de plus près on remarque que la méthode getCacheKeyInfo() retourne un tableau qui contient une seule valeur : le nom du block dans le Layout :

<?php
abstract class Mage_Core_Block_Abstract extends Varien_Object
{
/* ... */
public function getCacheKeyInfo()
{
return array(
$this->getNameInLayout()
);
}
/* ... */
}
view raw example_08.php hosted with ❤ by GitHub

Dans notre block, on surcharge cette méthode !

<?php
public function getCacheKeyInfo()
{
// Utilisation d'index pour pouvoir faire un array_merge au besoin ;)
// Par exemple si ce tableau est en partie dans un Helper pour faciliter les développement ?
return array(
'name_in_layout' => $this->getNameInLayout(),
'store' => Mage::app()->getStore()->getId(),
'design_package' => Mage::getDesign()->getPackageName(),
'design_theme' => Mage::getDesign()->getTheme('template'),
// 'object_id' => $this->getObjectId()
);
}
view raw example_09.php hosted with ❤ by GitHub

Les tags avec getCacheTags()

Par défaut on essaiera toujours de donner une constante CACHE_TAG à un modèle concocté par nos soins par exemple. A nous de ne pas oublier de l'utiliser !

L'intérêt de définir une constante CACHE_TAG à nos modèles qui sont utilisés dans nos blocks c'est de pouvoir vider le cache de tous les blocks concernés par nos objets lorsqu'on les sauvegarde par exemple !

Il suffit de modifier la méthode _afterSave de nos modèles pour y ajouter le clean du cache !

Revenons à nos moutons et voyons à quoi ressemble la méthode getCacheTags par défaut de Magento :

<?php
abstract class Mage_Core_Block_Abstract extends Varien_Object
{
/* ... */
public function getCacheTags()
{
if (!$this->hasData('cache_tags')) {
$tags = array();
} else {
$tags = $this->getData('cache_tags');
}
$tags[] = self::CACHE_GROUP;
return $tags;
}
/* ... */
}
view raw example_10.php hosted with ❤ by GitHub

Allez, on surcharge dans notre block !

J'ajoute ici un exemple vite fait d'un modèle avec une constante CACHE_TAG bien utilisée :

<?php
class Jbh_Demo_Model_Foo extends Mage_Core_Model_Abstract
{
const CACHE_TAG = 'FOO';
public function getCacheTags()
{
$tags = parent::getCacheTags();
array_push(
$tags,
Jbh_Demo_Model_Foo::CACHE_TAG,
Jbh_Demo_Model_Foo::CACHE_TAG . '_' . $this->getObjectId()
);
return $tags;
}
}
view raw example_11.php hosted with ❤ by GitHub

Vous noterez qu'on peut ajouter n'importe quels tags. Attention cependant à ne pas oublier les tags par défaut récupérés grâce au parent !

La durée de vie avec getCacheLifetime()

Rien de plus simple !

<?php
public function getCacheLifetime()
{
return 36000; // 10h
}
view raw example_12.php hosted with ❤ by GitHub

Pensez à vider le cache !

Utilisez bien les tags enregistrés avec le cache de vos blocks pour vider tout ça !

Vous pouvez aussi enregistrer tous les tags utilisés sur les blocks par défaut d'une page de cette manière :

Dans le fichier /app/code/core/Mage/Core/Model/App.php, ajoutez un log dans la méthode saveCache :

<?php
public function saveCache($data, $id, $tags=array(), $lifeTime=false)
{
$this->_cache->save($data, $id, $tags, $lifeTime);
Mage::log($tags, null, 'tags.log', true);
return $this;
}
view raw example_13.php hosted with ❤ by GitHub

Vous aurez ici les tags utilisés ! Mais pas ceux qui ne sont pas utilisés, mais vidés !

Dans le fichier /app/code/core/Mage/Core/Model/App.php, ajoutez un log dans la méthode cleanCache :

<?php
public function cleanCache($tags=array())
{
$this->_cache->clean($tags);
Mage::log($tags, null, 'tags.log', true);
Mage::dispatchEvent('application_clean_cache', array('tags' => $tags));
return $this;
}
view raw example_14.php hosted with ❤ by GitHub

Conclusion

Gérer le cache des blocks n'est pas une mince affaire mais on s'y retrouve !

Il faut juste savoir s'y prendre.

Vous pouvez également utiliser la méthode addData dans la surcharge du constructeur secondaire protected function _construct() de votre block pour y assigner les valeurs de cache_key, cache_tags et cache_lifetime mais je le déconseille.

Bon cache !


Commentaires

blog comments powered by Disqus

Quelques infos

Contact

Mon QRcode

le QRcode

Un projet, Une agence

Monsieur Biz, une agence spécialisée Magento pour votre projet.

C'est par ici que ça se passe !