Optimizing your CakePHP elements and views with caching

By Tane Piper (digitalspaghetti)
As I have developed my CakePHP application, I have discovered new and useful functions every day - and today I want to talk to you about caching, and how easily it can be implemented. When I implemented it on my front page, I went from having 7 database queries down to 1.
For my application, I wished to cache a few sections, but for this example I will use my front page Event list. The method selects the last X events from the database (I use 5) and displays it in the block. If I didn't cache it, the database would be called on every page that is viewed by every user. Thank to Andy Dawson (http://www.noswad.me.uk) aka AD7six on #cakephp for pointing this out to me, and for all the help so far.

Also, I have to admit 2 of these optimizations were from upgrading to the new version of the ConfComponent (http://bakery.cakephp.org/articles/view/243) by Othman ouahbi, which now caches it's database queries per user visit.

So first, here is my EventsController method:

Download code
**
* Function getposts grabs x posts from database
* @int num
*/
function getevents($num = 5)
{
$getevents = $this->Event->findAll('Event.published = 1 AND Event.event_date >= NOW()', array('Event.id','Event.event_url','Event.venue','Event.address','Event.city', 'Event.country', 'Event.event_date', 'Event.event_time','Event.notes','EventType.id', 'EventType.name'), 'Event.event_date ASC', $num);

if(isset($this->params['requested'])) {
return $getevents;
}
$this->set('getevents', $getevents);
}


You may notice that I select a lot of fields in this example - this is infact another optimization. If I didn't select the fields I wanted to use, CakePHP would select every field in the events table, as well as the users table (because the generator of the event is linked via the user id), including the users password (hashed) and email. It also saves against getting ambiguous fields that may confuse during development. Also, by checking if $this->params['requested'] has been set, I can also pass the data to a requestAction().

So, now we are going to display the data. To do this, you first need to create an element. I stick to naming it after the controller, so in my /app/views/elements/ directory I create a file called getevents.ctp (.ctp is the Cake 1.2 default views extention).

At the top of the file, I need to be able to access the data. In elements, we use the requestAction() method. This allows us to perform an action just like going to the URL in your browser, but outputs the data to a variable you set. Be careful of this, it can be expensive (see http://www.noswad.me.uk/MiBlog/MiniControllers to run a requestAction(), but caching will help solve this problem.

Download code
<?php $getevents $this->requestAction('/events/getevents/5');?>

So now we have our events displayed. We have set the array variable $events, so now do an loop like this:

Download code
<h2>Upcoming Gigs</h2>
<ul>
<?php foreach($getevents as $event) : ?>
<li class="vevent">
<?php e($html->link($event['EventType']['name'] . ' : ' $event['Event']['venue'], '/events/view/'.$event['Event']['id'], array('class'=>'url description')));?>
<abbr class="dtstamp" title="<?php $time->format('Y-m-dTH:m:s'$event['Event']['event_date'] . $event['Event']['event_time']));?>"><?php e($time->format('D d M Y',$event['Event']['event_date']));?> @ <?php         e($time->format('H:i'$event['Event']['event_time']));?></abbr>
<br />
<span class="location"><?php e($event['Event']['address']);?>,         <?php e($event['Event']['city']);?><?php e($event['Event']['country']);?></span>
</li>
<?php endforeach; ?>
</ul>


Now, the final step is to actually render the content to the page. In your layout template, where you wish to place the element, type in this line:

Download code
<?php $this->element('getevents');?>


Ok, now go ahead and refresh your page. Woaala!, you have rendered your element. But hold on, we haven't cached it yet! Just have a look around your site, or refresh your page a few times. If you have debug on 2, you will see your SQL statement form this function is still querying the database on every page hit. So lets add caching.

Download code
<?php $this->element('getevents', array('cache'=>'1 day'));?>


Wait? Thats it? Yep! You add caching, all you need to do is set the attribute. Now refresh your page a couple of times, and you should see the database stop querying for this element. This element will now stay cached for 24 hours, but there is a problem with that - if the data in the element is updated since the last cache action, it won't show up until the next one. You could solve that by reducing the cache time (e.g. 3600 seconds). Or another way is to use afterSave. afterSave is a method in your model that is executed after the model has been saved to the database. It is useful for clearing up unwanted files or session data, and in our case the element cache file.

Element cache files are located in the /app/tmp/cache/views/ directory, and will normally start with the filename element__. In this case it's called element__getevents. To remove this after a data save, put this method in your Model

Download code
<?php class Event extends AppModel
{
function 
afterSave()
{
@
unlink(CACHE.'views'.DS.'element__getposts');
}


Now every time you update the model, the file is removed. So I hope this article has been useful to you. If it has, please leave a comment and let me know, or if you have anything to say about the article to improve it, feel free.

 

Comments 325

CakePHP Team Comments Author Comments
 

Comment

1 Good Article

You do not need to use Unlink and specify the path. You could use the built-it function

$this->clearCache('element__getposts','views','');

This will do the same as your unlink statement, but in a more proper.

On the whole, good article to get people going with caching.

Cheers,
Ketan
http://www.eclassifieds4u.com
Posted Jul 5, 2007 by Ketan
 

Comment

2 A perfect starter

A big thank you, this great article got me started with elements caching in Cake 1.2.
One note though: I didn't find anything to cache 'dynamic elements' (like a navigation menu changing accordingly to the page you are currently browsing), so I use the $params['plugin'] to insert my dynamic content to make sure to save 2 cached contents in 2 different cache files:


<?php 
$magicURI 
str_replace('/''__'env('REQUEST_URI'));
echo 
$this->element('breadcrumbs'
                     array(
'plugin' => $magicURI,
                           
'cache' => '+1 day'));


If we are on page /links/add, it will save cache in:
app/tmp/cache/element__links__add_breadcrumbs
if on /domains/all, it will save cache in:
app/tmp/cache/element__domain__all_breadcrumbs

Got my page to issue 18 SQL requests instead of 65 ;)

T0aD
French Free WebHosting, http://www.lescigales.org/
Posted Jul 6, 2007 by Julien Perez
 

Comment

3 Dynamic cached content

There is a ticket on trac to have the cache based on the parameters sent... which i really like.

https://trac.cakephp.org/ticket/2498
Posted Jul 8, 2007 by Alex McFadyen
 

Comment

4 Great

Thanks a lot for the article, it helped me a lot :P, a understood a few concepts.

s4lu2 from México
Posted Oct 29, 2007 by keogh
 

Comment

5 Little Mistake in your article

First off, I'd like to point out how awesome is that article. Really well written and simple enough for me to understand the powerful features of Elements and Caching.

I believe you have made a mistake when you show the following code:
<?php $this->element('getevents', array('cache'=>'1 day'));?>

it should be:

<?php echo $this->element('getevents', array('cache'=>'1 day'));?>

otherwise you just don't see anything!

Thanks again.

Alex
Posted Mar 17, 2008 by Alexandre V
 

Question

6 Is caching per query or per action

Is caching only per element? For example, if I have a user dashboard, 2 users in the system will have different queries to get their own user data to be loaded into their dashboards. However, they will still be using the same view/elements. Will I not be able to cache this since then the second user would see the data cached by the first user?
Posted Jun 14, 2008 by Matt Huggins