[PHP5] LambdaCache : Système de mise en cache

Eléphant du PHP | 199 Messages

27 mai 2007, 14:04

Je me permets de poster le code d'une classe de mise en cache que j'ai récemment codée dans le cadre d'un ensemble de classes plus complet que je compte mettre en place dans les semaines à venir pour faciliter mes travaux.

La classe propose une compression des fichiers de cache via la fonction gzcompress.

Edit : La gestion du temps de validité du cache serveur est ajoutée. Une classe pour gérer le cache client (via headers HTTP) est ajoutée aussi.

Exemple d'utilisation :
<?php
//On initialise le cache client
$cacheHTTP = new LambdaHTTPCache();

//On va afficher une page qui varie selon l'utilisateur, on initialise donc le cache serveur propre à l'utilisateur
$cache = new LambdaCache('cache_'.$_SESSION['username'].'.php');

//On indique au cache client que la page envoyée sera personnelle (pour les proxies)
$cacheHTTP -> setProxy(TRUE, FALSE);

//On indique au cache client la date de dernière modification du fichier de cache serveur
$cacheHTTP -> checkTime($cache -> lastModified());

//On envoie les headers HTTP
$cacheHTTP -> sendHeaders();

//On recalcule la page si la méthode show() ne renvoit rien (sinon elle aura affiché le contenu du cache serveur)
if (!$cache -> show())
{
  echo 'Blablabla...';
  $cache -> close(); //On écrit le fichier cache et on affiche le résultat
}
?>

Code source des deux classes :
<?php
	/*
		Lambda System - by Klomac ([email protected])

		LambdaCache : This class allows the user to call a cache file or to create it if it doesn't exist yet.
		LambdaHTTPCache : This class send the appropriate HTTP headers to use the client cache.

		-----------------------------GNU GPL--------------------------------------
		This program is free software; you can redistribute it and/or modify
		it under the terms of the GNU General Public License as published by
		the Free Software Foundation; either version 2 of the License, or
		(at your option) any later version.

		This program is distributed in the hope that it will be useful, but
		WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
		or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
		for more details.

		You should have received a copy of the GNU General Public License along
		with this program; if not, write to the Free Software Foundation, Inc.,
		51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
		---------------------------------------------------------------------------
	*/

	class LambdaCache
	{
		private $cacheFile; //The cache filename
		private $cachePath; //The cache full path
		private $compressData; //Define if the gz compression must be used or not
		private $cacheContents; //The content of the cache file
		private $cacheExists = FALSE; //Indicate if the cache file exists or not
		private $timeLimit;

		//The errors which may be displayed
		private $errors = array(
			'Unable to write the cache file',
			'Unable to create the cache directory');

		public static $cacheDir; //The directory which contains the cache files (must be set by the user)
		public $gzLevel = 1; //The level of the gz compression (better is 1, worst is 9)


		//Magical methods
		public function __construct ($filename, $timeLimit = FALSE, $cacheDir = 'cache/', $compression = TRUE)
		{
			//We set up the main variables
			$this -> cacheFile = $filename;
			$this -> cacheDir = $cacheDir;
			$this -> cachePath = $cacheDir.$filename;
			$this -> timeLimit = is_int($timeLimit) ? $timeLimit : time();

			//We check if the directories-tree exists, and create it if it doesn't
			$string = '';
			$cacheDir = explode('/', $cacheDir);

			foreach ($cacheDir AS $dir)
			{
				if (is_dir($string.$dir) OR @mkdir($string.$dir)) $string .= $dir.'/';
				else die ($this -> errors[1]);
			}

			//We set up the compression level
			if ($compression === TRUE) $this -> compressData = TRUE;
			elseif (is_int($compression) AND $compression >= 1 AND $compression <= 9)
			{
				$this -> compressData = TRUE;
				$this -> gzLevel = $compression;
			}
			else $this -> compressData = FALSE;

			//We check if the cache file exists, and start a buffer if it doesn't
			if (is_readable($this -> cachePath) AND filemtime($this -> cachePath) > (time() - $this -> timeLimit))
			{
				$this -> cacheExists = TRUE;

				if ($this -> compressData)
				{
					$this -> cacheContents = gzuncompress(file_get_contents($this -> cachePath));
				}
				else
				{
					$this -> cacheContents = file_get_contents($this -> cachePath);
				}
			}
			else
			{
				ob_start();
			}
		}


		//Static methods
		public static function clear ($filename, $dir = NULL) //This static method will try to destroy the cache file if it exists
		{
			$dir = $dir ? $dir : self::$cacheDir;

			if (file_exists($dir.$filename)) unlink($dir.$filename);
		}


		//Public methods
		public function lastModified () //This method returns the last-modified timestamp of the cache file
		{
			if ($this -> cacheExists) return filemtime($this -> cachePath);
			else return time();
		}
		
		public function show ($print = TRUE) //This method prints (or returns) the cache contents if it exists, or return false
		{
			if ($this -> cacheExists)
			{
				if ($print == TRUE)
				{
					print $this -> cacheContents;
					return TRUE;
				}
				else return $this -> cacheContents;
			}
			else
			{
				return FALSE;
			}
		}

		public function close () //This method write the cache file and print the contents
		{
			$contents = ob_get_clean();

			if ($this -> compressData)
			{
				$cacheContents = gzcompress($contents, $this -> gzLevel);
			}
			else
			{
				$cacheContents = $contents;
			}

			if ($file = @fopen($this -> cachePath, 'w'))
			{
				fwrite($file, $cacheContents);
				fclose($file);
			}
			else die ($this -> errors[0]);

			print $contents;
		}
	}
	
	class LambdaHTTPCache
	{
		private $lastModified; //The last-modified time of the document
		private $proxy = 'Public'; //The proxy option (public or private)
		private $userAgent = FALSE;
		
		//The errors which may be displayed
		private $errors = array (
			'The $time parameter must be an integer');
		
		//Public methods
		public function __construct () { }
		
		public function checkTime ($time) //This method checks if this time is bigger than the last-modified time
		{
			if (!is_int($time)) die ($this -> errors[0]);
			elseif ($time > $this -> lastModified) $this -> lastModified = $time;
		}
		
		public function setProxy ($private, $userAgent) //This method adds some parameters to use client proxies
		{
			if ($private == TRUE) $this -> proxy = 'Private';
			if ($userAgent == TRUE) $this -> userAgent = TRUE;
		}
		
		public function sendHeaders () //This method sends the HTTP headers
		{
			$serverDate = gmdate('D, d M Y H:i:s', time());
			$lastModifiedDate = gmdate('D, d M Y H:i:s', $this -> lastModified);
			
			header('Date: '.$serverDate.' GMT');
			header('Last-Modified: '.$lastModifiedDate.' GMT');
			if ($this -> userAgent == TRUE) header ('Vary: User-Agent');
			header('Cache-Control: '.$this -> proxy.', must-revalidate');
			header('Pragma: '.strtolower($this -> proxy));
			
			$requestDate = substr(@$_SERVER['HTTP_IF_MODIFIED_SINCE'],0,29);
			
			if ($requestDate != '' AND strtotime($requestDate) == $this -> lastModified)
			{
				header($_SERVER['SERVER_PROTOCOL'] . ' 304 Not Modified', TRUE, 304);
				exit;
			}
		}
	}
Modifié en dernier par Klomac le 06 juin 2007, 22:37, modifié 1 fois.
Klomac - Blog Lambda

jed
Eléphant du PHP | 218 Messages

27 mai 2007, 14:55

Salut, existe t'il une version php4?

Eléphant du PHP | 199 Messages

27 mai 2007, 15:20

Je ne l'ai pas codée mais la classe n'utilise pas de nouveautés spécifiques à PHP5 donc suffit de modifier la "grammaire" de la classe pour qu'elle soit compatible à PHP4 (virer les public, private etc...).
Klomac - Blog Lambda

ViPHP
fab
ViPHP | 2657 Messages

05 juin 2007, 15:09

c'est moi ou il n'existe pas fonction dans ta classe permettant d'écrire un fichier cache?
Seul l'intelligent a le pouvoir de se trouver con
try { work(); } catch(FlemmeExeption $e) { sleep(84600); }

Eléphant du PHP | 199 Messages

05 juin 2007, 18:36

Le principe est le suivant :

- On crée un objet qui représentera un fichier de cache (et donc un élément de contenu)
- On vérifie si la méthode show() retourne quelque chose, si c'est le cas, ça veut dire que le fichier de cache existe et que la méthode aura écrit le contenu du fichier de cache (à moins qu'on passe le paramètre $print à FALSE
- Si ce n'est pas le cas ça veut dire que le fichier de cache n'existe pas, donc on traite la portion de code concernée par ce fichier et on appelle la méthode close() qui stocke le résultat de ces calculs dans le fichier de cache (et qui l'affiche par la même occasion).

Donc pour répondre à ta question, c'est la méthode close() qui écrit le contenu du fichier.

PS : Je suis en train d'améliorer un peu cette classe, j'éditerai quand j'aurai terminé.
Klomac - Blog Lambda

Administrateur PHPfrance
Administrateur PHPfrance | 3131 Messages

05 juin 2007, 22:52

Attention, telle qu'elle est cette classe ne peut être utilisée pour mettre en cache une page résultat d'un formulaire dynamique, ou un morceau dépendant de la session (par exemple si on met en cache une page avec «hello $_SESSION['username']», tous les utilisateurs verront le même nom d'utilisateur s'afficher : celui du premier qui a généré le cache).

Eléphant du PHP | 199 Messages

05 juin 2007, 22:58

Oui, cette classe est faite pour mettre en cache des données semi-statiques (genre ce qui est stocké en DB).
Klomac - Blog Lambda

Eléphant du PHP | 199 Messages

06 juin 2007, 10:01

Enfin cela dit je nuance le propos : la classe peut tout à fait gérer du contenu dynamique pour peu qu'on passe les paramètres variables dans le nom du fichier de cache.

Genre là il faudrait créer un objet : new LambdaCache('test'.$_SESSION['username'].'.php')
Klomac - Blog Lambda

Eléphant du PHP | 199 Messages

06 juin 2007, 22:38

J'ai mis à jour le code (cf. le premier message que j'ai édité).

J'ai ajouté ma classe de gestion du cache client, j'aimerais avoir votre avis sur tout ça, si possible :)
Klomac - Blog Lambda

Petit nouveau ! | 2 Messages

01 sept. 2007, 02:56

Bonjour,
Le probléme encore une fois dans ta classe c'est que le cache ne sera pas éfficace.

Imaginons un site avec 600 utilisateurs, il va donc y avoir 600 fichiers par utilisateur, c'est énorme, maintenant admettons qu'il y ait 500 000 utilisateurs avec une moyenne de 3ko par page HTML, sa ferait 1.5go de cache pour les utilisateurs donc ensuite comme tu inclus ta session, toute les pages seront stocker avec la session donc 3ko*2 et donc 3go pour les utilisateurs.

De plus, généralement les sites que les développeurs PHP font sont généralement destiné a avoir plusieurs variable de sessions, et des variables de sessions dynamique genre $_SESSION['last_login'] qui va changer a chaque fois que l'utilisateur va se logguer, mais pourtant dans le cache il sera marqué comme le premier login.

Personnelement, je suis en train de faire un CMS qui inclus près de 15 variables de sessions pour un utilisateur et qui change tout le temps. Donc je ne pourrais pas utiliser ceci sous peine de prendre énormément d'espace sur le disque dur de l'utilisateur.

Enfin bref, dans l'ensemble je trouve que ta classe est très bonne.

Cependant, j'ai un petit probléme, j'utilise des formulaire dynamique (géré par PHP), mon probléme est que lorsque l'utilisateur rempli le formulaire dynamique les données ne soit pas envoyé sur la base de donnée. Encore une fois je ne peut pas trop faire un systéme de cache, en stockant tout les données POST et GET dans le nom du fichier. Aurai-tu une solution ?

Cordialement

ViPHP
ViPHP | 5924 Messages

01 sept. 2007, 03:47

@BlAcKbUrRy : Un site qui a 500 000 connexions simultannées, ce n'est pas un gros site c'est un site gigantesque, et qui tourne sur un très gros dédié, le genre de dédiés pour lesquels on ne compte pas l'espace disque en Gigaoctets…
Donc ton argument est irrecevable, s'il y a 500 000 membres, il n'y a aucune raison qu'il y ait autant de fichiers, puisque un bon cache est un cache qui se vide. Au fur et à mesure, le gestionnaire de cache est sensé supprimer les fichiers des membres qui se déconnectent ou dont la session expire, c'est pour cette raison que pour avoir 500 000 fichiers de cache, il faudrait 500 000 connectés, ce qui est considérable, bien plus que les 3 Gigas de cache que cela occasionnerait…

Petit nouveau ! | 2 Messages

01 sept. 2007, 11:43

Bonjour,
Oui je suis d'accord.


Mon probléme reste un mystére.