Caching Queries in CakePHP

Published on Apr 25, 2009 by Jamie Munro

If you haven't noticed already, at times CakePHP can be a little slow loading!  The reason for this is quite simple.  Rapid Application Development.  To allow for RAD, sometimes we must give up something, in this scenario it's a bit of speed when loading.  Don't worry, CakePHP offers some excellent utilities to help with this.

The one I will focus on today is caching our CakePHP query results.  The key to this is, we are caching the results, not the queries themselves.  If you know databases well, you may be thinking, "why do I want to cache queries, doesn't my database server do this already?"  The answer to the question is, yes it does.  However, CakePHP still needs to call the database query and parse your results.  What I'm proposing, will avoid both of those steps and allow you to just retrieve the results.

This process not only avoids excess load on the database, it also reduces PHP's processing time that CakePHP has to do to provide you with such useful arrays.



Before we begin, I need to post a big disclaimer...BE VERY CAREFUL WITH WHAT DATA YOU CACHE!  As Peter Parker's Uncle Sam once told him, "With great power, comes great responsibility".  The same applies here.  Caching your data can be extremely useful, but it can be very bad if you cache data incorrectly.  You will begin seeing incorrect data appear, errors because the data is not what was expected, the list could go on and on.

Don't be scared though, we just need to use it correctly and we will have great success!

Step 1, create an app_model.php.  This file should live in the root of your "app" folder.  Below is an example of my app_model.php, it contains one function called find().

class AppModel extends Model {
 
    function find($conditions = null, $fields = array(), $order = null, $recursive = null) {
 $doQuery = true;

     // check if we want the cache
     if (!empty($fields['cache'])) {
  $cacheConfig = null;
  // check if we have specified a custom config, e.g. different expiry time
  if (!empty($fields['cacheConfig']))
      $cacheConfig = $fields['cacheConfig'];
 
  $cacheName = $this->name . '-' . $fields['cache'];
     
      // if so, check if the cache exists
      if (($data = Cache::read($cacheName, $cacheConfig)) === false) {
       $data = parent::find($conditions, $fields, $order, $recursive);
       Cache::write($cacheName, $data, $cacheConfig);
      }
      $doQuery = false;
     }

 if ($doQuery)
     $data = parent::find($conditions, $fields, $order, $recursive);
 return $data;
    }
 
}


The following code overrides the find() function in the main model class.  What it does is, it looks for an array key called "cache".  This is a new key that we are implementing.  If this key is found, we generate our cache name.  It's the modelName-cacheName.  We automatically append the model name to help prevent cross-table contamination incase we accidently used the same name twice!

We also look for another new key called cacheConfig.  If this exists, it allows us to specify a different config period for our data.  By default, it uses CakePHP's default caching.  I'm not exactly sure what it is, I think it's in the one week range.

We then proceed to read the cache with that name.  If it does not exist, we execute the query and save the results to the cache for next time.

If we wished to cache data for a shorter (or longer) period of time, we would create a new config item in our config/core.php file as shown:

Cache::config('short', array(
    'engine' => 'File',
    'duration'=> '+5 minutes',
    'probability'=> 100,
    'path' => CACHE . 'short' . DS,
      ));


The following code creates a config setting named "short".  We tell it to only cache our data for 5 minutes.  This is great for something on the homepage that we don't want to reload everytime, but don't want it to be cached for a long time either.

While we are in our config/core.php, it's a good idea to ensure the following line is uncommented:

Configure::write('Cache.check', true);


Ok, now we have everything setup to use, so how do we use it?  Good question, let's pretend we have a lookup table for our countries.  It's pretty safe to assume that we will not be changing the data fairly often, so we should cache the query results.  To do that, we do the following (this assumes we have a "Country" model)

// get country list using default config
$countries = $this->Country->find('list', array('cache' => 'countryList'));

// get country list using a custom config
$countries = $this->Country->find('list', array('cache' => 'countryList', 'cacheConfig' => 'long'));


The following code executes a straight forward find statement.  The first time it runs, CakePHP will execute the find query and parse the results.  The next time though, it will find the cached results and just return those to us.

To take this one extra step and make it even more useful, assuming we have a countries_controller.php file that allows us to add, edit, and delete countries.  We can easily update these three functions to remove our cached data.  This way, next time the countries are queried, it will not load the stale data, it will load the fresh data.

To accomplish this add the following line inside add, edit, and delete functions.  I would place it inside the if ($this->Country->save(...)) statement so we only do it on a successful action:

// we need to remove the status cache now
Cache::delete('Country-countryList');


It's important to note that I did NOT just use countryList like in the find statement.  Instead I prefixed the model name "Country" and a hypen as well.

That's all there is too it.  For good starting places, I would cache all of your "lookup" table queries, similar to the country list above.  From there, I'll let your imagination do the trick.  One piece of advice though, if you are caching a query that contains a "where id = $logged_in_user" (or any other conditional statements), be sure you include the $logged_in_user in the name of the "cahce" key, otherwise, you will load the wrong person's data!

Tags: CakePHP | Optimization | cache | find()

Related Posts

blog comments powered by Disqus