Blame | Last modification | View Log | RSS feed
<?php// declare(encoding='UTF-8');/*** Classe Cache permettant de mettre en cache des données.* Basée sur les principes de Zend_Cache (Copyright (c) 2005-2010, Zend Technologies USA, Inc. All rights reserved.)** @category php 5.2* @package Framework* @author Jean-Pascal MILCENT <jpm@tela-botanica.org>* @copyright Copyright (c) 2010, Tela Botanica (accueil@tela-botanica.org)* @license http://framework.zend.com/license/new-bsd Licence New BSD* @license http://www.cecill.info/licences/Licence_CeCILL_V2-fr.txt Licence CECILL* @license http://www.gnu.org/licenses/gpl.html Licence GNU-GPL* @version $Id: Cache.php 299 2011-01-18 14:03:46Z jpm $* @link /doc/framework/*/class Cache {/** Socke les enregistrements du cache dans des fichiers textes de façon extremement simple. */const STOCKAGE_MODE_SIMPLE = "FichierSimple";/** Socke les enregistrements du cache dans des fichiers textes. */const STOCKAGE_MODE_FICHIER = "Fichier";/** Socke les enregistrements du cache dans une base de données SQLite. */const STOCKAGE_MODE_SQLITE = "Sqlite";/** 'tous' (par défaut) : supprime tous les enregistrements. */const NETTOYAGE_MODE_TOUS = "tous";/** 'expiration' : supprime tous les enregistrements dont la date d'expériration est dépassée. */const NETTOYAGE_MODE_EXPIRATION = "expiration";/** 'avecLesTags' : supprime tous les enregistrements contenant tous les tags indiqués. */const NETTOYAGE_MODE_AVEC_LES_TAGS = "avecLesTags";/** 'sansLesTags' : supprime tous les enregistrements contenant aucun des tags indiqués. */const NETTOYAGE_MODE_SANS_LES_TAGS = "sansLesTags";/** 'avecUnTag' : supprime tous les enregistrements contenant au moins un des tags indiqués. */const NETTOYAGE_MODE_AVEC_UN_TAG = "avecUnTag";/*** Dernier identifiant de cache utilisé.** @var string $dernier_id*/private $dernier_id = null;/*** Les options disponibles pour le cache :* ====> (string) stockage_mode :* Indique le mode de stockage du cache à utiliser parmis :* - Cache::STOCKAGE_MODE_FICHIER : sous forme d'une arborescence de fichiers et dossier* - Cache::STOCKAGE_MODE_SQLITE : sous forme d'une base de données SQLite** ====> (string) stockage_chemin :* Chemin vers :* - Cache::STOCKAGE_MODE_FICHIER : le dossier devant contenir l'arborescence.* - Cache::STOCKAGE_MODE_SQLITE : le fichier contenant la base SQLite.** ====> (boolean) controle_ecriture :* - Active / Désactive le controle d'écriture (le cache est lue jute après l'écriture du fichier pour détecter sa corruption)* - Activer le controle d'écriture ralentira légèrement l'écriture du fichier de cache mais pas sa lecture* Le controle d'écriture peut détecter la corruption de fichier mais ce n'est pas un système de controle parfait.** ====> (boolean) mise_en_cache :* - Active / Désactive la mise en cache* (peut être très utile pour le débogage des scripts utilisant le cache** =====> (string) cache_id_prefixe :* - préfixe pour les identifiant de cache ( = espace de nom)** ====> (boolean) serialisation_auto :* - Active / Désactive la sérialisation automatique* - Peut être utilisé pour sauver directement des données qui ne sont pas des chaines (mais c'est plus lent)** ====> (int) nettoyage_auto :* - Désactive / Régler le processus de nettoyage automatique* - Le processus de nettoyage automatiques détruit les fichier trop vieux (pour la durée de vie donnée)* quand un nouveau fichier de cache est écrit :* 0 => pas de nettoyage automatique* 1 => nettoyage automatique systématique* x (integer) > 1 => nettoyage automatique toutes les 1 fois (au hasard) sur x écriture de fichier de cache** ====> (int) duree_de_vie :* - Durée de vie du cache (en secondes)* - Si null, le cache est valide indéfiniment.** @var array $options les options disponibles pour le cache .*/protected $options = array('stockage_mode' => self::STOCKAGE_MODE_FICHIER,'stockage_chemin' => null,'controle_ecriture' => true,'mise_en_cache' => true,'cache_id_prefixe' => null,'serialisation_auto' => false,'nettoyage_auto' => 10,'duree_de_vie' => 3600,);protected $stockage = null;public function __construct($options = array(), $options_stockage = array()) {$this->initialiserOptionsParConfig();$this->setOptions($options);if ($this->options['stockage_mode'] == self::STOCKAGE_MODE_FICHIER) {$this->stockage = new CacheFichier($options_stockage, $this);$this->stockage->setEmplacement($this->options['stockage_chemin']);} else if ($this->options['stockage_mode'] == self::STOCKAGE_MODE_SQLITE) {$this->stockage = new CacheSqlite($options_stockage, $this);$this->stockage->setEmplacement($this->options['stockage_chemin']);} else {trigger_error("Ce mode de stockage n'existe pas ou ne supporte pas la création par le constructeur", E_USER_WARNING);}}private function initialiserOptionsParConfig() {while (list($nom, $valeur) = each($this->options)) {if (Config::existe($nom)) {$this->options[$nom] = Config::get($nom);}}}private function setOptions($options) {while (list($nom, $valeur) = each($options)) {if (!is_string($nom)) {trigger_error("Nom d'option incorecte : $nom", E_USER_WARNING);}$nom = strtolower($nom);if (array_key_exists($nom, $this->options)) {$this->options[$nom] = $valeur;}}}/*** Permet de (re-)définir l'emplacement pour le stockage du cache.* En fonction du mode de stockage utilisé , l'emplacement indiqué correspondra au chemin du :* - dossier où stocker les fichiers pour le mode "fichier".* - fichier de la base de données pour le mode "sqlite".* @param string $emplacement chemin vers dossier (Cache::STOCKAGE_MODE_FICHIER) ou fichier base Sqlite (Cache::STOCKAGE_MODE_SQLITE)* @return void*/public function setEmplacement($emplacement) {if ($emplacement != null) {$this->executerMethodeStockage('setEmplacement', array($emplacement));} else {trigger_error("L'emplacement ne peut pas être null.", E_USER_WARNING);}}public static function fabriquer($mode, $options = array()) {if ($mode == self::STOCKAGE_MODE_SIMPLE) {return new CacheSimple($options);} else {trigger_error("Le mode '$mode' de stockage n'existe pas ou ne supporte pas la création par fabrique", E_USER_WARNING);}return false;}/*** Teste si un cache est disponible pour l'identifiant donné et (si oui) le retourne (false dans le cas contraire)** @param string $id Identifiant de cache.* @param boolean $ne_pas_tester_validiter_du_cache Si mis à true, la validité du cache n'est pas testée* @return mixed|false Cached datas*/public function charger($id, $ne_pas_tester_validiter_du_cache = false) {$donnees = false;if ($this->options['mise_en_cache'] === true) {$id = $this->prefixerId($id);$this->dernier_id = $id;self::validerIdOuTag($id);$donnees = $this->executerMethodeStockage('charger', array($id, $ne_pas_tester_validiter_du_cache));$donnees = $this->deserialiserAutomatiquement($donnees);}return $donnees;}/*** Test if a cache is available for the given id** @param string $id Cache id* @return int|false Last modified time of cache entry if it is available, false otherwise*/public function tester($id) {$resultat = false;if ($this->options['mise_en_cache'] === true) {$id = $this->prefixerId($id);self::validerIdOuTag($id);$this->dernier_id = $id;$resultat = $this->executerMethodeStockage('tester', array($id));}return $resultat;}/*** Sauvegarde en cache les données passées en paramètre.** @param mixed $donnees Données à mettre en cache (peut être différent d'une chaine si serialisation_auto vaut true).* @param string $id Identifiant du cache (s'il n'est pas définit, le dernier identifiant sera utilisé).* @param array $tags Mots-clés du cache.* @param int $duree_de_vie_specifique Si != false, indique une durée de vie spécifique pour cet enregistrement en cache (null => durée de vie infinie)* @return boolean True si aucun problème n'est survenu.*/public function sauver($donnees, $id = null, $tags = array(), $duree_de_vie_specifique = false) {$resultat = true;if ($this->options['mise_en_cache'] === true) {$id = ($id === null) ? $this->dernier_id : $this->prefixerId($id);self::validerIdOuTag($id);self::validerTableauDeTags($tags);$donnees = $this->serialiserAutomatiquement($donnees);$this->nettoyerAutomatiquement();$resultat = $this->executerMethodeStockage('sauver', array($donnees, $id, $tags, $duree_de_vie_specifique));if ($resultat == false) {// Le cache étant peut être corrompu, nous le supprimons$this->supprimer($id);} else {$resultat = $this->controlerEcriture($id, $donnees);}}return $resultat;}/*** Supprime un enregistrement en cache.** @param string $id Identificant du cache à supprimer.* @return boolean True si ok*/public function supprimer($id) {$resultat = true;if ($this->options['mise_en_cache'] === true) {$id = $this->prefixerId($id);self::validerIdOuTag($id);$resultat = $this->executerMethodeStockage('supprimer', array($id));}return $resultat;}/*** Nettoyage des enregistrements en cache** Mode de nettoyage disponibles :* 'tous' (défaut) => supprime tous les enregistrements ($tags n'est pas utilisé)* 'expiration' => supprime tous les enregistrements dont la date d'expériration est dépassée ($tags n'est pas utilisé)* 'avecLesTag' => supprime tous les enregistrements contenant tous les tags indiqués* 'sansLesTag' => supprime tous les enregistrements contenant aucun des tags indiqués* 'avecUnTag' => supprime tous les enregistrements contenant au moins un des tags indiqués** @param string $mode mode de nettoyage* @param array|string $tags peut être un tableau de chaîne ou une simple chaine.* @return boolean True si ok*/public function nettoyer($mode = self::NETTOYAGE_MODE_TOUS, $tags = array()) {$resultat = true;if ($this->options['mise_en_cache'] === true) {if (!in_array($mode, array(Cache::NETTOYAGE_MODE_TOUS,Cache::NETTOYAGE_MODE_EXPIRATION,Cache::NETTOYAGE_MODE_AVEC_LES_TAGS,Cache::NETTOYAGE_MODE_SANS_LES_TAGS,Cache::NETTOYAGE_MODE_AVEC_UN_TAG))) {trigger_error("Le mode de nettoyage du cache indiqué n'est pas valide", E_USER_WARNING);}self::validerTableauDeTags($tags);$resultat = $this->executerMethodeStockage('nettoyer', array($mode, $tags));}return $resultat;}/*** Return an array of stored cache ids** @return array array of stored cache ids (string)*/public function getIds() {$ids = $this->executerMethodeStockage('getIds');$ids = $this->supprimerPrefixe($ids);return $ids;}/*** Return an array of stored tags** @return array array of stored tags (string)*/public function getTags() {return $this->executerMethodeStockage('getTags');}/*** Return an array of stored cache ids which match given tags** In case of multiple tags, a logical AND is made between tags** @param array $tags array of tags* @return array array of matching cache ids (string)*/public function getIdsAvecLesTags($tags = array()) {$ids = $this->executerMethodeStockage('getIdsAvecLesTags', array($tags));$ids = $this->supprimerPrefixe($ids);return $ids;}/*** Return an array of stored cache ids which don't match given tags** In case of multiple tags, a logical OR is made between tags** @param array $tags array of tags* @return array array of not matching cache ids (string)*/public function getIdsSansLesTags($tags = array()) {$ids = $this->executerMethodeStockage('getIdsSansLesTags', array($tags));$ids = $this->supprimerPrefixe($ids);return $ids;}/*** Return an array of stored cache ids which match any given tags** In case of multiple tags, a logical OR is made between tags** @param array $tags array of tags* @return array array of matching any cache ids (string)*/public function getIdsAvecUnTag($tags = array()) {$ids = $this->executerMethodeStockage('getIdsAvecUnTag', array($tags));$ids = $this->supprimerPrefixe($ids);return $ids;}/*** Return the filling percentage of the backend storage** @return int integer between 0 and 100*/public function getPourcentageRemplissage() {return $this->executerMethodeStockage('getPourcentageRemplissage');}/*** Return an array of metadatas for the given cache id** The array will include these keys :* - expire : the expire timestamp* - tags : a string array of tags* - mtime : timestamp of last modification time** @param string $id cache id* @return array array of metadatas (false if the cache id is not found)*/public function getMetadonnees($id) {$id = $this->prefixerId($id);return $this->executerMethodeStockage('getMetadonnees', array($id));}/*** Give (if possible) an extra lifetime to the given cache id** @param string $id cache id* @param int $extraLifetime* @return boolean true if ok*/public function ajouterSupplementDureeDeVie($id, $supplement_duree_de_vie) {$id = $this->prefixerId($id);return $this->executerMethodeStockage('ajouterSupplementDureeDeVie', array($id, $supplement_duree_de_vie));}/*** Fabrique et retourne l'identifiant du cache avec son préfixe.** Vérifie l'option 'cache_id_prefixe' et retourne le nouvel id avec préfixe ou simplement l'id lui même si elle vaut null.** @param string $id Identifiant du cache.* @return string Identifiant du cache avec ou sans préfixe.*/private function prefixerId($id) {$nouvel_id = $id;if (($id !== null) && isset($this->options['cache_id_prefixe'])) {$nouvel_id = $this->options['cache_id_prefixe'].$id;}return $nouvel_id;}private function executerMethodeStockage($methode, $params = null) {if (method_exists($this->stockage, $methode)) {if ($params == null) {$resultat = call_user_func(array($this->stockage, $methode));} else {$resultat = call_user_func_array(array($this->stockage, $methode), $params);}} else {$resultat = false;trigger_error("La méthode '$methode' n'existe pas dans la classe '".get_class($this)."'.", E_USER_WARNING);}return $resultat;}private function supprimerPrefixe($ids) {// Il est nécessaire de retirer les cache_id_prefixe des ids (voir #ZF-6178, #ZF-7600)if (isset($this->options['cache_id_prefixe']) && $this->options['cache_id_prefixe'] !== '') {$prefixe =& $this->options['cache_id_prefixe'];$prefixe_longueur = strlen($prefixe);foreach ($ids as &$id) {if (strpos($id, $prefixe) === 0) {$id = substr($id, $prefixe_longueur);}}}return $ids;}private function controlerEcriture($id, $donnees_avant_ecriture) {$resultat = true;if ($this->options['controle_ecriture']) {$donnees_apres_ecriture = $this->executerMethodeStockage('charger', array($id, true));if ($donnees_avant_ecriture != $donnees_apres_ecriture) {$this->executerMethodeStockage('supprimer', array($id));$resultat = false;}}return $resultat;}private function deserialiserAutomatiquement($donnees) {if ($donnees !== false && $this->options['serialisation_auto']) {// we need to unserialize before sending the result$donnees = unserialize($donnees);}return $donnees;}private function serialiserAutomatiquement($donnees) {if ($this->options['serialisation_auto']) {// we need to serialize datas before storing them$donnees = serialize($donnees);} else {if (!is_string($donnees)) {trigger_error("Les données doivent être une chaîne de caractères ou vous devez activez l'option serialisation_auto = true", E_USER_WARNING);}}return $donnees;}private function nettoyerAutomatiquement() {if ($this->options['nettoyage_auto'] > 0) {$rand = rand(1, $this->options['nettoyage_auto']);if ($rand == 1) {$this->nettoyer(self::NETTOYAGE_MODE_EXPIRATION);}}}/*** Valide un identifiant de cache ou un tag (securité, nom de fichiers fiables, préfixes réservés...)** @param string $chaine Identificant de cache ou tag* @return void*/protected static function validerIdOuTag($chaine) {if (!is_string($chaine)) {trigger_error('Id ou tag invalide : doit être une chaîne de caractères', E_USER_ERROR);}if (substr($chaine, 0, 9) == 'internal-') {trigger_error('"internal-*" identifiants ou tags sont réservés', E_USER_WARNING);}if (!preg_match('~^[a-zA-Z0-9_]+$~D', $chaine)) {trigger_error("Id ou tag invalide '$chaine' : doit contenir seulement [a-zA-Z0-9_]", E_USER_WARNING);}}/*** Valide un tableau de tags (securité, nom de fichiers fiables, préfixes réservés...)** @param array $tags tableau de tags* @return void*/protected static function validerTableauDeTags($tags) {if (!is_array($tags)) {trigger_error("Tableau de tags invalide : doit être un tableau 'array'", E_USER_WARNING);}foreach ($tags as $tag) {self::validerIdOuTag($tag);}reset($tags);}/*** Calcule et retourne le timestamp d'expiration** @return int timestamp d'expiration (unix timestamp)*/public function getTimestampExpiration($duree_de_vie) {if ($duree_de_vie === false) {if (isset($this->options['duree_de_vie']) && is_int($this->options['duree_de_vie'])) {$duree_de_vie = (int) $this->options['duree_de_vie'];} else {$duree_de_vie = 3600;}}$timestamp = ($duree_de_vie === null) ? 9999999999 : (time() + $duree_de_vie);return $timestamp;}}