If you use CakePHP’s caching system but don’t like having to wrap you calls to Model::find() in if statements (“if cache result found… else…”) then this little quick tip is for you. Basically, we’re just going to put a slightly modified version of Model::find() in AppModel. Our new find() method will check for the existence of a ‘cache’ argument in the options array. Then, the function will either grab the query results from the cache or run a DB query, depending on what’s passed in the ‘cache’ argument.

A couple of notes:

  • Automatic caching only occurs if debug is set to 0.
  • The updated Model::find() function also implements Matt Curry’s version of custom find methods.
  • After writing this code a few months ago, I came across another implementation of the same idea on End Your If. The two methods are pretty similar, but we came to the results independently. Must mean it’s a pretty good system. ;)

We’re putting two methods into AppModel: find() and __getCachedResults(), a helper function. You can download the latest version of this code at my GitHub repository, or grab it from the bottom of this post.

Usage is simple:

  • Whenever you want to cache a Model::find() result, pass ‘cache’ as one of the method’s arguments.  Pass it as either a string or an array.
    • string: the name used to generate the cache key, which takes the format: model_alias_cache_key
    • array: two valid arguments: ‘name’, used as above, and ‘config’, optionally used to pass the name of the cache config to use. ‘default’ is used if not otherwise specified.
  • And that’s it! Enjoy the speed benefits, but use caching carefully. Pay attention to your cache results; only cache stuff that makes sense (for example, only cache user-specific stuff if you specify the user ID in the cache name).

The code:

<?php
class AppModel extends Model {
/**
   * Adds support for custom find methods (__findXX) and automatic caching.
   * Automatic caching will kick in when 'cache' is passed in the $options
   * array.
   *
   * If 'cache' is a string, then it will be used to generate the
   * cache name, which takes the format model_alias_cache_name.
   *
   * If 'cache' is an array, then two arguments are valid: 'name', required,
   * and 'config', optional. 'name' is used as above, while 'config'
   * determines the cache configuration to use - 'default' if not specified.
   */
  function find($type, $options = array()) {
      $results = $this->_getCachedResults($options);
      if (!$results) {
          $method = null;
          if (is_string($type)) {
              $method = sprintf('__find%s', Inflector::camelize($type));
          }

          if ($method && method_exists($this, $method)) {
              $results = $this->{$method}($options);
          } else {
              $args = func_get_args();
              $results = call_user_func_array(array('parent', 'find'), $args);
          }
          if ($this->useCache) {
              Cache::write($this->cacheName, $results, $this->cacheConfig);
          }
      }

      return $results;
  }

  function _getCachedResults($options) {
      $this->useCache = true;
      if (Configure::read('debug') > 0 || !is_array($options) || !isset($options['cache']) || $options['cache'] == false) {
          $this->useCache = false;
          return false;
      }

      if (is_string($options['cache'])) {
          $this->cacheName = $this->alias . '_' . $options['cache'];
      } else {
          if (!isset($options['cache']['name'])) {
              return false;
          }
          $this->cacheName = $this->alias . '_' . $options['cache']['name'];
          $this->cacheConfig = isset($options['cache']['config']) ? $options['cache']['config'] : 'default';
      }

      $results = Cache::read($this->cacheName, $this->cacheConfig);

      return $results;
  }
}
?>