-
Notifications
You must be signed in to change notification settings - Fork 0
Improved Query Caching
[size=6][b][color=red][Edit: This article is still work in progress! No actual code publicly available yet, sorry.][/color][/b][/size]
The way CodeIgniter does Query Caching (namely Controller-based caching) [b]works fine with small and decentralized pages[/b] where all controllers are pretty much independent. But as soon as you have a [b]model that's shared[/b] by a handful of controllers, you [b]end up with a big mess[/b].
Just take a model for generating the data for a tag cloud that's displayed on every page. You would end up with dozens of duplicates and handling those caches would suck as hell.
[url=http://codeigniter.com/forums/viewthread/78146/]See this topic for discussion[/url]
I got pretty sick of this and thus I of re-wrote pretty much CI's entire caching storage mechanisms. Using my code CI now supports [b]several[/b] different ways to cache database queries.
[b]# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #[/b]
[size=4][color=red][b]Note: [/b][/color][/size]
-
Functions ending with [b]"cache_single(…)"[/b] have parameters and run [b]$this->db->cache_off()[/b] automatically once a query has been sent. [i](This is due to some technical limitations and might get fixed in future.)[/i]
-
Functions ending with [b]"cache_on(…)"[/b] have no parameters (they gather all necessary data themselves). They need to be turned of manually by running [b]$this->db->cache_off()[/b]. [i](They work just the way like CI's [b]"$this->db->cache_on()"[/b] used to)[/i]
[b]# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #[/b]
[size=6]1. [b]Controller[/b][/size]
[color=orange][b]Purpose:[/b][/color] For controllers-centric and fairly [b]simple projects[/b] with mostly independent controllers. [color=green][i]This is just what you know from CI 1.6.x.[/i][/color]
[color=orange][b]Where to use it:[/b][/color] In [b]Controllers[/b].
[color=orange][b]File Storage:[/b][/color] [color=blue]application / cache_database / [b]controller/ + controller + function + md5hash[/b][/color] [i](Folder is created automatically)[/i]
[color=orange][b]Sample Code:[/b][/color]
[size=4][b]Activating Controller Caches:[/b][/size] [code]<?php
//Create cache for current controller function: $this->db->cache_on(); //alias to controller_cache_on() //or better: $this->db->controller_cache_on();
?>[/code]
[size=4][b]Clearing Controller Caches:[/b][/size] [code]<?php
//Delete cache of current controller function: $this->db->cache_delete(); //alias to delete_controller_cache() $this->db->controller_cache_delete();
//Delete cache of custom defined controller $this->db->controller_cache_delete($model_name);
//Delete cache of custom defined controller function: $this->db->controller_cache_delete($model_name, $function_name);
?>[/code]
[b]# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #[/b]
[size=6]2. [b]Model[/b][/size]
[color=orange][b]Purpose:[/b][/color] For model-centric and [b]semi-complex projects[/b] with multiple controllers sharing the same model.
[color=orange][b]Where to use it:[/b][/color] In [b]Models[/b].
[color=orange][b]File Storage:[/b][/color] [color=blue]application / cache_database / [b] model/ + model + function + md5hash[/b][/color] [i](Folder is created automatically)[/i]
[color=orange][b]Sample Code:[/b][/color]
[size=4][b]Activating Model Caches:[/b][/size] [code]<?php
//Create cache for current model: $this->db->model_cache_single(CLASS);
//Create cache for current model function: $this->db->model_cache_single(CLASS,FUNCTION);
//Create cache for custom model: $this->db->model_cache_single($model_name);
//Create cache for custom defined model function: $this->db->model_cache_single($model_name, $function_name);
?>[/code]
[size=4][b]Clearing Model Caches:[/b][/size] [code]<?php
//Delete cache of current model $this->db->model_cache_delete(CLASS);
//Delete cache of current model function: $this->db->model_cache_delete(CLASS,FUNCTION);
//Delete cache of custom defined model: $this->db->model_cache_delete($model_name);
//Delete cache of custom defined model function: $this->db->model_cache_delete($model_name, $function_name);
?>[/code]
[b]# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #[/b]
[size=6]3. [b]Database Table[/b][/size]
[color=orange][b]Purpose:[/b][/color] For all of us who want the [b]best possible performance[/b] and [b]most advanced caching management[/b].
[color=orange][b]Where to use it:[/b][/color] Wherever you need to.
[color=orange][b]File Storage:[/b][/color] [color=blue]application / cache_database / [b]table/ + table1 + table2 + table3 + md5hash[/b][/color] [i](Folder is created automatically)[/i]
[color=red][b]Important:[/b][/color] [b]Active Record Queries support automatic table detection[/b] while [b]custom queries require tablenames to be provided manually[/b]. (This might get improved, though)
[color=orange][b]Sample Code:[/b][/color]
[size=4][b]Activating Table Caches:[/b][/size] [code]<?php
//Create cache for queries handling with one single table: $this->db->table_cache_single($table_name);
//Create cache for queries handling with multiple tables: $this->db->table_cache_single(array($table_name_1, $table_name_2));
?>[/code]
[size=4][b]Clearing Table Caches:[/b][/size] [code]<?php
//Delete all caches containing data from a particular table: $this->db->table_cache_delete($table_name);
?>[/code]
[b]# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #[/b]
[size=6]4. [b]Key-Value[/b][/size]
[color=orange][b]Purpose:[/b][/color] For a [b]lightweight[/b] but [b]useful[/b] caching option.
[color=orange][b]Where to use it:[/b][/color] Wherever you need to.
[color=orange][b]File Storage:[/b][/color] [color=blue]application / cache_database / [b]key / + key + md5hash[/b][/color] [i](Folder is created automatically)[/i]
[color=orange][b]Sample Code:[/b][/color]
[size=4][b]Activating Key Caches:[/b][/size] [code]<?php
//Create cache for a particular key: $this->db->key_cache_single($key_name);
?>[/code]
[size=4][b]Clearing Key Caches:[/b][/size] [code]<?php
//Delete all caches associated with a particular key: $this->db->key_cache_delete($key_name);
?>[/code]
[b]# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #[/b]
[size=4][b]Turn Caching off:[/b][/size] [code]<?php
$this->db->cache_off();
?>[/code]
[size=4][b]Delete cache files of a all caching mode:[/b][/size] [code]<?php
$this->db->cache_delete_all();
?>[/code]
[size=4][b]Delete cache files of a particular caching mode:[/b][/size] [code]<?php
$this->db->cache_delete_all($mode); //$mode can either be "controller", "model", "table" or "key". ?>[/code]
[b]# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #[/b]
[size=4][b]These are the functions I either added or modified in DB_driver.php:[/b][/size]
[code]function CI_DB_driver($params) function controller_cache_on() function controller_cache_single($controller_name = NULL, $controller_function = NULL) function model_cache_single($model_name = NULL, $model_function = NULL) function table_cache_single($table_names = NULL) function ar_table_cache_on($table_names = array()) function key_cache_on($key_name = NULL) function controller_cache_delete($controller_name = '', $controller_function = '') function model_cache_delete($model_name = '', $model_function = '') function table_cache_delete($table_name = '') function key_cache_delete($key_name = NULL) function cache_on() //Deprecated function cache_off() function cache_delete($segment_one = '', $segment_two = '') //Deprecated function cache_delete_all()[/code]
[size=4][b]And these are the functions I either added or modified in DB_cache.php:[/b][/size]
[code]function read($sql) function write($sql, $object) function _get_tables_from_ar() function delete_cache_files($mode = NULL, $criteria = NULL, $cache_dir = NULL, &$cache_files = array()) function delete($segment_one = '', $segment_two = '') function delete_all($mode = '')[/code]
[b]# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #[/b]
Now for the actual code:
[size=4][b]DB_cache.php:[/b][/size] [code]<?php if (!defined('BASEPATH')) exit('No direct script access allowed'); /**
- CodeIgniter
- An open source application development framework for PHP 4.3.2 or newer
- @package CodeIgniter
- @author ExpressionEngine Dev Team
- @copyright Copyright (c) 2006, EllisLab, Inc.
- @license http://codeigniter.com/user_guide/license.html
- @link http://codeigniter.com
- @since Version 1.0
- @filesource */
// ------------------------------------------------------------------------
/**
-
Database Cache Class
-
@category Database
-
@author ExpressionEngine Dev Team
-
@link http://codeigniter.com/user_guide/database/ */ class CI_DB_Cache {
var $CI;
/**
- Constructor
- Grabs the CI super object instance so we can access it.
*/
function CI_DB_Cache() { // Assign the main CI object to $this->CI // and load the file helper since we use it a lot $this->CI =& get_instance(); $this->CI->load->helper('file');
}// --------------------------------------------------------------------
/**
-
Set Cache Directory Path
-
@access public
- @param string the path to the cache directory
-
@return bool */
function check_path($path = '') { if ($path == '') { if ($this->CI->db->cachedir == '') { return $this->CI->db->cache_off(); }$path = $this->CI->db->cachedir;
}
// Add a trailing slash to the path if needed
$path = preg_replace("/(.+?)/*$ /", "\1/", $path);if ( ! is_dir($path) OR ! is_really_writable($path)) { // If the path is wrong we'll turn off caching return $this->CI->db->cache_off(); }
$this->CI->db->cachedir = $path; return TRUE; }
// --------------------------------------------------------------------
/**
-
Retrieve a cached query
-
The URI being requested will become the name of the cache sub-folder.
-
An MD5 hash of the SQL statement will become the cache file name
-
@access public
-
@return string */ function read($sql) { $cache_mode = $this->CI->db->cache_mode; $cache_id = $this->CI->db->cache_ids->$cache_mode;
if ($cache_mode == 'ar_table') { $this->_get_tables_from_ar(); }
$cache_mode = ($cache_mode == 'ar_table') ? 'table' : $cache_mode;
$dir_path = $this->CI->db->cachedir.'/'.$cache_mode.'/'; $file_name = $cache_id.md5($sql);
if ( ! @is_dir($dir_path)) { return FALSE; }
if (FALSE === ($cachedata = read_file($dir_path.$file_name))) {
return FALSE; }return unserialize($cachedata);
}
// --------------------------------------------------------------------
/**
-
Write a query to a cache file
-
@access public
-
@return bool */ function write($sql, $object) { $cache_mode = $this->CI->db->cache_mode; $cache_id = $this->CI->db->cache_ids->$cache_mode;
if ($cache_mode == 'ar_table') { $this->_get_tables_from_ar(); }
$cache_mode = ($cache_mode == 'ar_table') ? 'table' : $cache_mode;
$dir_path = $this->CI->db->cachedir.'/'.$cache_mode.'/'; $file_name = $cache_id.md5($sql);
if ( ! @is_dir($dir_path)) { if ( ! @mkdir($dir_path, 0777)) { return FALSE; }
@chmod($dir_path, 0777);
}
if (write_file($dir_path.$file_name, serialize($object)) === FALSE) { return FALSE; }
@chmod($dir_path.$file_name, 0777);
return TRUE; }
// --------------------------------------------------------------------
/**
-
Detect Tables from Active Record
-
@access public
-
@return bool */ function _get_tables_from_ar() { if ($this->CI->db->cache_mode == 'table' && !empty($this->CI->db->ar_from)) { $ar_tables = array();
foreach ($this->CI->db->ar_from as $table) { $ar_tables[] = substr($table,1,strlen($table)-2); } $this->CI->db->cache_ids->table = '+'.implode('+',$ar_tables).'+';
} return TRUE; }
// --------------------------------------------------------------------
/**
-
Find cache files matching the criteria within a particular directory
-
@access public
-
@param array the names that were used for caching (required)
-
@param string the directory to be scanned (required)
-
@param array array of already found cache files (optional)
-
@return bool */ function delete_cache_files($mode = NULL, $criteria = NULL, $cache_dir = NULL, &$cache_files = array()) {
if ( ! @is_dir($cache_dir)) { return FALSE; } if ($handle = opendir($cache_dir)) { while (false !== ($file = readdir($handle))) { //Ignore items starting with '.' if (strncasecmp($file,'.',1)) { //If it's a file and matches the criteria, then add it to the array if (is_file($cache_dir.$file)) {
//Tables do not need to be found at offset 0 if ($mode == 'table') { if (strpos($file,$criteria) !== FALSE) { $cache_files[] = $cache_dir.$file; } } else //Everything else does need to be found at offset 0 { if (strpos($file,$criteria) === 0) { $cache_files[] = $cache_dir.$file; } } } else if (is_dir($cache_dir.$file)) { //Scan subdir for cache files $this->delete_cache_files($mode, $criteria, $cache_dir.$file.'/', $cache_files); } } } closedir($handle); }foreach ($cache_files as $cache_file) { if (FALSE === unlink($cache_file)) { return FALSE; } }
return $cache_files; }
// --------------------------------------------------------------------
/**
- Delete cache files within a particular directory
- @access public
- @return bool
*/
function delete($segment_one = '', $segment_two = '')
{
$this->CI->db->delete_controller_cache($segment_one = '', $segment_two = ''); }
// --------------------------------------------------------------------
/**
- Delete all existing cache files
- @access public
- @return bool */ function delete_all($mode = '') { $mode = (empty($mode)) ? '' : $mode.'/'; delete_files($this->CI->db->cachedir.$mode, TRUE); }
}
?>[/code]
[size=4][b]DB_driver.php:[/b][/size] [code][/code]