Eviter les requestAction dans le layout

Il n’est pas un projet de site qui ne fasse apparaître certains éléments dynamiques dans le layout général, comme un menu construit à partir d’une liste de catégories, une liste des dernières actualités publiées ou des commentaires récents. CakePHP propose pour ce faire une méthode bien pratique mais très gourmande en ressources, la méthode requestAction(). Nous allons proposer une autre approche qui évite d’y faire appel.

Imaginons un simple blog : sur chaque page publique, nous souhaitons faire apparaître la navigation principale qui sera en fait la liste des catégories d’articles, ainsi que la liste des 5 derniers commentaires validés.

Pour bien illustrer notre propos, nous allons commencer par voir comment nous faisions avec la méthode requestAction(), puis nous exposerons notre alternative qui évite toute surcharge inutile du serveur.

1. Ancienne méthode avec requestAction()

Commençons par examiner notre layout public :

// {app}/views/layout/default.ctp
<?php
$categories = $this->requestAction('/categories/menu');
$comments = $this->requestAction('/comments/recents/5');
?>
<html>
	<head>...</head>
	<body>
		<h1>Mon blog</h1>
 
		<div id="menu">
			<ul>
			<?php foreach($categories as $categorie): ?>
				<li><?php echo $html->link($categorie['Category']['name'], array('controller' => 'categories', 'action' => 'index', $categorie['Category']['id'])); ?></a></li>
			<?php endforeach; ?>
			</ul>
		</div>
 
		<div id="content">
			<?php echo $content_for_layout; ?> 
		</div>
 
		<div id="extra">
			<h2>Les derniers commentaires</h2>
			<ul>
			<?php foreach($comments as $comment): ?>
				<li><?php echo $html->link($comment['Comment']['title'], array('controller' => 'comments', 'action' => 'view', $comment['Comment']['id'])); ?></a></li>
			<?php endforeach; ?>
			</ul>
		</div>
	</body>
</html>

Les deux méthodes requestAction() en tête de fichier appellent chacune une action d’un contrôleur, que nous examinerons rapidement :

// {app}/controllers/categories_controller.php
function menu()
{
	return $this->Category->find('list');
}
// {app}/controllers/comments_controller.php
function recents($limit = 10)
{
	return $this->Comment->find('all', array(
		'conditions' => array('approved' => 1),
		'order' => 'created DESC',
		'limit' => $limit,
		'recursive' => -1
	));
}

Cette manière de faire fonctionne parfaitement, mais le défaut de la méthode requestAction() est qu’elle équivaut quasiment à l’appel d’une page entière, c’est à dire que Cake lance un cycle complet de traitement comme s’il s’agissait de charger une nouvelle page… Rien que dans notre simple exemple, ce n’est pas une, mais trois pages que nous chargeons (la page elle-même + 2 requestAction) ! Sur un site à forte charge, le temps de réponse peut s’allonger très rapidement.

Une première mesure pour réduire la charge serait de mettre en place un système de cache, mais cela n’est pas toujours possible, ni voulu, c’est pourquoi nous avons cherché une alternative à la méthode requestAction().

2. Nouvelle méthode sans requestAction()

Puisque nous voulons que la liste des catégories et les derniers commentaires soient disponibles pour chaque page, nous allons placer notre logique dans la méthode beforeRender() du Contrôleur général, AppController. Pour que celui-ci puisse toujours avoir accès aux données recherchées, nous allons déplacer nos actions de collecte des données, jusqu’ici dans les Contrôleurs CategoriesController et CommentsController, vers leurs Modèles respectifs (règle baptisée « Fat Models, skinny Controllers » par nos amis anglo-saxons) et faire appeler ces deux Modèles par l’AppController. Celui-ci n’aura plus qu’à appeler les deux nouvelles méthodes de Modèle avant de les passer à la Vue, donc au layout.

2.1. Migration de la logique du Contrôleur au Modèle

// {app}/models/category.php
function menu()
{
	return $this->find('list');
}
// {app}/models/comment.php
function recents($limit = 10)
{
	return $this->find('all', array(
		'conditions' => array('approved' => 1),
		'order' => 'created DESC',
		'limit' => $limit,
		'recursive' => -1
	));
}

2.2. Récupération des données depuis l’AppController

// {app}/app_controller.php
class AppController extends Controller
{
	var $uses = array('Category', 'Comment');
 
	function beforeRender()
	{
		$this->set('categories', $this->Category->menu());
		$this->set('comments', $this->Comment->recents(5));
	}
}

2.3. Layout

Il suffit de supprimer les deux appels à requestAction() ! Les variables $categories et $comments sont déjà disponibles grâce à AppController.

C’est parfait, nous arrivons bien au même résultat mais sans le risque de surcharge de la méthode requestAction(). Mais que se passe-t-il si nous avons plusieurs layouts ? Il n’est pas difficile d’imaginer une interface d’administration pour le gestionnaire du blog, et dans ce cas l’AppController charge des Modèles en trop avec la variable $uses. Avec deux Modèles comme ici, l’impact sera faible, mais si nous en avons plus, cela risque de pénaliser les performances de l’interface d’administration. Et tout le temps que le serveur passe à cela est bien sûr du temps en moins pour servir les visiteurs de la partie publique…

3. Nouvelle méthode optimisée

Nous allons donc chercher à ne charger les Modèles que si nous nous trouvons bien dans la partie publique, sans utiliser la variable $uses. Nous allons pour cela utiliser la méthode init() de la classe ClassRegistry, qui permet d’initialiser une classe de notre appli (Modèle ou autre) à la volée. Voyons la version optimisée de notre AppController :

// {app}/app_controller.php
class AppController extends Controller
{
	// Plus besoin de $uses :
	//var $uses = array('Category', 'Comment');
 
	function beforeRender()
	{
		// Layout de l'administration
		if(isset($this->params['prefix']) && $this->params['prefix'] == 'admin' && !$this->params['isAjax'])
		{
			$this->layout = 'admin_default';
		}
 
		// Pour le layout public
		if($this->layout == 'default')
		{
			$this->set('categories', ClassRegistry::init('Category')->menu());
			$this->set('comments', ClassRegistry::init('Comment')->recents(5));
		}
	}
}

Et voilà ! Cette fois, les Modèles ne seront appelés que si nous sommes bien sur la partie publique.

Pierre-Emmanuel Fringant

Articles connexes

Commentaires

Bien joué !

Très sympa ce blog. Bravo.

[...] Eviter les requestAction dans le layout [...]

Bonjour, En traduisant la documentation, j’ai mis à jour la section « requestAction » qui justement traite d’un cas similaire.

Afin de réduire encore plus la charge et une meilleure réutilisabilité, il est proposé un appel à un élément couplé avec le système de cache. Ainsi, en fonction de la fréquence de mise à jour (les catégories évoluent peu, mais les commentaires évoluent plus rapidement) on peut gérer simplement la restitution des derniers affichages.

L’exemple proposé est la récupération des commentaires récents. Voici le code de l’élément :

// views/elements/latest_comments.ctp</p>
 
<p>$comments = $this-&gt;requestAction('/comments/latest');
foreach($comments as $comment) {
    echo $comment['Comment']['title'];
}

Et son appel avec le cache (au maximum une requête par heure) :

echo $this-&gt;element('latest_comments', array('cache'=&gt;'+1 hour'));

La version française est ici : http://book.cakephp.org/fr/view/434/requestAction (à l’heure actuelle la traduction n’a pas été publiée, donc lisez la version anglaise)

bonjour,

j’ai appliqué vos conseils mais j’ai une erreur mysql lorsque je crée ma fonction dans mon beforeRender() voir le post à l’url : http://forum.cakephp-fr.org/viewtopic.php?id=1009

Quelqu’un a t’il une idée de la nature de mon message d’erreur ?

Merci.

Je suis débutant et y a quelques choses qui m’échappe. Et si maintenant je voudrais faire passer un paramètre (que je récupérerais dans une URL) à une de mes 2 fonctions, par exemple : $this->set(‘comments’, $this->Comment->recents($id_cat)); Comment pourrais t’on fonctionner ?

Je me pose la même question que DarkLago …

Moi aussi je débute… Une association m’a demandé de développer son site. Mon choix s’est arrêté sur CakePHP et je me posais la question sur comment afficher des rubriques dans un layout.

J’ai enfin trouvé bon bonheur avec cet article, et en plus avec une version optimisée !

Merci.

Participez

Pour insérer une portion de code, utilisez <pre lang="php">...</pre>