In a project i’m working on, being unaware of super behaviours or plugins that may appear on github, i made some search to find fast and effective solutions (or ideas) for the needs at hand. Namely, performance -> caching.
Needs stacked over time.
I liked the idea of single query cache. I needed ad hoc joins to perform complex filtering e reduce the number of queries per request. I had some future-publish feature. I needed to cache many paginated records.
For every need, i found a ready made solution (or almost ready, with a good implementation idea and examples):
- http://bakery.cakephp.org/articles/view/quick-tip-doing-ad-hoc-joins-in-model-find (tweaked to add hasMany relations)
- http://www.jotlab.com/2010/03/23/cakephp-caching-until-a-future-post
- http://www.jotlab.com/2010/02/09/pagination-caching-with-cakephp
- http://www.sanisoft.com/blog/2010/06/28/cakephp-cache-individual-queries
Great! Just one problem: many of these solutions implied a Model::find override (and the need to define Model::paginate and Model::paginateCount).
So, I took the ideas and big chunks of code, and tried to merge them together in a single working thing for my app.
I’m sharing this because 1- it may be useful for someone else, 2- i may get suggestions on a better implementation / alternative or bug’s / issues notices.
The code is specific to my case, i hope it’s clear and easily customizable (i.e. think of $this->site_id property as a group, we need to differentiate query cache for).
Methods in AppModel:
- <?php
-
- // Custom find
- // - 'macthes' for actually joining hasMany and Habtm models,
- // and use conditions on related models
- $join_type = 'inner';
-
- switch ($conditions) {
- case 'matches':
- }
-
- break;
- }
- // hack to filter over several HABTM tables
-
- foreach ($model_list as $model) {
-
- $assoc = $this->hasAndBelongsToMany[$model];
- $bind = "{$assoc['with']}.{$assoc['foreignKey']} = {$this->alias}.{$this->primaryKey}";
- 'table' => $assoc['joinTable'],
- 'alias' => $assoc['with'],
- 'type' => $join_type,
- 'foreignKey' => false,
- );
- $bind = $model . '.' . $this->{$model}->primaryKey . ' = ';
- $bind .= "{$assoc['with']}.{$assoc['associationForeignKey']}";
- 'table' => $this->{$model}->table,
- 'alias' => $model,
- 'type' => $join_type,
- 'foreignKey' => false,
- );
- }
-
- App::import('Inflector');
- $assoc = $this->hasMany[$model];
- $bind = "{$assoc['className']}.{$assoc['foreignKey']} = {$this->alias}.{$this->primaryKey}";
- 'table' => Inflector::tableize($assoc['className']),
- 'alias' => $assoc['className'],
- 'type' => $join_type,
- 'foreignKey' => false,
- );
- }
-
- break;
- }
- }
- }
-
- //
- // CACHE
- //
- $key = $options['cache']; // solitamente nella forma "modello.'-'.$this->id";
- $expires = '+1 day';
- $conf = 'sql';
-
- $key = $options['cache'][0];
- $conf = $options['cache'][1];
- }
- }
-
- // Set cache settings
- $this->_set_cache_config($conf);
-
- // Load from cache
- $results = Cache::read($key, $conf);
-
- $results = parent::find($conditions, $options, $order, $recursive);
- Cache::write($key, $results, $conf);
- }
- return $results;
- }
- // Not cacheing
-
- return parent::find ($conditions, $options, $order, $recursive);
-
- }
-
- protected function _set_cache_config ($conf) {
-
- $expires = $this->_set_expiry();
-
- $path = CACHE . 'sql'.DS. $conf;
-
- ///// create folder if not exists
- App::import('Folder');
- $Folder = new Folder($path, true); //, '755');
-
- 'engine' => 'File', // DA SPECIFICARE, non defult 'FIle' in cake 1.3
- 'path' => $path,
- //'prefix' => strtolower($this->name) .'-',
- 'duration' => $expires
- )
- );
-
- }
-
- protected function _set_expiry() {
- $expires = '+1 day';
- $expires = $next[$this->name]['published_date'];
-
- }
- }
-
- return $expires;
-
- }
-
- function delete_cache_data($name = null, $conf = 'sql') {
- if($conf == 'sql')
-
- //debug ($conf);
- $this->_set_cache_config($conf);
-
- if ($name) {
- Cache::delete($name, $conf);
- } else {
- Cache::clear(false, $conf);
- }
- }
-
- //
- // public function paginateCount($conditions = array(), $recursive = 0, $extra = array()) {
- // $parameters = compact('conditions');
- // if ($recursive != $this->recursive) {
- // $parameters['recursive'] = $recursive;
- // }
- //
- // if (isset($extra['type']) && ($extra['type'] == 'matches')) {
- // $extra['operation'] = 'count';
- // return $this->find('matches', array_merge($parameters, $extra));
- // }
- // else {
- // return $this->find('count', array_merge($parameters, $extra));
- // }
- //
- // }
-
-
- if ($recursive != $this->recursive) {
- $parameters['recursive'] = $recursive;
- }
-
- // spostato sotto..
- // if (isset($extra['type']) && ($extra['type'] == 'matches')) {
- // $extra['operation'] = 'count';
- // return $this->find('matches', array_merge($parameters, $extra));
- // }
- // else {
- // return $this->find('count', array_merge($parameters, $extra));
- // }
- if ((Configure::read('Cache.disable') === false) && (Configure::read('Cache.check') === true) && $this->usePaginationCache ) {
- //$key = $options['cache'];
- $expires = '+1 day';
- $conf = 'sql';
- }
- if ($this->usePaginationCache) {
- $this->_set_cache_config($conf);
- $uniqueCacheId = '';
- foreach ($args as $arg) {
- }
- $contain = $extra['contain'];
- }
- $joins = $extra['joins'];
- }
- $group = $extra['group'];
- }
-
- $paginationcount = Cache::read('paginationcount-'.$uniqueCacheId, $conf);
- }
- $extra['operation'] = 'count';
- }
- else {
- $paginationcount = $this->find('count', compact('conditions', 'contain','joins','group')); // , 'recursive'
- }
- if ($this->usePaginationCache)
- Cache::write('paginationcount-'.$uniqueCacheId, $paginationcount, $conf);
- }
- return $paginationcount;
-
- }
-
- function paginate ($conditions, $fields, $order, $limit, $page = 1, $recursive = null, $extra = array()) {
-
- if ((Configure::read('Cache.disable') === false) && (Configure::read('Cache.check') === true) && $this->usePaginationCache) {
- //$key = $options['cache'];
- $expires = '+1 day';
- $conf = 'sql';
- }
-
- $this->_set_cache_config($conf);
-
- if ($this->usePaginationCache) {
-
- $uniqueCacheId = '';
- foreach ($args as $arg) {
- }
- $pagination = Cache::read('pagination-'.$uniqueCacheId, $conf);
-
- }
-
- $contain = $extra['contain'];
- }
-
- $joins = $extra['joins'];
- }
- $group = $extra['group'];
- }
-
- $pagination = $this->find('all', compact('conditions', 'fields', 'order', 'limit', 'page', 'group', 'contain','joins','group')); //'recursive'
- if ($this->usePaginationCache)
- Cache::write( 'pagination-' . $uniqueCacheId, $pagination, $conf);
-
- }
-
- return $pagination;
-
- }
-
- public function afterSave($created, $key = null) {
-
- $conf = $this->_set_cache_config($conf);
-
- if($created) $name = null;
-
- if(!$created) {
- $name = $key;
- }
-
- $this->delete_cache_data ($name, $conf);
-
- $this->delete_cache_data ($name, $home_conf);
-
- parent::afterSave($created);
- }
-
- ?>
Query cache is called in controller actions, as in sanisoft example, by adding a the ‘cache’ key like this
‘cache’ => array(‘uniquequeryidentifier’, ‘configuration_name’)
Pagination caching is (in my case, i simply differentiate between frontend and admin) turned on/off by a model’s property (AppModel->usePaginationCache), but it could of course be implemented in other ways.