Avis sur ma class CacheManager + question MVC - Apprendre-PHP.com

Rechercher
Boutique en ligne, solution e-commerce, script PHP et PERL : RAYNETTE

Avis sur ma class CacheManager + question MVC

Par Graphox -  7 reponses -  Le 23/05/2009 -  Flux RSS - 

Bonjour !

J'ai réalisé une classe pour gérer de différentes façons des données en cache : pouvoir mettre en cache des données spécifiques (en n'enregistrant qu'un tableau par exemplee), ou mettre en cache le contenu d'un tampon de sortie.

Pour les deux méthodes, il est possible de spécifier une durée de vie maximale à ce fichier de cache, mais ce n'est pas obligé : lors de l'ajout/modification/suppresion de donné, il suffit d'appeler une méthode statique pour supprimer le cache et donc ainsi économiser une mise en cache pour rien (si on ne poste pas de news depuis 1jour, innutile de rafraîchir toutes les 5 minutes avec cette organisation).

Donc voilà, j'ai essayé de caser tous les sytèmes les plus courants de mettre des données/pages en cache dans une seule classe, et j'aimerais, si possibl, vos avis sur ma classe, sachant que j'ai essayé d'optimiser au max, et donc la méthode Exists pourrait vous parraître longue pour pas grand chose (je débute en POO aussi) :

<?php
class CacheManager
{
private $_file = '';
private $_time = false;
private $_buffer = false;
private $_get_return = null;
const DIR = './_cache/';
const EXTENSION = '.php';
 
public function __construct($file, $time = false, $buffer = false)
{
$this->_file = $file;
$this->_time = $time;
$this->_buffer = $buffer;
}
 
public function Exist()
{
if(is_int($this->_time))
{
if(file_exists(self::DIR.$this->_file.self::EXTENSION))
{
if(time() - filemtime(self::DIR.$this->_file.self::EXTENSION) >= $this->_time)
{
unlink(self::DIR.$this->_file.self::EXTENSION);
return false;
}
 
else
return true;
}
 
else
return false;
}
 
else
return file_exists(self::DIR.$this->_file.self::EXTENSION);
}
 
public function BufferInit()
{
}
 
public function Create($save = null, $array_splice = false)
{
if($this->_buffer)
{
$content = ob_get_contents();
$this->_get_return = $content;
file_put_contents(self::DIR.$this->_file.self::EXTENSION, $content);
}
 
else
{
if(is_array($save) && $array_splice)
array_splice($save, -1);
$this->_get_return = $save;
file_put_contents(self::DIR.$this->_file.self::EXTENSION, serialize($save));
}
}
 
public function Get()
{
if($this->_get_return != null)
return $this->_get_return;
else
{
if($this->_buffer)
return file_get_contents(self::DIR.$this->_file.self::EXTENSION);
 
else
return unserialize(file_get_contents(self::DIR.$this->_file.self::EXTENSION));
 
}
}
 
public static function Destroy($file)
{
if(file_exists(self::DIR.$file.self::EXTENSION))
unlink(self::DIR.$file.self::EXTENSION);
}
 
public static function DestroyDirContent($dir = null)
{
if(is_dir(self::DIR.$dir))
{
if(!preg_match('`^.*\/$`', $dir))
$dir .= '/';
 
if($handle = opendir(self::DIR.$dir))
{
while( $item = readdir($handle) )
{
if($item != '.' && $item != '..')
{
if(is_dir(self::DIR.$dir.$item))
self::DestroyDirContent($dir.$item);
else
unlink(self::DIR.$dir.$item);
}
}
closedir($handle);
}
}
}
}
?>

Et voici des exemples d'utilisation :

 

Ne met en cache qu'un tableau, détruit manuellement
$cache = new CacheManager('test'); // Le cache sera détruit manuellement grâce à la méthode statique Destroy() ou DestroyDirContent();
if(!$cache->Exist())
{
$dbh = new PDO('mysql:host=localhost;dbname=test', 'root', '');
$dbh->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_WARNING);
 
$sth = $dbh->query('
SELECT * FROM guestbook
');
 
$result = $sth->fetchAll(PDO::FETCH_ASSOC);
 
$cache->Create($result);
}
 
$donnees = $cache->Get();
 
foreach($donnees as $line)
{
echo $line['id'].'<br />';
echo $line['pseudo'].'<br /><hr />';
}
Met en cache via une bufferisation, détruit au bout d' 1 minute
$cache = new CacheManager('test2', 60, true); // Le cache sera généré par une tamporisation de sortie et sera actualisé si il date de plus de X secondes (ici, 60)
 
if(!$cache->Exist())
{
$cache->BufferInit();
 
mysql_connect('localhost', 'root', '');
mysql_select_db('test');
 
$resultat = mysql_query('SELECT * FROM guestbook');
 
while ($donnees = mysql_fetch_assoc($resultat)) {
echo $donnees['pseudo'].'<br />';
echo $donnees['id'].'<br />';
}
 
$cache->Create();
}
 
echo $cache->Get();

Merci !

-----

Et sinon, une autre question, dans une application structurée en MVC, peut-on instancier des classes dans le controlleur ?

Par exemple, avec ma classe de cache si-dessus, serait-il logique de faire une condition if(cache exists) dans le contrôleur ou je dois essayer de caser les appels aux objets au modéle ?

 

Merci d'avance :)

 

Graphox.

 

 

Réponses apportées à cette discussion

Par Emacs -  Le 24/05/2009 - 

Salut,

Ton code est plutôt pas mal mais il a quelques problèmes... Déjà, je te conseille d'éviter d'écrire tes noms de méthodes avec une majuscule en première caractère. C'est laid et ce n'est pas conventionnel. Ecris tes méthodes en "lower camel case" comme je le fais dans le code ci-dessous.

Autre point négatif, certaines méthodes sont trop longues et ont un niveau d'imbrication conditionnelle trop élevé. Il est préférable de limiter le niveau d'imbrication en réfléchissant par négation. C'est à dire, tu testes d'abord que tu ne peux pas faire une action et dans ce cas tu jettes une exception ou bien tu retournes directement un FALSE. De cette manière, le code se lit de haut en bas séquentiellement et non de haut en bas et de gauche à droite. Ca le rend beaucoup plus simple à maintenir.

Enfin, il existe plusieurs types de cache (caches html, caches de requêtes SQL, cache de tableaux PHP sérialisés, caches d'objets PHP sérialisés... ). Il faut donc que tu aies au moins une classe de cache par type de cache. L'idéal est donc de centraliser la logique commune dans une classe abstraite comme je le fais ci-dessous. Puis tu dérives cette classe et tu instancies ces objets dérivés.

  1. <?php
  2.  
  3. abstract class CacheManager
  4. {
  5. private $_cacheDir;
  6. private $_lifeTime;
  7. private $_cacheKey;
  8.  
  9. public function __construct($cacheKey, $lifeTime)
  10. {
  11. $this->_cacheKey = $cacheKey;
  12. $this->_lifeTime = (int) $lifeTime;
  13. $this->_cacheDir = '/tmp';
  14. }
  15.  
  16. public function cache($content)
  17. {
  18. if (!file_exists($this->getCacheDir()))
  19. {
  20. throw new Exception(sprintf('The directoy "%s" does not exists', $this->getCacheDir()));
  21. }
  22.  
  23. if (!is_writable($this->getCacheDir()))
  24. {
  25. throw new Exception(sprintf('Directory "%s" is not writable', $this->getCacheDir()))
  26. }
  27.  
  28. return file_put_contents($this->getCacheFilePath(), $content);
  29. }
  30.  
  31. public function getContent()
  32. {
  33. return file_get_contents($this->getCacheFilePath());
  34. }
  35.  
  36. public function exists()
  37. {
  38. return (file_exists($this->getCacheFilePath()) && !$this->isExpired());
  39. }
  40.  
  41. public function clean()
  42. {
  43. unlink($this->getCacheFilePath());
  44. }
  45.  
  46. public function cleanCacheDir()
  47. {
  48. foreach (glob($this->getCacheDir().'/*.cache') as $file)
  49. {
  50. unlink($file);
  51. }
  52. }
  53.  
  54. public function isExpired()
  55. {
  56. return ((filemtime($this->getCacheFilePath()) + $this->getLifeTime()) > time());
  57. }
  58.  
  59. public function getCacheFilePath()
  60. {
  61. return $this->getCacheDir() . DIRECTORY_SEPARATOR . $this->getCacheFileName();
  62. }
  63.  
  64. public function getCacheFileName()
  65. {
  66. return $this->getMd5CacheKey() . '.cache';
  67. }
  68.  
  69. public function setCacheDir($dir)
  70. {
  71. $this->_cacheDir;
  72. }
  73.  
  74. public function getCacheKey()
  75. {
  76. return $this->_cacheKey;
  77. }
  78.  
  79. public function getCacheDir()
  80. {
  81. return $this->_cacheDir;
  82. }
  83.  
  84. public function getMd5CacheKey()
  85. {
  86. return md5($this->_cacheKey);
  87. }
  88.  
  89. public function getLifeTime()
  90. {
  91. return $this->_lifeTime;
  92. }
  93. }
  94.  
  95. class CacheHtml extends CacheManager
  96. {
  97.  
  98. }
  99.  
  100. class CacheArray extends CacheManager
  101. {
  102.  
  103. }

 

Ca te donne une idée d'implémentation. Au final, tu implémentes les ob_start() dans ta classe CacheHtml en redéfinissant les méthodes adéquates.

Concernant le MVC, oui tu peux instancier des classes dans le contrôleur. Pourquoi ne voudrais-tu pas le faire ?

++

Hugo. 

 
Par Emacs -  Le 24/05/2009 - 

En relisant mon code, je rendrai même la méthode cache() abstraite afin de la redéfinir obligatoirement dans les classes dérivées.

 
Par Graphox -  Le 24/05/2009 - 

Merci beaucoup pour ta réponse, je vais revoir l'organisation de ma class en m'inspirant de ton exemple :)

Pour le MVC, j'avais vu plusieurs définitions, et j'avais compris que le modéle devait récupérer les données et les transmettre au controleur, mais que le controleur ne devait pas s'occuper de la recherche de données.

Je vais donc instancier un objet News, par exemple, et appeler les méthodes dans le controleur.

Mais alors, la class sera le modéle ? (Je devrais placer le code de la class dans mon dossier /modeles)

 

Merci en tout cas,

 Graphox.

 
Par Graphox -  Le 24/05/2009 - 

Désolé du double post, mais pour mes fichiers de cache, qu'elle extension me conseilles-tu ? (j'ai souvent vu des .cache, .cache.php, .html, .php)

merci

 
Par Emacs -  Le 24/05/2009 - 

Tu peux utiliser l'extension que tu veux mais .cache convient très bien. De nombreux frameworks ou librairies de cache utilisent cette extension.

 
Par Graphox -  Le 24/05/2009 - 

D'accord merci ! Je vais utiliser .cache alors :)

 Et trois autres questions :

- Le constructeur d'une classe abstraite sert à quoi ? Je dois renseigner les paramètres obligatoires de ce constructeur de la classe abstraite lors de l'instancion de mon objet dérivé ?

- Je n'ai pas très bien compris ce que le contrôleur devait contenir : tout d'abord, j'inclus ma clase (le modèle donc), puis j'instancie mon objet puis j'appele des méthodes : mais c'est à partir de là que je suis perdu :

Dois-je juste appeler une méthode getNews, par exemple, qui fera tout le travail nécessaire puis appeler la vue juste après, ou je dois découper mon code (getNbrNews, instancion de l'objet pagination, getPagination, des conditions, etc..).

Je pense que découper serait mieux, mais ce n'ai pas "grave" d'avoir un long controleur qui, appele des méthodes, au lieu de faire des requêtes mais fait presque tout ?

Par exemple, un if(isset($_GET['page'])) { $pagination->getLink(); } serait mieux dans le controleur ou le modéle ?

- J'ai deux modules : articles et projets. Je souhaites utiliser le même code pour les commentaires, donc extactement le même code à part le nom de la table qui change. J'ai pensé à créer un module commentaire et l'utiliser pour mes deux autres modules articles et projets. Miais quelle est la meilleure façon de spécifier au module commentaires que je veux les commentaires pour les articles, et donc changer le nom de la table ? 

Merci en tout cas pour tes conseils, et merci aussi pour les tutos sur la POO, ça m'a permis de débuter en OO :)

 
Par heavenfr -  Le 11/08/2009 - 

@Emacs : Ton exemple de gestion du cache est sympa, mais pourrais-tu donner un exemple pour les classes filles stp ? Je n'ai pas bien compris comment l'utiliser ensuite oO

Pourquoi ne pas faire simplement une classe cache qui met en cache tout le fichier ?

 

 

Ajouter une réponse à la discussion

Seuls les membres loggués sont autorisés à poster dans les forums !