THIS CONTENT IS OUT OF DATE! IF YOU’RE USING CAKE 2, YOU SHOULDN’T READ THIS.

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