Overview

Packages

  • Framework
  • None
  • PHP
  • Utilitaire

Classes

  • Bdd
  • Cache
  • CacheSimple
  • Chronometre
  • Cli
  • Config
  • Controleur
  • Debug
  • Framework
  • GestionnaireException
  • I18n
  • Log
  • Registre
  • RestClient
  • RestServeur
  • RestService
  • Script
  • SquelettePhp
  • Url
  • Overview
  • Package
  • Class
  • Tree
  • Deprecated
  • Todo
  1: <?php
  2: // declare(encoding='UTF-8');
  3: /**
  4:  * Classe Cache permettant de mettre en cache des données.
  5:  * Basée sur les principes de Zend_Cache (Copyright (c) 2005-2010, Zend Technologies USA, Inc. All rights reserved.)
  6:  *
  7:  * @category    php 5.2
  8:  * @package Framework
  9:  * @author      Jean-Pascal MILCENT <jpm@tela-botanica.org>
 10:  * @copyright   Copyright (c) 2010, Tela Botanica (accueil@tela-botanica.org)
 11:  * @license http://framework.zend.com/license/new-bsd Licence New BSD
 12:  * @license http://www.cecill.info/licences/Licence_CeCILL_V2-fr.txt Licence CECILL
 13:  * @license http://www.gnu.org/licenses/gpl.html Licence GNU-GPL
 14:  * @version $Id: Cache.php 299 2011-01-18 14:03:46Z jpm $
 15:  * @link        /doc/framework/
 16:  */
 17: class Cache {
 18:     /** Socke les enregistrements du cache dans des fichiers textes de façon extremement simple. */
 19:     const STOCKAGE_MODE_SIMPLE = "FichierSimple";
 20:     /** Socke les enregistrements du cache dans des fichiers textes. */
 21:     const STOCKAGE_MODE_FICHIER = "Fichier";
 22:     /** Socke les enregistrements du cache dans une base de données SQLite. */
 23:     const STOCKAGE_MODE_SQLITE = "Sqlite";
 24:     
 25:     /** 'tous' (par défaut) : supprime tous les enregistrements. */
 26:     const NETTOYAGE_MODE_TOUS = "tous";
 27:     /** 'expiration' : supprime tous les enregistrements dont la date d'expériration est dépassée. */
 28:     const NETTOYAGE_MODE_EXPIRATION = "expiration";
 29:     /** 'avecLesTags' : supprime tous les enregistrements contenant tous les tags indiqués. */
 30:     const NETTOYAGE_MODE_AVEC_LES_TAGS = "avecLesTags";
 31:     /** 'sansLesTags' : supprime tous les enregistrements contenant aucun des tags indiqués. */
 32:     const NETTOYAGE_MODE_SANS_LES_TAGS = "sansLesTags";
 33:     /** 'avecUnTag' : supprime tous les enregistrements contenant au moins un des tags indiqués. */
 34:     const NETTOYAGE_MODE_AVEC_UN_TAG = "avecUnTag";
 35:     
 36:     /**
 37:      * Dernier identifiant de cache utilisé.
 38:      *
 39:      * @var string $dernier_id
 40:      */
 41:     private $dernier_id = null;
 42:     
 43:     /**
 44:      * Les options disponibles pour le cache :
 45:      * ====> (string) stockage_mode :
 46:      * Indique le mode de stockage du cache à utiliser parmis :
 47:      * - Cache::STOCKAGE_MODE_FICHIER : sous forme d'une arborescence de fichiers et dossier
 48:      * - Cache::STOCKAGE_MODE_SQLITE : sous forme d'une base de données SQLite
 49:      * 
 50:      * ====> (string) stockage_chemin :
 51:      * Chemin vers :
 52:      * - Cache::STOCKAGE_MODE_FICHIER : le dossier devant contenir l'arborescence.
 53:      * - Cache::STOCKAGE_MODE_SQLITE : le fichier contenant la base SQLite.
 54:      * 
 55:      * ====> (boolean) controle_ecriture :
 56:      * - Active / Désactive le controle d'écriture (le cache est lue jute après l'écriture du fichier pour détecter sa corruption)
 57:      * - Activer le controle d'écriture ralentira légèrement l'écriture du fichier de cache mais pas sa lecture
 58:      * Le controle d'écriture peut détecter la corruption de fichier mais ce n'est pas un système de controle parfait.
 59:      *
 60:      * ====> (boolean) mise_en_cache :
 61:      * - Active / Désactive la mise en cache
 62:      * (peut être très utile pour le débogage des scripts utilisant le cache
 63:      *
 64:      * =====> (string) cache_id_prefixe :
 65:      * - préfixe pour les identifiant de cache ( = espace de nom)
 66:      *
 67:      * ====> (boolean) serialisation_auto :
 68:      * - Active / Désactive la sérialisation automatique
 69:      * - Peut être utilisé pour sauver directement des données qui ne sont pas des chaines (mais c'est plus lent)
 70:      *
 71:      * ====> (int) nettoyage_auto :
 72:      * - Désactive / Régler le processus de nettoyage automatique
 73:      * - Le processus de nettoyage automatiques détruit les fichier trop vieux (pour la durée de vie donnée)
 74:      *   quand un nouveau fichier de cache est écrit :
 75:      *   0             => pas de nettoyage automatique
 76:      *   1             => nettoyage automatique systématique
 77:      *   x (integer) > 1 => nettoyage automatique toutes les 1 fois (au hasard) sur x écriture de fichier de cache
 78:      *
 79:      * ====> (int) duree_de_vie :
 80:      * - Durée de vie du cache (en secondes)
 81:      * - Si null, le cache est valide indéfiniment.
 82:      *
 83:      * @var array $options les options disponibles pour le cache .
 84:      */
 85:     protected $options = array(
 86:         'stockage_mode'              => self::STOCKAGE_MODE_FICHIER,
 87:         'stockage_chemin'                => null,   
 88:         'controle_ecriture'          => true,
 89:         'mise_en_cache'              => true,
 90:         'cache_id_prefixe'               => null,
 91:         'serialisation_auto'             => false,
 92:         'nettoyage_auto'                 => 10,
 93:         'duree_de_vie'                   => 3600,
 94:     );
 95:     
 96:     protected $stockage = null;
 97:     
 98:     public function __construct($options = array(), $options_stockage = array()) {
 99:         $this->initialiserOptionsParConfig();
100:         $this->setOptions($options);
101:         if ($this->options['stockage_mode'] == self::STOCKAGE_MODE_FICHIER) {
102:             $this->stockage = new CacheFichier($options_stockage, $this);
103:             $this->stockage->setEmplacement($this->options['stockage_chemin']);
104:         } else if ($this->options['stockage_mode'] == self::STOCKAGE_MODE_SQLITE) {
105:             $this->stockage = new CacheSqlite($options_stockage, $this);
106:             $this->stockage->setEmplacement($this->options['stockage_chemin']);
107:         } else {
108:             trigger_error("Ce mode de stockage n'existe pas ou ne supporte pas la création par le constructeur", E_USER_WARNING);
109:         }
110:     }
111:     
112:     private function initialiserOptionsParConfig() {
113:         while (list($nom, $valeur) = each($this->options)) {
114:             if (Config::existe($nom)) {
115:                 $this->options[$nom] = Config::get($nom);
116:             }
117:         }
118:     }
119:     
120:     private function setOptions($options) {
121:         while (list($nom, $valeur) = each($options)) {
122:             if (!is_string($nom)) {
123:                 trigger_error("Nom d'option incorecte : $nom", E_USER_WARNING);
124:             }
125:             $nom = strtolower($nom);
126:             if (array_key_exists($nom, $this->options)) {
127:                 $this->options[$nom] = $valeur;
128:             }
129:         }
130:     }
131:     
132:     /**
133:      * Permet de (re-)définir l'emplacement pour le stockage du cache.
134:      * En fonction du mode de stockage utilisé , l'emplacement indiqué correspondra au chemin du :
135:      *  - dossier où stocker les fichiers pour le mode "fichier".
136:      *  - fichier de la base de données pour le mode "sqlite". 
137:      * @param string $emplacement chemin vers dossier (Cache::STOCKAGE_MODE_FICHIER) ou fichier base Sqlite (Cache::STOCKAGE_MODE_SQLITE)
138:      * @return void 
139:      */
140:     public function setEmplacement($emplacement) {
141:         if ($emplacement != null) {
142:             $this->executerMethodeStockage('setEmplacement', array($emplacement));
143:         } else {
144:             trigger_error("L'emplacement ne peut pas être null.", E_USER_WARNING);
145:         }
146:     }
147:     
148:     public static function fabriquer($mode, $options = array()) {
149:         if ($mode == self::STOCKAGE_MODE_SIMPLE) {
150:             return new CacheSimple($options);
151:         } else {
152:             trigger_error("Le mode '$mode' de stockage n'existe pas ou ne supporte pas la création par fabrique", E_USER_WARNING);
153:         }
154:         return false;
155:     }
156:     
157:     /**
158:      * Teste si un cache est disponible pour l'identifiant donné et (si oui) le retourne (false dans le cas contraire)
159:      *
160:      * @param  string  $id Identifiant de cache.
161:      * @param  boolean $ne_pas_tester_validiter_du_cache Si mis à true, la validité du cache n'est pas testée
162:      * @return mixed|false Cached datas
163:      */
164:     public function charger($id, $ne_pas_tester_validiter_du_cache = false) {
165:         $donnees = false;
166:         if ($this->options['mise_en_cache'] === true) {
167:             $id = $this->prefixerId($id);
168:             $this->dernier_id = $id;
169:             self::validerIdOuTag($id);
170:             $donnees = $this->executerMethodeStockage('charger', array($id, $ne_pas_tester_validiter_du_cache));
171:             $donnees = $this->deserialiserAutomatiquement($donnees);
172:         }
173:         return $donnees;
174:     }
175:     
176:     /**
177:      * Test if a cache is available for the given id
178:      *
179:      * @param  string $id Cache id
180:      * @return int|false Last modified time of cache entry if it is available, false otherwise
181:      */
182:     public function tester($id) {
183:         $resultat = false;
184:         if ($this->options['mise_en_cache'] === true) {
185:             $id = $this->prefixerId($id);
186:             self::validerIdOuTag($id);
187:             $this->dernier_id = $id;
188:             $resultat = $this->executerMethodeStockage('tester', array($id));
189:         }
190:         return $resultat;
191:     }
192:     
193:     /**
194:      * Sauvegarde en cache les données passées en paramètre.
195:      *
196:      * @param  mixed $donnees Données à mettre en cache (peut être différent d'une chaine si serialisation_auto vaut true).
197:      * @param  string $id    Identifiant du cache (s'il n'est pas définit, le dernier identifiant sera utilisé).
198:      * @param  array $tags Mots-clés du cache.
199:      * @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)
200:      * @return boolean True si aucun problème n'est survenu.
201:      */
202:     public function sauver($donnees, $id = null, $tags = array(), $duree_de_vie_specifique = false) {
203:         $resultat = true;
204:         if ($this->options['mise_en_cache'] === true) {
205:             $id = ($id === null) ? $this->dernier_id : $this->prefixerId($id);
206:     
207:             self::validerIdOuTag($id);
208:             self::validerTableauDeTags($tags);
209:             $donnees = $this->serialiserAutomatiquement($donnees);
210:             $this->nettoyerAutomatiquement();
211:             
212:             $resultat = $this->executerMethodeStockage('sauver', array($donnees, $id, $tags, $duree_de_vie_specifique));
213:             
214:             if ($resultat == false) {
215:                 // Le cache étant peut être corrompu, nous le supprimons
216:                 $this->supprimer($id);
217:             } else {
218:                 $resultat = $this->controlerEcriture($id, $donnees);
219:             }
220:         }
221:         return $resultat;
222:     }
223:     
224:     /**
225:      * Supprime un enregistrement en cache.
226:      *
227:      * @param  string $id Identificant du cache à supprimer.
228:      * @return boolean True si ok
229:      */
230:     public function supprimer($id) {
231:         $resultat = true;
232:         if ($this->options['mise_en_cache'] === true) {
233:             $id = $this->prefixerId($id);
234:             self::validerIdOuTag($id);
235:            $resultat = $this->executerMethodeStockage('supprimer', array($id));
236:         }
237:         return $resultat;
238:     }
239:     
240:     /**
241:      * Nettoyage des enregistrements en cache
242:      * 
243:      * Mode de nettoyage disponibles :
244:      * 'tous' (défaut) => supprime tous les enregistrements ($tags n'est pas utilisé)
245:      * 'expiration'     => supprime tous les enregistrements dont la date d'expériration est dépassée ($tags n'est pas utilisé)
246:      * 'avecLesTag'     => supprime tous les enregistrements contenant tous les tags indiqués
247:      * 'sansLesTag'     => supprime tous les enregistrements contenant aucun des tags indiqués
248:      * 'avecUnTag'          => supprime tous les enregistrements contenant au moins un des tags indiqués
249:      *
250:      * @param string $mode mode de nettoyage
251:      * @param array|string $tags peut être un tableau de chaîne ou une simple chaine.
252:      * @return boolean True si ok
253:      */
254:     public function nettoyer($mode = self::NETTOYAGE_MODE_TOUS, $tags = array()) {
255:         $resultat = true;
256:         if ($this->options['mise_en_cache'] === true) {
257:             if (!in_array($mode, array(Cache::NETTOYAGE_MODE_TOUS,
258:                 Cache::NETTOYAGE_MODE_EXPIRATION,
259:                 Cache::NETTOYAGE_MODE_AVEC_LES_TAGS,
260:                 Cache::NETTOYAGE_MODE_SANS_LES_TAGS,
261:                 Cache::NETTOYAGE_MODE_AVEC_UN_TAG))) {
262:                 trigger_error("Le mode de nettoyage du cache indiqué n'est pas valide", E_USER_WARNING);
263:             }
264:             self::validerTableauDeTags($tags);
265:             
266:             $resultat = $this->executerMethodeStockage('nettoyer', array($mode, $tags));
267:         }
268:         return $resultat;
269:     }
270: 
271:     /**
272:      * Return an array of stored cache ids
273:      *
274:      * @return array array of stored cache ids (string)
275:      */
276:     public function getIds() {
277:         $ids = $this->executerMethodeStockage('getIds');
278:         $ids = $this->supprimerPrefixe($ids);
279:         return $ids;
280:     }
281: 
282:     /**
283:      * Return an array of stored tags
284:      *
285:      * @return array array of stored tags (string)
286:      */
287:     public function getTags() {
288:         return $this->executerMethodeStockage('getTags');
289:     }
290:     
291:     /**
292:      * Return an array of stored cache ids which match given tags
293:      *
294:      * In case of multiple tags, a logical AND is made between tags
295:      *
296:      * @param array $tags array of tags
297:      * @return array array of matching cache ids (string)
298:      */
299:     public function getIdsAvecLesTags($tags = array()) {
300:         $ids = $this->executerMethodeStockage('getIdsAvecLesTags', array($tags));
301:         $ids = $this->supprimerPrefixe($ids);
302:         return $ids;
303:     }
304: 
305:     /**
306:      * Return an array of stored cache ids which don't match given tags
307:      *
308:      * In case of multiple tags, a logical OR is made between tags
309:      *
310:      * @param array $tags array of tags
311:      * @return array array of not matching cache ids (string)
312:      */
313:     public function getIdsSansLesTags($tags = array()) {
314:         $ids = $this->executerMethodeStockage('getIdsSansLesTags', array($tags));
315:         $ids = $this->supprimerPrefixe($ids);
316:         return $ids;
317:     }
318: 
319:     /**
320:      * Return an array of stored cache ids which match any given tags
321:      *
322:      * In case of multiple tags, a logical OR is made between tags
323:      *
324:      * @param array $tags array of tags
325:      * @return array array of matching any cache ids (string)
326:      */
327:     public function getIdsAvecUnTag($tags = array()) {
328:         $ids = $this->executerMethodeStockage('getIdsAvecUnTag', array($tags));
329:         $ids = $this->supprimerPrefixe($ids);
330:         return $ids;
331:     }
332: 
333:     /**
334:      * Return the filling percentage of the backend storage
335:      *
336:      * @return int integer between 0 and 100
337:      */
338:     public function getPourcentageRemplissage() {
339:         return $this->executerMethodeStockage('getPourcentageRemplissage');
340:     }
341: 
342:     /**
343:      * Return an array of metadatas for the given cache id
344:      *
345:      * The array will include these keys :
346:      * - expire : the expire timestamp
347:      * - tags : a string array of tags
348:      * - mtime : timestamp of last modification time
349:      *
350:      * @param string $id cache id
351:      * @return array array of metadatas (false if the cache id is not found)
352:      */
353:     public function getMetadonnees($id) {
354:         $id = $this->prefixerId($id);
355:         return $this->executerMethodeStockage('getMetadonnees', array($id));
356:     }
357: 
358:     /**
359:      * Give (if possible) an extra lifetime to the given cache id
360:      *
361:      * @param string $id cache id
362:      * @param int $extraLifetime
363:      * @return boolean true if ok
364:      */
365:     public function ajouterSupplementDureeDeVie($id, $supplement_duree_de_vie) {
366:         $id = $this->prefixerId($id);
367:         return $this->executerMethodeStockage('ajouterSupplementDureeDeVie', array($id, $supplement_duree_de_vie));
368:     }
369:     
370: 
371:     /**
372:      * Fabrique et retourne l'identifiant du cache avec son préfixe.
373:      *
374:      * 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.
375:      *
376:      * @param  string $id Identifiant du cache.
377:      * @return string Identifiant du cache avec ou sans préfixe.
378:      */
379:     private function prefixerId($id) {
380:         $nouvel_id = $id;
381:         if (($id !== null) && isset($this->options['cache_id_prefixe'])) {
382:             $nouvel_id = $this->options['cache_id_prefixe'].$id;
383:         }
384:         return $nouvel_id;
385:     }
386:     
387:     private function executerMethodeStockage($methode, $params = null) {
388:         if (method_exists($this->stockage, $methode)) {
389:             if ($params == null) {
390:                 $resultat = call_user_func(array($this->stockage, $methode));
391:             } else {
392:                 $resultat = call_user_func_array(array($this->stockage, $methode), $params);
393:             }
394:         } else {
395:             $resultat = false;
396:             trigger_error("La méthode '$methode' n'existe pas dans la classe '".get_class($this)."'.", E_USER_WARNING);
397:         }
398:         return $resultat;
399:     }
400:     
401:     private function supprimerPrefixe($ids) {
402:         // Il est nécessaire de retirer les cache_id_prefixe des ids (voir #ZF-6178, #ZF-7600)
403:         if (isset($this->options['cache_id_prefixe']) && $this->options['cache_id_prefixe'] !== '') {
404:             $prefixe =& $this->options['cache_id_prefixe'];
405:             $prefixe_longueur = strlen($prefixe);
406:             foreach ($ids as &$id) {
407:                 if (strpos($id, $prefixe) === 0) {
408:                     $id = substr($id, $prefixe_longueur);
409:                 }
410:             }
411:         }
412:         return $ids;
413:     }
414:     
415:     private function controlerEcriture($id, $donnees_avant_ecriture) {
416:         $resultat = true;
417:         if ($this->options['controle_ecriture']) {
418:             $donnees_apres_ecriture = $this->executerMethodeStockage('charger', array($id, true));
419:             if ($donnees_avant_ecriture != $donnees_apres_ecriture) {
420:                 $this->executerMethodeStockage('supprimer', array($id));
421:                 $resultat = false;
422:             }
423:         }
424:         return $resultat;
425:     }
426:     
427:     private function deserialiserAutomatiquement($donnees) {
428:         if ($donnees !== false && $this->options['serialisation_auto']) {
429:                 // we need to unserialize before sending the result
430:                 $donnees = unserialize($donnees);
431:         }
432:         return $donnees;
433:     }
434:     
435:     private function serialiserAutomatiquement($donnees) {
436:         if ($this->options['serialisation_auto']) {
437:             // we need to serialize datas before storing them
438:             $donnees = serialize($donnees);
439:         } else {
440:             if (!is_string($donnees)) {
441:                 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);
442:             }
443:         }
444:         return $donnees;
445:     }
446:     
447:     private function nettoyerAutomatiquement() {
448:         if ($this->options['nettoyage_auto'] > 0) {
449:             $rand = rand(1, $this->options['nettoyage_auto']);
450:             if ($rand == 1) {
451:                 $this->nettoyer(self::NETTOYAGE_MODE_EXPIRATION);
452:             }
453:         }
454:     }
455:     
456:     /**
457:      * Valide un identifiant de cache ou un tag (securité, nom de fichiers fiables, préfixes réservés...)
458:      *
459:      * @param  string $chaine Identificant de cache ou tag
460:      * @return void
461:      */
462:     protected static function validerIdOuTag($chaine) {
463:         if (!is_string($chaine)) {
464:             trigger_error('Id ou tag invalide : doit être une chaîne de caractères', E_USER_ERROR);
465:         }
466:         if (substr($chaine, 0, 9) == 'internal-') {
467:             trigger_error('"internal-*" identifiants ou tags sont réservés', E_USER_WARNING);
468:         }
469:         if (!preg_match('~^[a-zA-Z0-9_]+$~D', $chaine)) {
470:             trigger_error("Id ou tag invalide '$chaine' : doit contenir seulement [a-zA-Z0-9_]", E_USER_WARNING);
471:         }
472:     }
473: 
474:     /**
475:      * Valide un tableau de tags  (securité, nom de fichiers fiables, préfixes réservés...)
476:      *
477:      * @param  array $tags tableau de tags
478:      * @return void
479:      */
480:     protected static function validerTableauDeTags($tags) {
481:         if (!is_array($tags)) {
482:             trigger_error("Tableau de tags invalide : doit être un tableau 'array'", E_USER_WARNING);
483:         }
484:         foreach ($tags as $tag) {
485:             self::validerIdOuTag($tag);
486:         }
487:         reset($tags);
488:     }
489:     
490:     /**
491:      * Calcule et retourne le timestamp d'expiration
492:      *
493:      * @return int timestamp d'expiration (unix timestamp)
494:      */
495:     public function getTimestampExpiration($duree_de_vie) {
496:         if ($duree_de_vie === false) {
497:             if (isset($this->options['duree_de_vie']) && is_int($this->options['duree_de_vie'])) {
498:                 $duree_de_vie = (int) $this->options['duree_de_vie'];
499:             } else {
500:                 $duree_de_vie = 3600;
501:             }
502:         }
503:         $timestamp = ($duree_de_vie === null) ? 9999999999 : (time() + $duree_de_vie);
504:         return $timestamp;
505:     }
506:     
507: }
TBFramework - v0.3 API documentation generated by ApiGen 2.8.0