yf_cache classCaching layer.
| File | plugins/cache/classes/yf_cache.class.php |
|---|---|
| Use | _class('cache') |
A site sets them in its config with $PROJECT_CONF['cache']['NAME'] = ...;
| Property | Default | Description |
|---|---|---|
$AUTO_CACHE_NSbool |
false |
Force cache class to generate unique namespace, based on project_path. Usually needed to separate projects within same cache storage (memcached as example) |
$CACHE_NSstring |
'' |
Namespace for drivers other than 'file' |
$DRIVERstring |
'memcache' |
Cache driver to use |
$FILES_TTL |
null |
|
$FORCE_REBUILD_CACHEbool |
false |
Forcing to delete elements |
$NO_CACHEbool |
false |
Allows to turn off cache at any moment. Useful for unit tests and complex situations. |
$RANDOM_TTL_ADDbool |
true |
Add random value for each entry TTL (to avoid one-time cache invalidation problems) |
$REMOVED_DRIVERSarray |
|
Drivers deleted in yf 3.9: name => reason, a requested one warns before the usual "no driver" fallback |
$TTLint |
3600 |
Cache entries time-to-live (in seconds) |
$_NO_CACHE_WHY |
false |
|
$_driver |
null |
|
$_driver_ok |
false |
|
$_extend |
[] |
|
$_init_complete |
false |
|
$_no_cache |
[] |
|
$_tried_to_connect |
null |
public function clean($name = '')
Clean selected cache entry (alias).
public function clean($name = '')
{
return $this->del($name);
}
public function clean_all()
Clean all cache entries (alias).
public function clean_all()
{
return $this->flush();
}
public function clear($name = '')
Clean selected cache entry (alias).
public function clear($name = '')
{
return $this->del($name);
}
public function clear_all()
Clean all cache entries (alias).
public function clear_all()
{
return $this->flush();
}
public function del($name)
Delete selected cache entry.
public function del($name)
{
if ($name === false || $name === null) {
return null;
}
$do_real_work = true;
if ( ! $this->_driver_ok) {
$do_real_work = false;
}
if (is_array($name)) {
return $this->multi_del($name);
}
$time_start = null;
if (DEBUG_MODE) {
$time_start = microtime(true);
}
$key_name_ns = $this->CACHE_NS . $name;
$result = null;
if ($do_real_work) {
$result = $this->_driver->del($key_name_ns);
}
DEBUG_MODE && debug('cache_' . __FUNCTION__ . '[]', [
'name' => $name,
'name_real' => $key_name_ns,
'driver' => $this->DRIVER,
'time' => round(microtime(true) - $time_start, 5),
'did_real_work' => (int) $do_real_work,
]);
return $result;
}
public function del_by_prefix($prefix = '')
public function del_by_prefix($prefix = '')
{
$do_real_work = true;
if ( ! $this->_driver_ok) {
$do_real_work = false;
}
$time_start = null;
if (DEBUG_MODE) {
$time_start = microtime(true);
}
$result = null;
if ($do_real_work) {
if ( ! strlen($prefix) || ! is_string($prefix)) {
$result = $this->flush();
} else {
$result = false;
$prefix_len = strlen($prefix);
$keys = $this->list_keys();
if ($keys === null) {
$result = $this->flush();
} elseif ($keys) {
foreach ($keys as $k => $v) {
if (substr($v, 0, $prefix_len) !== $prefix) {
unset($keys[$k]);
}
}
if ($keys) {
$result = $this->multi_del($keys);
} else {
$result = true;
}
}
}
}
DEBUG_MODE && debug('cache_' . __FUNCTION__ . '[]', [
'prefix' => $prefix,
'data' => $result,
'driver' => $this->DRIVER,
'time' => microtime(true) - $time_start,
'did_real_work' => (int) $do_real_work,
]);
return $result;
}
public function flush()
Clean all cache entries.
public function flush()
{
$do_real_work = true;
if ( ! $this->_driver_ok) {
$do_real_work = false;
}
$time_start = null;
if (DEBUG_MODE) {
$time_start = microtime(true);
}
$result = null;
if ($do_real_work) {
$result = $this->_driver->flush();
}
DEBUG_MODE && debug('cache_' . __FUNCTION__ . '[]', [
'data' => $result,
'driver' => $this->DRIVER,
'time' => microtime(true) - $time_start,
'did_real_work' => (int) $do_real_work,
]);
return $result;
}
public function get($name, $force_ttl = 0, array $params = [])
Get data from cache.
$params array public function get($name, $force_ttl = 0, array $params = [])
{
if ($name === false || $name === null || ! is_string($name)) {
return null;
}
$do_real_work = true;
if ( ! $this->_driver_ok || empty($name) || $this->NO_CACHE) {
$do_real_work = false;
}
if ($this->FORCE_REBUILD_CACHE) {
$this->del($name);
$do_real_work = false;
}
$time_start = null;
if (DEBUG_MODE) {
$time_start = microtime(true);
}
$key_name_ns = $this->CACHE_NS . $name;
$result = null;
if ($do_real_work) {
$result = $this->_driver->get($key_name_ns, $force_ttl, $params);
}
DEBUG_MODE && debug('cache_' . __FUNCTION__ . '[]', [
'name' => $name,
'name_real' => $key_name_ns,
'data' => $result,
'driver' => $this->DRIVER,
'params' => $params,
'force_ttl' => $force_ttl,
'time' => round(microtime(true) - $time_start, 5),
'trace' => main()->trace_string(),
'did_real_work' => (int) $do_real_work,
]);
if (($_GET['refresh_cache'] ?? false) && $this->_url_action_allowed('refresh_cache')) {
return null;
}
return $result;
}
public function getset($name, callable $func, $ttl = 0, array $params = [])
Get data from cache, if data is empty - then execute callback and store result in cache.
$func callable$params array public function getset($name, callable $func, $ttl = 0, array $params = [])
{
if ($name === false || $name === null || ! is_string($name)) {
return null;
}
$result = null;
if ( ! @$params['force']) {
$result = $this->get($name, $ttl, $params);
}
if (($result ?? null) === null) {
$result = $func($name, $ttl, $params, $this);
if ($result !== null) {
$this->set($name, $result, $ttl);
}
}
return $result;
}
public function list_keys()
public function list_keys()
{
$do_real_work = true;
if ( ! $this->_driver_ok) {
$do_real_work = false;
}
$time_start = null;
if (DEBUG_MODE) {
$time_start = microtime(true);
}
if ( ! ($this->_driver->implemented['list_keys'] ?? null)) {
$do_real_work = false;
}
$result = null;
if ($do_real_work) {
$result = $this->_driver->list_keys();
if ($this->CACHE_NS && is_array($result) && count((array) $result)) {
$ns_len = strlen($this->CACHE_NS);
foreach ($result as $k => $v) {
if (substr($v, 0, $ns_len) !== $this->CACHE_NS) {
unset($result[$k]);
} else {
$result[$k] = substr($v, $ns_len);
}
}
}
if (is_array($result) && count((array) $result)) {
asort($result);
$result = array_values($result);
} else {
$result = null;
}
}
DEBUG_MODE && debug('cache_' . __FUNCTION__ . '[]', [
'data' => $result,
'driver' => $this->DRIVER,
'time' => microtime(true) - $time_start,
'did_real_work' => (int) $do_real_work,
]);
return $result;
}
public function multi_del($names = [])
Del several cache entries at once.
public function multi_del($names = [])
{
$do_real_work = true;
if ( ! $this->_driver_ok) {
$do_real_work = false;
}
$time_start = null;
if (DEBUG_MODE) {
$time_start = microtime(true);
}
$result = null;
if ($do_real_work) {
$old_names = $names;
$failed = false;
$implemented = $this->_driver->implemented['multi_del'] ?? null;
if ($implemented) {
// Fix names prefix
foreach ((array) $names as $k => $name) {
$names[$k] = $this->CACHE_NS . $name;
}
$result = $this->_driver->multi_del($names);
if ( ! $result) {
$failed = true;
}
}
if ( ! $implemented || $result === null) {
$names = $old_names;
foreach ((array) $names as $name) {
if ( ! $this->del($name)) {
$failed = true;
}
}
}
$result = $failed ? null : true;
}
DEBUG_MODE && debug('cache_' . __FUNCTION__ . '[]', [
'names' => $names,
'data' => $result,
'driver' => $this->DRIVER,
'time' => microtime(true) - $time_start,
'did_real_work' => (int) $do_real_work,
]);
return $result;
}
public function multi_get($names = [], $force_ttl = 0, $params = [])
Get several cache entries at once.
public function multi_get($names = [], $force_ttl = 0, $params = [])
{
$do_real_work = true;
if ( ! $this->_driver_ok || $this->NO_CACHE) {
$do_real_work = false;
}
$time_start = null;
if (DEBUG_MODE) {
$time_start = microtime(true);
}
if ( ! empty($this->_no_cache)) {
foreach ((array) $names as $k => $name) {
if (isset($this->_no_cache[$name])) {
unset($names[$k]);
}
}
}
$result = null;
if ($do_real_work) {
if ($this->_driver->implemented['multi_get'] ?? null) {
// Fix names prefix
$p_len = strlen($this->CACHE_NS);
foreach ((array) $names as $k => $name) {
$names[$k] = $this->CACHE_NS . $name;
}
$result = $this->_driver->multi_get($names, $force_ttl, $params);
// Fix names prefix
foreach ((array) $result as $name => $val) {
$result[substr($name, $p_len)] = $val;
unset($result[$name]);
}
} else {
$result = [];
foreach ((array) $names as $name) {
$res = $this->get($name, $force_ttl, $params);
if (isset($res)) {
$result[$name] = $res;
}
}
}
if ( ! is_array($result) || ! count((array) $result)) {
$result = null;
}
}
DEBUG_MODE && debug('cache_' . __FUNCTION__ . '[]', [
'names' => $names,
'data' => $result,
'driver' => $this->DRIVER,
'time' => microtime(true) - $time_start,
'did_real_work' => (int) $do_real_work,
]);
return $result;
}
public function multi_set($data = [], $ttl = 0)
Set several cache entries at once.
public function multi_set($data = [], $ttl = 0)
{
$do_real_work = true;
if ( ! $this->_driver_ok || $this->NO_CACHE) {
$do_real_work = false;
}
$time_start = null;
if (DEBUG_MODE) {
$time_start = microtime(true);
}
if ( ! empty($this->_no_cache)) {
foreach ((array) $this->_no_cache as $name => $tmp) {
if (isset($data[$name])) {
unset($data[$name]);
}
}
}
$result = null;
if ($do_real_work) {
if ($this->_driver->implemented['multi_set'] ?? null) {
// Fix names prefix
foreach ((array) $data as $name => $_data) {
$data[$this->CACHE_NS . $name] = $_data;
unset($data[$name]);
}
$result = $this->_driver->multi_set($data, $ttl);
} else {
$failed = false;
foreach ((array) $data as $name => $_data) {
if ( ! $this->set($name, $_data, $ttl)) {
$failed = true;
}
}
$result = ! $failed;
}
if ( ! $result) {
$result = null;
}
}
DEBUG_MODE && debug('cache_' . __FUNCTION__ . '[]', [
'data' => $data,
'driver' => $this->DRIVER,
'time' => microtime(true) - $time_start,
'did_real_work' => (int) $do_real_work,
]);
return $result;
}
public function put($name = '', $data = null, $ttl = 0)
Put data into cache (alias for 'set').
public function put($name = '', $data = null, $ttl = 0)
{
return $this->set($name, $data, $ttl);
}
public function refresh($name = '')
Delete selected cache entry (alias).
public function refresh($name = '')
{
return $this->del($name);
}
public function refresh_all()
Clean all cache entries (alias).
public function refresh_all()
{
return $this->flush();
}
public function set($name, $data, $ttl = 0)
Set data into cache.
public function set($name, $data, $ttl = 0)
{
if ($name === false || $name === null) {
return null;
}
$do_real_work = true;
if ( ! $this->_driver_ok || $this->NO_CACHE || ( ! is_array($name) && ($this->_no_cache[$name] ?? null))) {
$do_real_work = false;
}
if (is_array($name)) {
return $this->multi_set($name, $data);
}
$time_start = null;
if (DEBUG_MODE) {
$time_start = microtime(true);
}
$ttl = (int) ($ttl ?: $this->TTL);
if ($this->RANDOM_TTL_ADD) {
$ttl += mt_rand(1, 15);
}
$key_name_ns = $this->CACHE_NS . $name;
$result = null;
if ($do_real_work) {
$result = $this->_driver->set($key_name_ns, $data, $ttl);
}
DEBUG_MODE && debug('cache_' . __FUNCTION__ . '[]', [
'name' => $name,
'name_real' => $key_name_ns,
'data' => $data,
'driver' => $this->DRIVER,
'ttl' => $ttl,
'time' => round(microtime(true) - $time_start, 5),
'trace' => main()->trace_string(),
'did_real_work' => (int) $do_real_work,
]);
return $result;
}
_ hooks and helpers; not routable as page actionspublic function __call($name, $args)
Catch missing method call.
public function __call($name, $args)
{
$self = main()->get_class_name($this);
$func = null;
if (isset($this->_extend[$name])) {
$func = $this->_extend[$name];
} elseif (isset(main()->_extend[$self][$name])) {
$func = main()->_extend[$self][$name];
}
if ($func) {
return $func($args[0], $args[1], $args[2], $args[3], $this);
}
// Support for driver-specific methods
if (is_object($this->_driver)) {
return call_user_func_array([$this->_driver, $name], $args);
}
trigger_error(__CLASS__ . ': No method ' . $name, E_USER_WARNING);
return false;
}
public function __clone()
public function __clone()
{
// Reset internal state to class defaults (unset() would turn them into dynamic properties)
$defaults = get_class_vars(get_class($this));
foreach ((array) get_object_vars($this) as $k => $v) {
if ($k[0] == '_') {
$this->$k = null; // not unset: the clone reads _init_complete etc before _init() runs
}
}
}
public function _clear_cache_files()
Clears all cache entries (alias).
public function _clear_cache_files()
{
return $this->flush();
}
public function _connect($params = [])
public function _connect($params = [])
{
if ( ! $this->DRIVER) {
return null;
}
if (isset($this->_tried_to_connect)) {
return $this->_driver;
}
$this->_driver = null;
$this->_driver_ok = false;
$driver = $this->_set_current_driver($params);
if ($driver) {
$this->_driver = _class('cache_driver_' . $driver, 'classes/cache/');
$this->_driver_ok = $this->_driver->is_ready();
$implemented = [];
foreach (get_class_methods($this->_driver) as $method) {
if ($method[0] != '_') {
$implemented[$method] = $method;
}
}
$this->_driver->implemented = $implemented;
$this->_driver->_parent = $this;
} else {
trigger_error('CACHE: empty driver name, will not use cache', E_USER_WARNING);
}
$this->_tried_to_connect = true;
return $this->_driver;
}
public function _get_avail_drivers_list()
public function _get_avail_drivers_list()
{
$prefix = 'cache_driver_';
$suffix = '.class.php';
$plen = strlen($prefix);
$slen = strlen($suffix);
$patterns = [
'app' => [
APP_PATH . 'classes/cache/*' . $prefix . '*' . $suffix,
APP_PATH . 'plugins/*/classes/cache/*' . $prefix . '*' . $suffix,
],
'framework' => [
YF_PATH . 'classes/cache/*' . $prefix . '*' . $suffix,
YF_PATH . 'plugins/*/classes/cache/*' . $prefix . '*' . $suffix,
],
];
$drivers = [];
foreach ($patterns as $paths) {
foreach ($paths as $path) {
foreach (glob($path) as $f) {
$f = basename($f);
$name = substr($f, strpos($f, $prefix) + $plen, -$slen);
if ($name) {
$drivers[$name] = $name;
}
}
}
}
return $drivers;
}
public function _init($params = [])
Framework constructor.
public function _init($params = [])
{
if ($this->_init_complete) {
return true;
}
$this->_init_settings();
$this->_connect($params);
$this->_init_complete = true;
}
public function _init_settings($params = [])
public function _init_settings($params = [])
{
// backwards compatibility
if ($this->FILES_TTL) {
$this->TTL = $this->FILES_TTL;
}
$conf_cache_ns = conf('CACHE_NS');
// Cache namespace need to be unique, especially when using memcached shared between several projects
if ( ! $conf_cache_ns && ! $this->CACHE_NS && $this->AUTO_CACHE_NS) {
$this->CACHE_NS = substr(md5(PROJECT_PATH), 0, 8) . '_';
}
if ($conf_cache_ns) {
$this->CACHE_NS = $conf_cache_ns;
}
$conf_no_cache = conf('NO_CACHE');
// backwards compatibility
if (defined('USE_CACHE')) {
if ( ! USE_CACHE) {
$this->NO_CACHE = true;
$this->_NO_CACHE_WHY = 'const NO_CACHE defined and false';
}
} elseif ($conf_no_cache !== null && $conf_no_cache) {
$this->NO_CACHE = true;
$this->_NO_CACHE_WHY = 'conf(NO_CACHE) is true';
} elseif ( ! main()->USE_SYSTEM_CACHE) {
$this->NO_CACHE = true;
$this->_NO_CACHE_WHY = 'main()->USE_SYSTEM_CACHE == false';
}
if ((($_GET['no_core_cache'] ?? false) || ($_GET['no_cache'] ?? false)) && $this->_url_action_allowed('no_cache')) {
$this->NO_CACHE = true;
$this->_NO_CACHE_WHY = '$_GET param no_cache';
}
if ($this->NO_CACHE && ! $this->_NO_CACHE_WHY) {
$this->_NO_CACHE_WHY = 'cache()->NO_CACHE == true';
}
if ((($_GET['refresh_cache'] ?? false) || ($_GET['rebuild_core_cache'] ?? false)) && $this->_url_action_allowed('refresh_cache')) {
$this->FORCE_REBUILD_CACHE = true;
}
$this->FORCE_REBUILD_CACHE = false;
}
public function _set_current_driver($params = [])
public function _set_current_driver($params = [])
{
$avail_drivers = $this->_get_avail_drivers_list();
$driver = '';
$want = isset($params['driver']) ? $params['driver'] : $this->DRIVER;
if ( ! $want || $want == 'auto') {
$want = 'memcache';
}
if (isset($this->REMOVED_DRIVERS[$want])) {
trigger_error('CACHE: driver "' . $want . '" ' . $this->REMOVED_DRIVERS[$want], E_USER_WARNING);
}
if (isset($avail_drivers[$want])) {
$driver = $want;
}
$this->DRIVER = $driver;
return $driver;
}
public function _url_action_allowed($action = '')
Callback that can be overriden to ensure security when allowing url params like no_cache, refresh_cache We can add DEBUG_MODE checking here to not allow refresh_cache attacks, maybe add check for: conf('cache_refresh_token', 'something_random'), main()->CACHE_CONTROL_FROM_URL.
public function _url_action_allowed($action = '')
{
$actions = ['no_cache', 'refresh_cache'];
$callback = conf('cache_url_action_allowed');
if (is_callable($callback)) {
return (bool) $callback($action);
}
return true;
}