1: <?php
2: class CacheSqlite {
3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19:
20: protected $options = array(
21: 'stockage_chemin' => null,
22: 'defragmentation_auto' => 10
23: );
24:
25: 26: 27: 28: 29:
30: private $bdd = null;
31:
32: 33: 34: 35: 36:
37: private $structure_ok = false;
38:
39: private $Cache = null;
40:
41: 42: 43: 44: 45: 46: 47:
48: public function __construct(array $options = array(), Cache $cache) {
49: $this->Cache = $cache;
50: if (extension_loaded('sqlite')) {
51: $this->initialiserOptionsParConfig();
52: $this->setOptions($options);
53: } else {
54: $e = "Impossible d'utiliser le cache SQLITE car l'extenssion 'sqlite' n'est pas chargée dans l'environnement PHP courrant.";
55: trigger_error($e, E_USER_ERROR);
56: }
57: }
58:
59: private function initialiserOptionsParConfig() {
60: while (list($nom, $valeur) = each($this->options)) {
61: if (Config::existe($nom)) {
62: $this->options[$nom] = Config::get($nom);
63: }
64: }
65: }
66:
67: 68: 69: 70: 71:
72: public function __destruct() {
73: @sqlite_close($this->getConnexion());
74: }
75:
76: private function setOptions($options) {
77: while (list($nom, $valeur) = each($options)) {
78: if (!is_string($nom)) {
79: trigger_error("Nom d'option incorecte : $nom", E_USER_WARNING);
80: }
81: $nom = strtolower($nom);
82: if (array_key_exists($nom, $this->options)) {
83: $this->options[$nom] = $valeur;
84: }
85: }
86: }
87:
88: public function setEmplacement($emplacement) {
89: if (extension_loaded('sqlite')) {
90: $this->options['stockage_chemin'] = $emplacement;
91: } else {
92: trigger_error("Impossible d'utiliser le mode de sotckage SQLite car l'extenssion 'sqlite' n'est pas chargé dans ".
93: "l'environnement PHP courrant.", E_USER_ERROR);
94: }
95: }
96:
97: 98: 99: 100: 101: 102: 103:
104: public function charger($id, $ne_pas_tester_validiter_du_cache = false) {
105: $this->verifierEtCreerStructureBdd();
106: $requete = "SELECT content FROM cache WHERE id = '$id'".
107: (($ne_pas_tester_validiter_du_cache) ? '' : ' AND (expire = 0 OR expire > '.time().')');
108: $resultat = $this->requeter($requete);
109: $ligne = @sqlite_fetch_array($resultat);
110: return ($ligne) ? $ligne['content'] : false;
111: }
112:
113: 114: 115: 116: 117: 118:
119: public function tester($id) {
120: $this->verifierEtCreerStructureBdd();
121: $requete = "SELECT lastModified FROM cache WHERE id = '$id' AND (expire = 0 OR expire > ".time().')';
122: $resultat = $this->requeter($requete);
123: $ligne = @sqlite_fetch_array($resultat);
124: return ($ligne) ? ((int) $ligne['lastModified']) : false;
125: }
126:
127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139:
140: public function sauver($donnees, $id, $tags = array(), $duree_vie_specifique = false) {
141: $this->verifierEtCreerStructureBdd();
142:
143:
144: $donnees = @sqlite_escape_string($donnees);
145: $timestamp_courrant = time();
146: $expiration = $this->Cache->getTimestampExpiration($duree_vie_specifique);
147:
148: $this->requeter("DELETE FROM cache WHERE id = '$id'");
149: $sql = "INSERT INTO cache (id, content, lastModified, expire) VALUES ('$id', '$donnees', $timestamp_courrant, $expiration)";
150: $resultat = $this->requeter($sql);
151: if (!$resultat) {
152:
153: Debug::printr("sauver() : impossible de stocker le cache d'id '$id'");
154: $resultat = false;
155: } else {
156: $resultat = true;
157: foreach ($tags as $tag) {
158: $resultat = $this->enregisterTag($id, $tag) && $resultat;
159: }
160: }
161: return $resultat;
162: }
163:
164: 165: 166: 167: 168: 169:
170: public function supprimer($id) {
171: $this->verifierEtCreerStructureBdd();
172: $resultat = $this->requeter("SELECT COUNT(*) AS nbr FROM cache WHERE id = '$id'");
173: $resultat_nbre = @sqlite_fetch_single($resultat);
174: $suppression_cache = $this->requeter("DELETE FROM cache WHERE id = '$id'");
175: $suppression_tags = $this->requeter("DELETE FROM tag WHERE id = '$id'");
176: $this->defragmenterAutomatiquement();
177: return ($resultat_nbre && $suppression_cache && $suppression_tags);
178: }
179:
180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196:
197: public function nettoyer($mode = Cache::NETTOYAGE_MODE_TOUS, $tags = array()) {
198: $this->verifierEtCreerStructureBdd();
199: $retour = $this->nettoyerSqlite($mode, $tags);
200: $this->defragmenterAutomatiquement();
201: return $retour;
202: }
203:
204: 205: 206: 207: 208:
209: public function getIds() {
210: $this->verifierEtCreerStructureBdd();
211: $resultat = $this->requeter('SELECT id FROM cache WHERE (expire = 0 OR expire > '.time().')');
212: $retour = array();
213: while ($id = @sqlite_fetch_single($resultat)) {
214: $retour[] = $id;
215: }
216: return $retour;
217: }
218:
219: 220: 221: 222: 223:
224: public function getTags() {
225: $this->verifierEtCreerStructureBdd();
226: $resultat = $this->requeter('SELECT DISTINCT(name) AS name FROM tag');
227: $retour = array();
228: while ($id = @sqlite_fetch_single($resultat)) {
229: $retour[] = $id;
230: }
231: return $retour;
232: }
233:
234: 235: 236: 237: 238: 239: 240: 241:
242: public function getIdsAvecLesTags($tags = array()) {
243: $this->verifierEtCreerStructureBdd();
244: $premier = true;
245: $ids = array();
246: foreach ($tags as $tag) {
247: $resultat = $this->requeter("SELECT DISTINCT(id) AS id FROM tag WHERE name='$tag'");
248: if ($resultat) {
249: $lignes = @sqlite_fetch_all($resultat, SQLITE_ASSOC);
250: $ids_tmp = array();
251: foreach ($lignes as $ligne) {
252: $ids_tmp[] = $ligne['id'];
253: }
254: if ($premier) {
255: $ids = $ids_tmp;
256: $premier = false;
257: } else {
258: $ids = array_intersect($ids, $ids_tmp);
259: }
260: }
261: }
262:
263: $retour = array();
264: if (count($ids) > 0) {
265: foreach ($ids as $id) {
266: $retour[] = $id;
267: }
268: }
269: return $retour;
270: }
271:
272: 273: 274: 275: 276: 277: 278: 279:
280: public function getIdsSansLesTags($tags = array()) {
281: $this->verifierEtCreerStructureBdd();
282: $resultat = $this->requeter('SELECT id FROM cache');
283: $lignes = @sqlite_fetch_all($resultat, SQLITE_ASSOC);
284: $retour = array();
285: foreach ($lignes as $ligne) {
286: $id = $ligne['id'];
287: $correspondance = false;
288: foreach ($tags as $tag) {
289: $resultat = $this->requeter("SELECT COUNT(*) AS nbr FROM tag WHERE name = '$tag' AND id = '$id'");
290: if ($resultat) {
291: $nbre = (int) @sqlite_fetch_single($resultat);
292: if ($nbre > 0) {
293: $correspondance = true;
294: }
295: }
296: }
297: if (!$correspondance) {
298: $retour[] = $id;
299: }
300: }
301: return $retour;
302: }
303:
304: 305: 306: 307: 308: 309: 310: 311:
312: public function getIdsAvecUnTag($tags = array()) {
313: $this->verifierEtCreerStructureBdd();
314: $premier = true;
315: $ids = array();
316: foreach ($tags as $tag) {
317: $resultat = $this->requeter("SELECT DISTINCT(id) AS id FROM tag WHERE name = '$tag'");
318: if ($resultat) {
319: $lignes = @sqlite_fetch_all($resultat, SQLITE_ASSOC);
320: $ids_tmp = array();
321: foreach ($lignes as $ligne) {
322: $ids_tmp[] = $ligne['id'];
323: }
324: if ($premier) {
325: $ids = $ids_tmp;
326: $premier = false;
327: } else {
328: $ids = array_merge($ids, $ids_tmp);
329: }
330: }
331: }
332:
333: $retour = array();
334: if (count($ids) > 0) {
335: foreach ($ids as $id) {
336: $retour[] = $id;
337: }
338: }
339: return $retour;
340: }
341:
342: 343: 344: 345: 346: 347:
348: public function getPourcentageRemplissage() {
349: $dossier = dirname($this->options['stockage_chemin']);
350: $libre = disk_free_space($dossier);
351: $total = disk_total_space($dossier);
352:
353: $pourcentage = 0;
354: if ($total == 0) {
355: trigger_error("Impossible d'utiliser la fonction disk_total_space", E_USER_WARNING);
356: } else {
357: $pourcentage = ($libre >= $total) ? 100 : ((int) (100. * ($total - $libre) / $total));
358: }
359: return $pourcentage;
360: }
361:
362: 363: 364: 365: 366: 367: 368: 369: 370: 371: 372:
373: public function getMetadonnees($id) {
374: $this->verifierEtCreerStructureBdd();
375: $tags = array();
376: $resultat = $this->requeter("SELECT name FROM tag WHERE id = '$id'");
377: if ($resultat) {
378: $lignes = @sqlite_fetch_all($resultat, SQLITE_ASSOC);
379: foreach ($lignes as $ligne) {
380: $tags[] = $ligne['name'];
381: }
382: }
383: $resultat = $this->requeter("SELECT lastModified, expire FROM cache WHERE id = '$id'");
384: if ($resultat) {
385: $ligne = @sqlite_fetch_array($resultat, SQLITE_ASSOC);
386: $resultat = array(
387: 'tags' => $tags,
388: 'mtime' => $ligne['lastModified'],
389: 'expiration' => $ligne['expire']);
390: } else {
391: $resultat = false;
392: }
393: return $resultat;
394: }
395:
396: 397: 398: 399: 400: 401: 402:
403: public function ajouterSupplementDureeDeVie($id, $supplement_duree_de_vie) {
404: $this->verifierEtCreerStructureBdd();
405: $augmentation = false;
406: $requete = "SELECT expire FROM cache WHERE id = '$id' AND (expire = 0 OR expire > ".time().')';
407: $resultat = $this->requeter($requete);
408: if ($resultat) {
409: $expiration = @sqlite_fetch_single($resultat);
410: $nouvelle_expiration = $expiration + $supplement_duree_de_vie;
411: $resultat = $this->requeter('UPDATE cache SET lastModified = '.time().", expire = $nouvelle_expiration WHERE id = '$id'");
412: $augmentation = ($resultat) ? true : false;
413: }
414: return $augmentation;
415: }
416:
417: 418: 419: 420: 421: 422: 423: 424:
425: private function getConnexion() {
426: if (!is_resource($this->bdd)) {
427: if ($this->options['stockage_chemin'] === null) {
428: $e = "L'emplacement du chemin vers le fichier de la base de données SQLite n'a pas été défini";
429: trigger_error($e, E_USER_ERROR);
430: } else {
431: $this->bdd = sqlite_open($this->options['stockage_chemin']);
432: if (!(is_resource($this->bdd))) {
433: $e = "Impossible d'ouvrir le fichier '".$this->options['stockage_chemin']."' de la base de données SQLite.";
434: trigger_error($e, E_USER_ERROR);
435: $this->bdd = null;
436: }
437: }
438: }
439: return $this->bdd;
440: }
441:
442: 443: 444: 445: 446: 447:
448: private function requeter($requete) {
449: $bdd = $this->getConnexion();
450:
451: $resultat = (is_resource($bdd)) ? @sqlite_query($bdd, $requete, SQLITE_ASSOC, $e_sqlite) : false;
452: if (is_resource($bdd) && ! $resultat) {
453: Debug::printr("Erreur SQLITE :\n$e_sqlite\nPour la requête :\n$requete\nRessource : $bdd");
454: }
455: return $resultat;
456: }
457:
458: 459: 460: 461: 462:
463: private function defragmenterAutomatiquement() {
464: if ($this->options['defragmentation_auto'] > 0) {
465: $rand = rand(1, $this->options['defragmentation_auto']);
466: if ($rand == 1) {
467: $this->requeter('VACUUM');
468: @sqlite_close($this->getConnexion());
469: }
470: }
471: }
472:
473: 474: 475: 476: 477: 478: 479:
480: private function enregisterTag($id, $tag) {
481: $requete_suppression = "DELETE FROM tag WHERE name = '$tag' AND id = '$id'";
482: $resultat = $this->requeter($requete_suppression);
483: $requete_insertion = "INSERT INTO tag(name,id) VALUES ('$tag','$id')";
484: $resultat = $this->requeter($requete_insertion);
485: if (!$resultat) {
486:
487: Debug::printr("Impossible d'enregistrer le tag=$tag pour le cache id=$id");
488: }
489: return ($resultat) ? true : false;
490: }
491:
492: 493: 494: 495: 496:
497: private function creerStructure() {
498: $this->requeter('DROP INDEX IF EXISTS tag_id_index');
499: $this->requeter('DROP INDEX IF EXISTS tag_name_index');
500: $this->requeter('DROP INDEX IF EXISTS cache_id_expire_index');
501: $this->requeter('DROP TABLE IF EXISTS version');
502: $this->requeter('DROP TABLE IF EXISTS cache');
503: $this->requeter('DROP TABLE IF EXISTS tag');
504: $this->requeter('CREATE TABLE version (num INTEGER PRIMARY KEY)');
505: $this->requeter('CREATE TABLE cache(id TEXT PRIMARY KEY, content BLOB, lastModified INTEGER, expire INTEGER)');
506: $this->requeter('CREATE TABLE tag (name TEXT, id TEXT)');
507: $this->requeter('CREATE INDEX tag_id_index ON tag(id)');
508: $this->requeter('CREATE INDEX tag_name_index ON tag(name)');
509: $this->requeter('CREATE INDEX cache_id_expire_index ON cache(id, expire)');
510: $this->requeter('INSERT INTO version (num) VALUES (1)');
511: }
512:
513: 514: 515: 516: 517:
518: private function verifierBddStructureVersion() {
519: $version_ok = false;
520: $resultat = $this->requeter('SELECT num FROM version');
521: if ($resultat) {
522: $ligne = @sqlite_fetch_array($resultat);
523: if ($ligne) {
524: if (((int) $ligne['num']) == 1) {
525: $version_ok = true;
526: } else {
527:
528: }
529: }
530: }
531: return $version_ok;
532: }
533:
534: 535: 536: 537: 538: 539: 540: 541: 542: 543: 544: 545: 546: 547: 548: 549: 550:
551: private function nettoyerSqlite($mode = Cache::NETTOYAGE_MODE_TOUS, $tags = array()) {
552: $nettoyage_ok = false;
553: switch ($mode) {
554: case Cache::NETTOYAGE_MODE_TOUS:
555: $suppression_cache = $this->requeter('DELETE FROM cache');
556: $suppression_tag = $this->requeter('DELETE FROM tag');
557: $nettoyage_ok = $suppression_cache && $suppression_tag;
558: break;
559: case Cache::NETTOYAGE_MODE_EXPIRATION:
560: $mktime = time();
561: $suppression_tag = $this->requeter("DELETE FROM tag WHERE id IN (SELECT id FROM cache WHERE expire > 0 AND expire <= $mktime)");
562: $suppression_cache = $this->requeter("DELETE FROM cache WHERE expire > 0 AND expire <= $mktime");
563: return $suppression_tag && $suppression_cache;
564: break;
565: case Cache::NETTOYAGE_MODE_AVEC_LES_TAGS:
566: $ids = $this->getIdsAvecLesTags($tags);
567: $resultat = true;
568: foreach ($ids as $id) {
569: $resultat = $this->supprimer($id) && $resultat;
570: }
571: return $resultat;
572: break;
573: case Cache::NETTOYAGE_MODE_SANS_LES_TAGS:
574: $ids = $this->getIdsSansLesTags($tags);
575: $resultat = true;
576: foreach ($ids as $id) {
577: $resultat = $this->supprimer($id) && $resultat;
578: }
579: return $resultat;
580: break;
581: case Cache::NETTOYAGE_MODE_AVEC_UN_TAG:
582: $ids = $this->getIdsAvecUnTag($tags);
583: $resultat = true;
584: foreach ($ids as $id) {
585: $resultat = $this->supprimer($id) && $resultat;
586: }
587: return $resultat;
588: break;
589: default:
590: break;
591: }
592: return $nettoyage_ok;
593: }
594:
595: 596: 597: 598: 599: 600:
601: private function verifierEtCreerStructureBdd() {
602: if (! $this->structure_ok) {
603: if (! $this->verifierBddStructureVersion()) {
604: $this->creerStructure();
605: if (! $this->verifierBddStructureVersion()) {
606: $e = "Impossible de construire la base de données de cache dans ".$this->options['stockage_chemin'];
607: trigger_error($e, E_USER_WARNING);
608: $this->structure_ok = false;
609: }
610: }
611: $this->structure_ok = true;
612: }
613: return $this->structure_ok;
614: }
615:
616: }
617: ?>