<?php
namespace Lia\Compo;
class Cache extends \Lia\Compo {
/**
*
* - Consider adding defaultExpiry as a config, rather than a default paramater in the cacheFile() function call
* - Add a key=>value cache that returns values instead of file paths
* - probably auto-delete if an expired file is requested
* - Possibly add `lia.cacheDir.public` & `lia.cacheDir.public.baseUrl` to assist in routing
* - Are these the same thing?
* - Consider function to return file content, not just file path. Consider additional decoding like parsing json or yaml.
* - Consider adding type-based processing (PHP, JSON, DOMDocument??)
* - Consider extensible features (such as auto en/decoding additional types not supported by Liaison)
* - cache response of getCacheFile() so files don't have to be loaded more than once
*
* @export(TODO.Cache)
*/
public function onStart(){
parent::onStart();
if ($this->lia->hasApi('lia.conf')){
$this->lia->api('lia.conf', 'default', 'lia.cacheDir', $this->package->dir('cache'));
// delete stuff every 5 days
$this->lia->api('lia.conf', 'default', 'lia.Cache.clearAfterXMinutes', 60*24*5);
}
}
public function onEmit_Server_responseSent(){
if ($this->isStaleCacheCheckRequired()){
$this->deleteStaleCacheFiles();
}
}
public function apiDeprecated_lia_cache_getCacheFile($key){
$path = $this->lia->getCachedFilePath($key);
return $path;
}
/**
*
* @param string $key
* @return boolean|string, false when cache file is not found or is stale, else absolute path to the cache file
*/
public function apiGet_lia_cache_getCachedFilePath(string $key){
$dir = $this->lia->get('lia.cacheDir');
$file = $dir.'/file-'.$key;
$metaFile = $dir.'/meta-'.$key;
if (file_exists($file)
&&file_exists($metaFile)){
$meta = json_decode(file_get_contents($metaFile),true);
if (time()>$meta['expiry']){
//@TODO Auto-delete stale cache files when requested
return false;
}
return $file;
} else {
return false;
}
}
/**
* Store the given $value in a file identified by $key
* Returns the path to the file
*/
public function apiSet_lia_cache_cacheFile($key, $value, $maxAge=60*60*24*5){
$dir = $this->lia->get('lia.cacheDir');
$file = $dir.'/file-'.$key;
$metaFile = $dir.'/meta-'.$key;
$parent = $dir;
$hasDir = true;
while (!is_dir($parent)){
$hasDir = false;
$parent = dirname($parent);
}
$perms = fileperms($parent);
if (!$hasDir)mkdir($dir, $perms, true);
file_put_contents($file,$value);
$expiry = time() + $maxAge;
$meta = [
'expiry'=>$expiry,
];
file_put_contents($metaFile,json_encode($meta));
return $file;
}
protected function isStaleCacheCheckRequired(){
// get cache-clearing frequency (lia config)
$frequencyInMinutes = $this->lia->get('lia.Cache.clearAfterXMinutes');
$seconds = $frequencyInMinutes*60;
// check when last cache-clear was performed
$path = $this->lia->getCachedFilePath('lia.Cache.LastCleanupCheck');
if ($path==null||!is_file($path)){
$lastClear = 0;
} else if (is_file($path)){
$content = file_get_contents($path);
$content = trim($content);
$lastClear = (int)$content;
} else {
throw new \Lia\Exception\Base("Could not load cache frequency file.");
}
// if enough time has elapsed, clear the cache
if ($lastClear + $seconds > time())return false;
return true;
}
protected function deleteStaleCacheFiles(){
$this->lia->cacheFile('lia.Cache.LastCleanupCheck', time(), 60*60*24*180);
$dir = $this->lia->get('lia.cacheDir');
if (!is_dir($dir))throw new \Lia\Exception\Base("The cache directory '{$dir}' does not exist, thus cannot be checked for stale files.");
$dh = opendir($dir);
$openLen = strlen('{"expiry":');
$endLen = -1 * strlen('}');
$now = time();
while (($f = readdir($dh)) !== false){
if (substr($f,0,5)!='meta-')continue;
$content = trim(file_get_contents($dir.'/'.$f));
$expiresAt = substr($content, $openLen, $endLen);
if ($expiresAt>$now)continue;
$actualFile = $dir.'/file-'.substr($f, 5);
if (is_file($actualFile))unlink($actualFile);
//delete the meta file
unlink($dir.'/'.$f);
}
closedir($dh);
}
}