I think in this instance I would use a combination of action caching and view caching. I won't go into the vagaries of different caching engines, just the basics to solve your problem.
The first thing you need to do is turn caching on. In app/config/core.php
, you'll need to make a couple changes. First, comment out the Cache.disable
line:
//Configure::write('Cache.disable', true);
Second, uncomment the Cache.check
line:
Configure::write('Cache.check', true);
Finally, you'll need to make CacheHelper
available to all views, so add it to the $helpers
member definition in app/app_controller.php
:
class AppController extends Controller
{
var $helpers = array('Cache');
}
Next, we'll set up the view element that contains your category menu (doing the view stuff before the controller stuff may seem a little backwards, but bear with me -- it'll make sense). Putting the menu in a view element makes caching it a bit simpler than trying to selectively designate parts of a larger view or layout as uncached with <cake:no-cache></cake:no-cache>
tags.
Here's a sample view element, rendering the category menu in a simple unordered list (<ul>
):
<?php
$menuItems = $this->requestAction(array('controller'=>'categories','action'=>'menu'));
echo $html->nestedList( $menuItems, 'ul' );
?>
You may have been told by one or more people in the past that requestAction
is evil, as it initiates a whole new dispatch. This is true. You should avoid requestAction
like the plague, except when you're doing this type of view-caching, in which case it's the best way to go.
We'll store this new view element as app/views/elements/category_menu.ctp
. The requestAction
invokes the menu
action in CategoriesController
. Here's what CategoriesController
looks like, then I'll go through it bit by bit:
class CategoriesController extends AppController
{
var $cacheAction = array(
'menu/' => '1 day'
);
function menu()
{
return $this->Category->find('list', array(
'fields' => array('Category.id','Category.label'),
'order' => array('Category.order')
));
}
}
What this does:
- The member definition for
$cacheAction
tells Cake that it should cache the results of the menu
action for 1 day.
- The
menu
method returns a one-dimensional array of categories, using Category.id
as the keys, and Category.label
as the values.
So, let's go over what we've got so far. We have a category_menu
element, which requests the CategoriesController::menu
action. When this request is received, Cake will check to see whether the result of that action has been cached, and hasn't expired. If the cached result is still valid, Cake will return it without invoking the menu
method we defined. Otherwise, Cake will invoke the method, and will cache the result. The view element then renders the result as a nestedList.
All that remains, then, is to use the view element in other views. Here's a simple sample app/views/layouts/default.ctp
:
<html>
[ snip... ]
<body>
<div class="category-menu">
<?php echo $this->element('category_menu', array('cache'=>'1 day')); ?>
</div>
<div class="content">
<p>Lorem ipsum sit dolor amet.</p>
</div>
</body
</html>
We use the familiar View::element
to render the category_menu
view element we created earlier, but we also tell Cake that we want it to render the view element once, then cache this rendered version, and hold onto it for a month. Any subsequent requests for that element, until a month elapses or we clear the cache, will deliver the pre-rendered view element. It won't even interpret app/views/elements/category_menu.ctp
, much less invoke requestAction
, or hit the database.
To ensure changes you make to Category
model records are reflected immesiately, you can invoke the global function clearCache()
in the create/update actions. One caveat about clearCache()
: it's a bit of a shotgun approach: inelegant but effective. clearCache()
wipes out the entire cache, rather than selectively clearing only the necessary views. It is a simple approach, however, and given you're talking about updating the categories monthly, having to regenerate the cache once a month is probably worth the diminished hassle of a more selective approach.
Very long answer! HTH