Laravel 4

Laravel 4 : chapitre 27 : Cache et configuration

Voyons aujourd’hui la mise en cache à la sauce Laravel 4. Je rappelle qu’un cache est destiné à accélérer la génération des pages en gardant en mémoire des informations. Le cas le plus classique est celui de requêtes sur une base de données. Si les données ne changent pas fréquemment il est plus efficace de mettre en cache leur résultat plutôt que d’aller chaque fois interroger la base. On peut aussi carrément mettre en cache des pages complètes.

Laravel 4 propose un système de cache simple et efficace qui mémorise par défaut les informations dans un fichier. Regardez le fichier app/config/cache.php :

return array(

	/*
	|--------------------------------------------------------------------------
	| Default Cache Driver
	|--------------------------------------------------------------------------
	|
	| This option controls the default cache "driver" that will be used when
	| using the Caching library. Of course, you may use other drivers any
	| time you wish. This is the default when another is not specified.
	|
	| Supported: "file", "database", "apc", "memcached", "redis", "array"
	|
	*/

	'driver' => 'file',

	/*
	|--------------------------------------------------------------------------
	| File Cache Location
	|--------------------------------------------------------------------------
	|
	| When using the "file" cache driver, we need a location where the cache
	| files may be stored. A sensible default has been specified, but you
	| are free to change it to any other place on disk that you desire.
	|
	*/

	'path' => storage_path().'/cache',

	/*
	|--------------------------------------------------------------------------
	| Database Cache Connection
	|--------------------------------------------------------------------------
	|
	| When using the "database" cache driver you may specify the connection
	| that should be used to store the cached items. When this option is
	| null the default database connection will be utilized for cache.
	|
	*/

	'connection' => null,

	/*
	|--------------------------------------------------------------------------
	| Database Cache Table
	|--------------------------------------------------------------------------
	|
	| When using the "database" cache driver we need to know the table that
	| should be used to store the cached items. A default table name has
	| been provided but you're free to change it however you deem fit.
	|
	*/

	'table' => 'cache',

	/*
	|--------------------------------------------------------------------------
	| Memcached Servers
	|--------------------------------------------------------------------------
	|
	| Now you may specify an array of your Memcached servers that should be
	| used when utilizing the Memcached cache driver. All of the servers
	| should contain a value for "host", "port", and "weight" options.
	|
	*/

	'memcached' => array(

		array('host' => '127.0.0.1', 'port' => 11211, 'weight' => 100),

	),

	/*
	|--------------------------------------------------------------------------
	| Cache Key Prefix
	|--------------------------------------------------------------------------
	|
	| When utilizing a RAM based store such as APC or Memcached, there might
	| be other applications utilizing the same cache. So, we'll specify a
	| value to get prefixed to all our keys so we can avoid collisions.
	|
	*/

	'prefix' => 'laravel',

);

Vous trouvez toutes les possibilités de configuration du cache. Vous voyez en premier la façon dont les informations sont stockées :

  • fichier (valeur par défaut)
  • base de données
  • APC
  • Memcached
  • Redis
  • array

Vous trouvez aussi la localisation du fichier (par défaut app/storage/cache). Le nom de la base et de la table à utiliser dans le cas de stockage en base de données. Le paramétrage de memcached. Le préfixe pour APC ou Memcached.

Quelle que soit la méthode choisie la classe Cache de Laravel offre les mêmes possibilités de base.

Les commandes de base

Avec une nouvelle installation de Laravel 4 entrez ce code dans le fichier des routes :

Route::get('/', function()
{
	Cache::put('un', '1', 2);
	echo Cache::get('un');
});

Résultat :

1

La méthode put permet de mémoriser une information, le premier paramètre est la clé, le second la valeur et le troisième la durée de conservation de l’information.

La méthode get est l’inverse, elle permet de retrouver une information mémorisée à partir de sa clé.

Regardons maintenant le fichier créé :

img55

Et son contenu :

1368103994s:1:"1";

Il est possible de conserver indéfiniment une information en utilisant la méthode forever :

Cache::forever('un', '1');

Et la méthode forget pour en supprimer une :

Cache::forget('un');

On a aussi la possibilité d’affecter une valeur par défaut en cas d’inexistence de la valeur :

Cache::get('deux', function() { return '2'; });

Vous pouvez aussi récupérer une valeur dans le cache et la mémoriser si elle n’est pas présente :

$value = Cache::remember('un', 1, function()
{
    return 1;
});

Un cas d’application

Voyons maintenant un cas d’application qui justifie l’utilisation d’un cache. Si vous allez sur cette page vous trouvez la documentation de Laravel 4. Cette application a été réalisée par Dayle Rees et le code se trouve ici. Je me suis dit qu’il y avait une façon plus simple de procéder en allant directement chercher les informations sur la page du projet. Surtout qu’il y a une page avec la table des matières.

La route

Partez d’une nouvelle installation de Laravel. Dans le fichier des routes mettez juste ce code pour appeler un contrôleur :

Route::get('/{name?}', 'DocController@show');

Le contrôleur

Voici le code du contrôleur app/controllers/DocController.php :

<?php

class DocController extends BaseController {

    /**
     * Le template de base
     */
    protected $layout = 'layout.master';

    /**
     * Récupération du HTML d'un URL
     */
	private function getHtml($url)
	{
        $curl = curl_init($url);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
		$output = curl_exec($curl);
        curl_close($curl);
        return $output; 		
	}

    /**
     * Récupération d'une balise dans du HTML
     */
    private function getTag($html, $tag, $exclu = null)
    {
        $start = strpos($html, '<' . $tag);
        $end = strpos($html, '</' . $tag);
        if($exclu == null) {
            return substr($html, $start, $end - $start + strlen($tag) + 2); 
        } else {
            $start = strpos($html, '>', $start) + 1;
            return substr($html, $start, $end - $start);  
        }                 
    }

    /**
     * Affichage de la page.
     */
	public function show($name = 'introduction')
	{
        // Récupération de la page d'index
		$index = $this->getHtml('https://github.com/laravel/docs/blob/master/documentation.md');

        // Sélection de l'index
        $index = $this->getTag($index, 'article', true); 

        // Création du DOM
        libxml_use_internal_errors(true);
        $doc = new DOMDocument;
        $doc->loadHTML($index);

        // Changement des liens
        $nodes = $doc->getElementsByTagName('a');
        foreach ($nodes as $node) {
        	$link = $node->getAttribute('href');
        	$node->setAttribute('href', basename($link));
            $node->setAttribute('class', 'btn btn-small');
        }

        // Création du Html élagué
        return $this->getTag($doc->saveHTML(), 'body', true);

        // Récupération du contenu
        $content = $this->getHtml('https://github.com/laravel/docs/blob/master/' . $name . '.md');

        // Sélection du texte
        $content = $this->getTag($content, 'article');  

        // Création du DOM
        $doc->loadHTML($content);

        // Ajout de la classe pour le prettify
        $nodes = $doc->getElementsByTagName('pre');
        foreach ($nodes as $node) {
            $node->setAttribute('class', 'prettyprint');
        }  

        // Ajout classes pour les tableaux
        $nodes = $doc->getElementsByTagName('table');
        foreach ($nodes as $node) {
            $node->setAttribute('class', 'table table-bordered table-condensed');
        }  

        // Création du Html élagué
        $content = $this->getTag($doc->saveHTML(), 'body', true); 

        // Affichage de la vue
        $this->layout->content = View::make('main', array('nav' => $nav, 'content' => $content));
	}

}

En gros le code permet d’aller récupérer le Html de la documentation, de sélectionner juste ce qui est utile, de modifier les liens et d’ajouter quelques classes pour la présentation. Je laisse des classes devenue inutiles, les puristes peuvent les supprimer…

Les vues

Créez le template app/views/layout/master.blade.php :

<!DOCTYPE html>
<head>
  <meta charset="utf-8">
  <title>Laravel documentation</title>
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  {{ HTML::style('assets/css/bootstrap.min.css') }}
  {{ HTML::style('assets/css/bootstrap-responsive.min.css') }}
  {{ HTML::style('assets/css/main.css') }}
</head>

<body>
  <div class="container">
    <h1>Documentation de Laravel 4</h1>
    <hr>
    <div class="row">
      <nav class="span3">
        @yield('navigation')
      </nav>
      <section class="span9">
        @yield('content')
      </section>
    </div>
  </div>
  {{ HTML::script('assets/javascript/run_prettify.js') }}
</body>

On charge Bootstrap pour la mise en forme, on prévoit deux localisations pour la navigation et le contenu et on charge Prettify pour la coloration syntaxique du code.

Créez ensuite la vue app/views/main.blade.php qui utilise ce template :

@section('navigation')
  {{ $nav }}
@stop

@section('content')
  {{ $content }}
@stop

Les assets

Il ne nous manque plus que les assets :

img56

Pour télécharger Bootstrap c’est ici. Et pour Prettify c’est ici. Et voici le code de main.css :

body {
	background-color: #ccc;
}
.container {
	margin-top: 20px;
}
pre {
	background-color: white;
}
ul {
	list-style-type: none;
}
nav ul:first-child {
	font-size: 20px;
	padding: 10px;
	color: #27a;
}

Et voici l’apparence du résultat :

img57

Les liens fonctionnent et la présentation est agréable (bon enfin c’est à mon goût vous pouvez changer tout ce que vous voulez Tongue Out).

Mise en cache pour l’application

Maintenant que nous avons une application plutôt laborieuse parce qu’elle doit envoyer des requêtes HTTP pour récupérer des informations, nous pouvons utiliser un cache pour la rendre bien plus rapide. Il faut mettre en cache d’une part la table des matières, et ensuite chacun des chapitres au fur et à mesure qu’ils sont affichés. Voici le contrôleur modifié en conséquence :

class DocController extends BaseController {

    /**
     * Le template de base
     */
    protected $layout = 'layout.master';

    /**
    * Récupération du HTML d'un URL
    */
	private function getHtml($url)
	{
        $curl = curl_init($url);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
		$output = curl_exec($curl);
        curl_close($curl);
        return $output; 		
	}

    /**
    * Récupération d'une balise dans du HTML
    */
    private function getTag($html, $tag, $exclu = null)
    {
        $start = strpos($html, '<' . $tag);
        $end = strpos($html, '</' . $tag);
        if($exclu == null) {
            return substr($html, $start, $end - $start + strlen($tag) + 2); 
        } else {
            $start = strpos($html, '>', $start) + 1;
            return substr($html, $start, $end - $start);  
        }                 
    }

    /**
     * Affichage de la page
     */
	public function show($name = 'introduction')
	{
        // Récupération de l'index
        $nav = Cache::remember('nav', 60, function()
        {
            // Récupération de la page d'index
    		$index = $this->getHtml('https://github.com/laravel/docs/blob/master/documentation.md');

            // Sélection de l'index
            $index = $this->getTag($index, 'article', true); 

            // Création du DOM
            libxml_use_internal_errors(true);
            $doc = new DOMDocument;
            $doc->loadHTML($index);

            // Changement des liens
            $nodes = $doc->getElementsByTagName('a');
            foreach ($nodes as $node) {
            	$link = $node->getAttribute('href');
            	$node->setAttribute('href', basename($link));
                $node->setAttribute('class', 'btn btn-small');
            }

            // Création du Html élagué
            return $this->getTag($doc->saveHTML(), 'body', true);
        });

        // Récupération du contenu
        $content = Cache::remember($name, 60, function() use ($name)
        {
            $content = $this->getHtml('https://github.com/laravel/docs/blob/master/' . $name . '.md');

            // Sélection du texte
            $content = $this->getTag($content, 'article');  

            // Création du DOM
            libxml_use_internal_errors(true);
            $doc = new DOMDocument;
            $doc->loadHTML($content);

            // Ajout de la classe pour le prettify
            $nodes = $doc->getElementsByTagName('pre');
            foreach ($nodes as $node) {
                $node->setAttribute('class', 'prettyprint');
            }  

            // Ajout classes pour les tableaux
            $nodes = $doc->getElementsByTagName('table');
            foreach ($nodes as $node) {
                $node->setAttribute('class', 'table table-bordered table-condensed');
            }  

            // Création du Html élagué
            return $this->getTag($doc->saveHTML(), 'body', true); 
        });

        // Affichage de la vue
        $this->layout->content = View::make('main', array('nav' => $nav, 'content' => $content));
	}

}

Maintenant notre application devient performante. J’ai réglé la durée de mémorisation à 60 minutes, ce qui semble très raisonnable dans ce cas parce que la documentation change rarement.

Configuration

On peut améliorer l’ergonomie de notre application en utilisant un fichier de configuration pour la durée de mémorisation et le titre de la page. Créez un fichier app/config/doc.php avec ce code :

return array(

/*
|--------------------------------------------------------------------------
| Titre
|--------------------------------------------------------------------------
|
| Titre qui apparaît en haut de la page 
|
| Défaut : 'Documentation de Laravel 4'
|
*/

'title' => 'Documentation de Laravel 4',

/*
|--------------------------------------------------------------------------
| Durée
|--------------------------------------------------------------------------
|
| Durée de mémorisation pour le cache en minutes
|
| Défaut : 60
|
*/

'timer' => 60

);

Il ne reste plus qu’à modifier le template app/views/layout/master.blade.php :

<h1>{{ Config::get('doc.title') }}</h1>

Et le contrôleur :

$nav = Cache::remember('nav', Config::get('doc.timer'), function()
$content = Cache::remember($name, Config::get('doc.timer'), function() use ($name)

Les paramètres sont ainsi centralisés dans un fichier dédié Wink.

Print Friendly, PDF & Email

3 commentaires

  • billgates107

    Salut Bestmomo, je sais que c’est déjà expliqué mais j’ai toujours du mal à comprendre comment utiliser la méthode Cache::remember(), est-ce que tu peux me la réexpliquer s’il te plaît ? Là où j’ai du mal c’est que tu utilises le retour d’une fonction anonyme pour le stockage alors que dans Cache::get() on a juste besoin d’indiquer une paire clé/valeur. Merci d’avance.

    • bestmomo

      Salut Bill !

      Quand tu écris : Cache::get(‘deux’, function() { return ‘2’; });

      Tu dis que tu veux récupérer la valeur qui correspond à « deux » et si elle n’existe pas dans le cache tu retournes la valeur 2. Mais rien n’est écrit dans le cache.

      Quand tu écris : Cache::remember(‘deux’, 10, function() { return ‘2’; });

      Tu dis aussi que tu veux récupérer la valeur qui correspond à « deux » et si elle n’existe pas dans le cache tu retournes la valeur 2 avec la fonction anonyme. Mais en plus tu mémorises (la méthode appelle put) cette valeur dans le cache pour la durée indiquée dans le deuxième paramètre.

Laisser un commentaire