Optimizing your CakePHP elements and views with caching

This article is also available in the following languages:
By 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:


**
* 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.


<?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:


<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:


<?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.


<?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


<?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

  • Posted 12/13/09 03:29:19 PM
    I got several $this->set('variable', $this->find()) in controller and after I render element he is finish with error unable to receive $variable with records.
  • Posted 08/12/09 02:48:34 PM
    In my personal opinion there is a major downside to clearCache().

    Here is how it works:
    You give no argument = clears the directory of all cache files
    You give the name of an existing file = clears only that file
    (so far so good... here it comes)
    You give the name of a non-existing file = clears the directory of all cache files

    So in reality, if you want to remove just a single file you would in most cases need to check that the file existed first. You might even want to clear the filesystem cache first aswell.

    This is a whole lot more trouble than using unlink() IMHO.
  • Posted 06/21/09 07:11:05 PM
    I followed what you have done here in my App, it will break down my sort and pagination function. How you solve this problem?
  • Posted 06/14/08 03:43:13 PM
    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 03/17/08 07:19:29 PM
    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:
    element('getevents', array('cache'=>'1 day'));?>

    it should be:

    element('getevents', array('cache'=>'1 day'));?>

    otherwise you just don't see anything!

    Thanks again.

    Alex
  • Posted 10/29/07 02:35:38 PM
    Thanks a lot for the article, it helped me a lot :P, a understood a few concepts.

    s4lu2 from México
  • Posted 07/08/07 02:09:17 PM
    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 07/06/07 05:32:00 AM
    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 ;)
  • Posted 07/05/07 01:14:47 PM
    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
    • Posted 08/12/09 11:48:43 AM
      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.
      .....

      I prefer to use unlink as old stable function. and I think it is faster. Of course it is not big difference... I is good to use clearCache if params are dynamic. If you need just to delete on user event there I prefer to use unlink.

Comments are closed for articles over a year old