| 1 |
<?php
|
| 2 |
|
| 3 |
/**
|
| 4 |
* function result cache
|
| 5 |
* alex at developmentseed dot org
|
| 6 |
*
|
| 7 |
* function passed gets cached on first call, subsequent calls pull
|
| 8 |
* the function result from cache. cache is being updated on cron time
|
| 9 |
*
|
| 10 |
* using function result cache makes sense, if you have functions that
|
| 11 |
* are slow but can do with periodic updates.
|
| 12 |
*
|
| 13 |
* @todo: test scalability
|
| 14 |
* the argument col. in result cache is TEXT, first 255 char indexed
|
| 15 |
*
|
| 16 |
*/
|
| 17 |
|
| 18 |
/**
|
| 19 |
* help func hook
|
| 20 |
*
|
| 21 |
* @param string $section
|
| 22 |
* @return string
|
| 23 |
*/
|
| 24 |
function resultcache_help($section='') {
|
| 25 |
|
| 26 |
$output = '';
|
| 27 |
|
| 28 |
switch ($section) {
|
| 29 |
case "admin/modules#description":
|
| 30 |
$output = t('Croned cache for function results that are text. Call resultcache_getresult(yourfunction, $yourarguments) to use it. (Note: Only MySQL supported.)');
|
| 31 |
break;
|
| 32 |
}
|
| 33 |
|
| 34 |
return $output;
|
| 35 |
}
|
| 36 |
|
| 37 |
function resultcache_settings() {
|
| 38 |
$form = array();
|
| 39 |
|
| 40 |
$form['resultcache_enabled'] = array(
|
| 41 |
'#type' => 'checkbox',
|
| 42 |
'#title' => t('Enable result cache'),
|
| 43 |
'#default_value' => variable_get('resultcache_enabled', 1),
|
| 44 |
'#description' => t('Turn on/off result caching on this site.'),
|
| 45 |
);
|
| 46 |
$form['resultcache_clear'] = array(
|
| 47 |
'#type' => 'markup',
|
| 48 |
'#value' => '<div>'.l(t('Clear entire result cache'), 'admin/resultcache/clear', array('title' => t('Clear entire result cache')), 'destination='.$_GET['q']).'</div>
|
| 49 |
<div>'.t('(Your site might slow down until the cache populates again)').'</div>',
|
| 50 |
);
|
| 51 |
$form['resultcache_refresh'] = array(
|
| 52 |
'#type' => 'markup',
|
| 53 |
'#value' => '<div>'.l(t('Refresh result cache'), 'admin/resultcache/refresh', array('title' => t('Refresh entire result cache')), 'destination='.$_GET['q']).'</div>
|
| 54 |
<div>'.t('(This recalculates all function results stored in the result cache)').'</div>',
|
| 55 |
);
|
| 56 |
return system_settings_form($form);
|
| 57 |
}
|
| 58 |
|
| 59 |
function resultcache_perm() {
|
| 60 |
return array('administer result cache');
|
| 61 |
}
|
| 62 |
|
| 63 |
function resultcache_menu($may_cache = FALSE) {
|
| 64 |
$items = array();
|
| 65 |
|
| 66 |
if ($may_cache) {
|
| 67 |
$items[] = array(
|
| 68 |
'path' => 'admin/resultcache/clear',
|
| 69 |
'title' => t('clear result cache'),
|
| 70 |
'callback' => 'resultcache_page_clear',
|
| 71 |
'access' => user_access('administer result cache'),
|
| 72 |
'type' => MENU_CALLBACK,
|
| 73 |
);
|
| 74 |
$items[] = array(
|
| 75 |
'path' => 'admin/resultcache/refresh',
|
| 76 |
'title' => t('clear result cache'),
|
| 77 |
'callback' => 'resultcache_page_refresh',
|
| 78 |
'access' => user_access('administer result cache'),
|
| 79 |
'type' => MENU_CALLBACK,
|
| 80 |
);
|
| 81 |
$items[] = array(
|
| 82 |
'path' => 'admin/settings/resultcache',
|
| 83 |
'title' => t('Result Cache Settings'),
|
| 84 |
'callback' => 'drupal_get_form',
|
| 85 |
'callback arguments' => 'resultcache_settings',
|
| 86 |
'access' => user_access('administer result cache'),
|
| 87 |
'type' => MENU_NORMAL_ITEM,
|
| 88 |
);
|
| 89 |
}
|
| 90 |
return $items;
|
| 91 |
}
|
| 92 |
|
| 93 |
/**
|
| 94 |
*Implementation of hook_keystroke()
|
| 95 |
*/
|
| 96 |
function resultcache_keystroke_info($op = 'list', $arg1 = '') {
|
| 97 |
$strokes = array(
|
| 98 |
'resultcache_keystroke_clear_cache' =>
|
| 99 |
array('title' => t('Result Cache: Clear Cache'),
|
| 100 |
'name' => 'resultcache_keystroke_clear_cache',
|
| 101 |
'type' => 'drupal_callback',
|
| 102 |
'callback' => 'admin/resultcache/clear'),
|
| 103 |
'resultcache_keystroke_refresh_cache' =>
|
| 104 |
array('title' => t('Result Cache: Refresh Cache'),
|
| 105 |
'name' => 'resultcache_keystroke_refresh_cache',
|
| 106 |
'type' => 'drupal_callback',
|
| 107 |
'callback' => 'admin/resultcache/refresh'),
|
| 108 |
);
|
| 109 |
|
| 110 |
if($op == 'list') {
|
| 111 |
return $strokes;
|
| 112 |
}
|
| 113 |
|
| 114 |
if($op == 'search' && in_array($arg1, array_keys($strokes))) {
|
| 115 |
return $strokes[$arg1];
|
| 116 |
}
|
| 117 |
return;
|
| 118 |
}
|
| 119 |
|
| 120 |
/**
|
| 121 |
* menu call back for clearing cache
|
| 122 |
*/
|
| 123 |
function resultcache_page_clear() {
|
| 124 |
resultcache_clear();
|
| 125 |
drupal_set_message(t('Cleared result cache'));
|
| 126 |
if (isset($_GET['destination'])) {
|
| 127 |
drupal_goto($_GET['destination']);
|
| 128 |
}
|
| 129 |
}
|
| 130 |
|
| 131 |
/**
|
| 132 |
* menu call back for refreshing cache
|
| 133 |
*/
|
| 134 |
function resultcache_page_refresh() {
|
| 135 |
timer_start('result_cache_refresh');
|
| 136 |
resultcache_refresh('result_cache_refresh');
|
| 137 |
drupal_set_message(t('Refreshed result cache (%time ms)', array('%time' => timer_read('result_cache_refresh'))));
|
| 138 |
timer_stop('result_cache_refresh');
|
| 139 |
if (isset($_GET['destination'])) {
|
| 140 |
drupal_goto($_GET['destination']);
|
| 141 |
}
|
| 142 |
}
|
| 143 |
|
| 144 |
/**
|
| 145 |
* cron hook
|
| 146 |
*
|
| 147 |
*/
|
| 148 |
function resultcache_cron() {
|
| 149 |
resultcache_refresh();
|
| 150 |
}
|
| 151 |
|
| 152 |
/**
|
| 153 |
* call to refresh all functions in cache
|
| 154 |
* todo: make optional incremental refresh for cron time
|
| 155 |
*/
|
| 156 |
function resultcache_refresh() {
|
| 157 |
$r = db_query("SELECT crid, lastused, function, arguments, lastupdated FROM {cache_result}");
|
| 158 |
while ($fr = db_fetch_object($r)) {
|
| 159 |
|
| 160 |
// recalculate once an hour
|
| 161 |
// todo: make this user configurable
|
| 162 |
if ( (time() - $fr->lastupdated) < 3600 )
|
| 163 |
continue;
|
| 164 |
|
| 165 |
// if cached result not used in 2 days, kill it - todo: make user configurable
|
| 166 |
if ((time() - $fr->lastused) > 86400*2) {
|
| 167 |
db_query("DELETE FROM {cache_result} WHERE crid = %d", $fr->crid);
|
| 168 |
}
|
| 169 |
else {
|
| 170 |
if (function_exists($fr->function)) {
|
| 171 |
$result = call_user_func_array($fr->function, unserialize($fr->arguments));
|
| 172 |
db_query("UPDATE {cache_result} SET result = '%s', lastupdated = %d WHERE crid = %d", serialize($result), time(), $fr->crid);
|
| 173 |
}
|
| 174 |
}
|
| 175 |
}
|
| 176 |
}
|
| 177 |
|
| 178 |
/**
|
| 179 |
* call this function the same way as you would call resultcache_getresult()
|
| 180 |
* for invalidating cache
|
| 181 |
*
|
| 182 |
*/
|
| 183 |
function resultcache_invalidate(){
|
| 184 |
$arguments = func_get_args();
|
| 185 |
$function = array_shift($arguments);
|
| 186 |
if (func_num_args() == 1)
|
| 187 |
$arguments = "";
|
| 188 |
db_query("DELETE FROM {cache_result} WHERE function = '%s' AND arguments = '%s'", $function, serialize($arguments));
|
| 189 |
}
|
| 190 |
|
| 191 |
/**
|
| 192 |
* clears entire cache
|
| 193 |
*/
|
| 194 |
function resultcache_clear(){
|
| 195 |
db_query("DELETE FROM {cache_result}");
|
| 196 |
}
|
| 197 |
|
| 198 |
/**
|
| 199 |
* wrap your function into this one for caching
|
| 200 |
*
|
| 201 |
* resultcache_getresult("functionname", $arg_1, $arg_2 ...)
|
| 202 |
*
|
| 203 |
* First time called, the original function is called an stored in the {cache_result} table
|
| 204 |
* successive calls will draw result from {cache_result}. so be sure to run cron regularly in order
|
| 205 |
* to have up to date function results.
|
| 206 |
*
|
| 207 |
*/
|
| 208 |
function resultcache_getresult() {
|
| 209 |
|
| 210 |
timer_start("getres");
|
| 211 |
$time = time();
|
| 212 |
|
| 213 |
$arguments = func_get_args();
|
| 214 |
$function = array_shift($arguments);
|
| 215 |
if (func_num_args() == 1) {
|
| 216 |
$arguments = "";
|
| 217 |
}
|
| 218 |
|
| 219 |
// return result here if result caching off
|
| 220 |
if (!variable_get('resultcache_enabled', 1)) {
|
| 221 |
return call_user_func_array($function, $arguments);
|
| 222 |
}
|
| 223 |
|
| 224 |
$r = db_query("SELECT crid, result FROM {cache_result} WHERE function = '%s' AND arguments = '%s'", $function, serialize($arguments));
|
| 225 |
if ($call = db_fetch_object($r)) {
|
| 226 |
|
| 227 |
$result = unserialize($call->result);
|
| 228 |
if ($result === false) {
|
| 229 |
|
| 230 |
// retrieval of cached result failed, delete cache entry and tell watchdog
|
| 231 |
// calculate function instead of pulling it from db
|
| 232 |
db_query("DELETE FROM {cache_result} WHERE function = '%s' AND arguments = '%s'", $function, serialize($arguments));
|
| 233 |
watchdog("resultcache", "could not unserialize result, function $function", WATCHDOG_ERROR);
|
| 234 |
}
|
| 235 |
else {
|
| 236 |
// successful retrieval. update lastused mark and
|
| 237 |
db_query("UPDATE {cache_result} SET lastused = %d, retrievelapse = %d WHERE crid = %d", $time, timer_read("getres"), $call->crid);
|
| 238 |
timer_stop("getres");
|
| 239 |
return $result;
|
| 240 |
}
|
| 241 |
|
| 242 |
}
|
| 243 |
|
| 244 |
// if called first time
|
| 245 |
// insert result into result table, cron will calculate functions in this table regularily
|
| 246 |
$result = call_user_func_array($function, $arguments);
|
| 247 |
|
| 248 |
// todo: apparantly some ยด` combinations can break unserialize()
|
| 249 |
// eliminate those combinations here
|
| 250 |
|
| 251 |
db_query("INSERT INTO {cache_result}(function, arguments, result, lastused, createlapse) VALUES('%s', '%s', '%s', %d, %d)",
|
| 252 |
$function, serialize($arguments), serialize($result), $time, timer_read("getres") );
|
| 253 |
timer_stop("getres");
|
| 254 |
|
| 255 |
return $result;
|
| 256 |
}
|
| 257 |
|