Dynamic menus without requestAction() in CakePHP 1.2

You’ve probably heard time and time again (especially in a few recent and popular blog posts) that using requestAction() is, generally speaking, considered to be a “bad practice”, “last resort”, “hackish” way of doing things in cake.

Let’s consider one common use to see if we can achieve our goals without using requestAction()…

We have a Company model and our goal is to build a navigational menu consisting of all the companies in our table. We would create an element called ‘company_nav.ctp’ and somehow load the relevant data from our Company model.
The first, obvious, approach is to use requsestAction() in an element, to get the required data. This, however, is something we would like to avoid.

The approach I offer below relies on cache, or cached model’s data to be precise…

We can agree that our menu or navigation would only change if a company has been added, modified or deleted. Therefore we use our Company model’s callback methods such as afterSave() and afterDelete() to properly cache the required data for the menu.

So, assuming we have a Company model, we can add the following to handle the data caching:

function afterSave() {
   $this->_cacheNav();
}

function afterDelete() {
   $this->_cacheNav();
}

function _cacheNav() {
$companies = $this->find('list');

Cache::config(null, array('engine'=>'File', 'path'=>CACHE));
Cache::write('companies', $companies, array('duration'=>7200, 'config'=>null));
}

The code is pretty simple, but let me explain it just a little…

We rely on afterSave() and afterDelete() to call our custom method _cacheNav(), which handles the actual caching of the Company data.

In this case we only need Company.name and Company.id for our menu, so using find(‘list’) works perfectly well for that.
We then call Cache::config() to ensure that we write to the correct location (the constant CACHE is defined for us by cake core) and that we use the correct storage engine (I use File, but you can use any supported engine). I found that specifying the ‘path’ is a good idea, since Cache can write to different locations depending on the context, therefore it’s best to be explicit about it.

Alright, now that our data is cached, we can easily build our element (company_nav.ctp):

<?php

Cache::config(null, array('engine'=>'File', 'path'=>CACHE));

$companyData = Cache::read('companies');

if(!empty($companyData)) {
   foreach($companyData as $key => $value) {
      echo '<div>'.$html->link($value, array('action'=>'view', $key)).'</div>';
   }
}

?>

I think the above should be pretty much self-explanatory, we are simply reading the data we’ve cached earlier in our model and building a menu of links.
Again, I prefer to specify the ‘path’ to ensure we attempting to read from the correct cache path.

Before we finish up, I wanted to point out a few important issues:

1. The duration of the cache is limited
Of course you can increase it to some unrealistically large value to cache forever, or you could come up with some fall-back mechanism (i.e. the good ol’ requestAction()) if cache data is not available.
At any rate, this is just something to be aware of and it can be easily fine-tuned to fit your specific needs.

2. Breaking of MVC (?)
In theory the view (element) should not access the data directly, and I’m not 100% sure if using Model’s cached data is somehow an exception to this rule. Looking at the benefits, however, I feel that this approach is justified. Cache is accessible to all of our application objects, therefore we could at least say that we are not breaking the rules, but rather bending them slightly.

%d bloggers like this: