[eluser]sdbruder[/eluser]
I was toying with a simple HMVC scheme, using real controlers in libraries subdir (it works!), but stumbled in the following dilema: the output cache is by URI, not by controller, its not designed to be multi-controller aware.
SO.. Ive made my own Cacheable Controller. It uses the same cache infra-structure, and it caches
BY METHOD.
Im using what php calls 'overloading' (its not classic overloading, but fit my need here) to create cached versions of any method, so if I want to cache $mycontroller->index() I call $mycontroller->indexCached();
Works for any method.
Autoload it, extend your Controller from it and that's it. The same ideia can be easily adapted in other places as well, not only controllers.
PS: Not tested under PHP4.
First, an obvious example:
Ive made a simple menu MVC, one entry in the menu for each file under /PAGES/ subdir (if the page is static, the content of the static page is there).
For each page the menu controller scandir('/pages/') {ob_start(); include() it, ob_end_clean() to drop the content if its a static page, etcetera }
Wonderfully simple to operate after, but 'costly' to do it every page hit..
So...
Code:
class Menu extends CachedController {
function Menu() {
parent::CachedController();
$this->methodcache(60);
}
function listfiles($dir) {
// costly function
}
function index($dir = '/') {
$data['menu'] = $this->listfilesCached($dir); // here is the cache magic tnkx to __call()
$data['current'] = $_SERVER['REQUEST_URI'];
$ret = $this->load->view('menu_view',$data,TRUE);
return $ret;
}
}
Now the code per se:
Code:
<?php if (!defined('BASEPATH')) exit('No direct script access allowed');
/**
* Cached Controller, an Cached version of CI Controller
*
* @package>> CodeIgniter
* @author> > Sergio Devojno Bruder
*/
class CachedController extends Controller {
var $methodcache_expiration = 0;
function CachedController() {
parent::Controller();
}
function methodcache($time) {
$this->methodcache_expiration = ( ! is_numeric($time)) ? 0 : $time;
}
function _cache_write($id,&$output) {
if ( $this->methodcache_expiration <= 0 ) {
return FALSE;
}
$cache_path = $this->config->item('cache_path');
$cache_path = ($cache_path == '') ? BASEPATH.'cache/' : $cache_path;
if ( ! is_dir($cache_path) OR ! is_really_writable($cache_path)) {
return FALSE;
}
// Build the file path. The file name is an MD5 hash of the $id array
$idstr = '';
foreach($id as $e) {
$idstr .= $e.'::';
}
$filepath = $cache_path.md5($idstr);
if ( ! $fp = @fopen($filepath, 'wb')) {
log_message('error', "Unable to write cache file: ".$filepath);
return;
}
$expire = time() + ($this->methodcache_expiration * 60);
flock($fp, LOCK_EX);
fwrite($fp, $expire.'TS--->'.serialize($output));
flock($fp, LOCK_UN);
fclose($fp);
@chmod($filepath, 0777);
log_message('debug', "Cache file written: ".$filepath);
return TRUE;
}
function _cache($id,&$value) {
$cache_path = $this->config->item('cache_path');
$cache_path = ($cache_path == '') ? BASEPATH.'cache/' : $cache_path;
if ( ! is_dir($cache_path) OR ! is_really_writable($cache_path)) {
return FALSE;
}
// Build the file path. The file name is an MD5 hash of the $id array
$idstr = '';
foreach($id as $e) {
$idstr .= $e.'::';
}
$filepath = $cache_path.md5($idstr);
if ( ! @file_exists($filepath)) {
return FALSE;
}
if ( ! $fp = @fopen($filepath, 'rb')) {
return FALSE;
}
flock($fp, LOCK_SH);
$cache = '';
if (filesize($filepath) > 0) {
$cache = fread($fp, filesize($filepath));
}
flock($fp, LOCK_UN);
fclose($fp);
// Strip out the embedded timestamp>>
if ( ! preg_match("/(\d+TS--->)/", $cache, $match)) {
return FALSE;
}
// Has the file expired? If so we'll delete it.
if (time() >= trim(str_replace('TS--->', '', $match['1']))) {
@unlink($filepath);
log_message('debug', "Cache file has expired. File deleted");
return FALSE;
}
// Display the cache
$value = unserialize(str_replace($match['0'], '', $cache));
log_message('debug', "Cache file is current. Sending it to browser.");
return TRUE;
}
function __call ($member, $arguments) {
$fn_id = array($member,$arguments);
if (substr($member,-6) == 'Cached') {
$method_name = substr($member,0,-6);
if (method_exists($this,$method_name)) {
if ($this->methodcache_expiration > 0) {
if (!$this->_cache($fn_id, &$ret)) {
$ret = call_user_method_array($method_name, &$this, $arguments);
$this->_cache_write($fn_id, $ret);
}
} else {
$ret = call_user_method_array($method_name, &$this, $arguments);
}
return $ret;
} else {
throw new Exception("Call to undefined method ".get_class($this)."::{$method_name}()");
}
} else {
throw new Exception("Call to undefined method ".get_class($this)."::{$method_name}()");
}
}
}
?>