309 lines
7.4 KiB
PHP
309 lines
7.4 KiB
PHP
<?php if (!defined('FW')) die('Forbidden');
|
|
|
|
/**
|
|
* Memory Cache
|
|
*
|
|
* Recommended usage example:
|
|
* try {
|
|
* $value = FW_Cache::get('some/key');
|
|
* } catch(FW_Cache_Not_Found_Exception $e) {
|
|
* $value = get_value_from_somewhere();
|
|
*
|
|
* FW_Cache::set('some/key', $value);
|
|
*
|
|
* // (!) after set, do not do this:
|
|
* $value = FW_Cache::get('some/key');
|
|
* // because there is no guaranty that FW_Cache::set('some/key', $value); succeeded
|
|
* // trust only your $value, cache can do clean-up right after set() and remove the value you tried to set
|
|
* }
|
|
*
|
|
* // use $value ...
|
|
*/
|
|
class FW_Cache
|
|
{
|
|
/**
|
|
* The actual cache
|
|
* @var array
|
|
*/
|
|
protected static $cache = array();
|
|
|
|
/**
|
|
* If the PHP will have less that this memory, the cache will try to delete parts from its array to free memory
|
|
*
|
|
* (1024 * 1024 = 1048576 = 1 Mb) * 10
|
|
*/
|
|
protected static $min_free_memory = 10485760;
|
|
|
|
/**
|
|
* A special value that is used to detect if value was found in cache
|
|
* We can't use null|false because these can be values set by user and we can't treat them as not existing values
|
|
*/
|
|
protected static $not_found_value;
|
|
|
|
/**
|
|
* The amount of times the data was already stored in the cache.
|
|
* @var int
|
|
* @since 2.4.17
|
|
*/
|
|
protected static $hits = 0;
|
|
|
|
/**
|
|
* Amount of times the cache did not have the value in cache.
|
|
* @var int
|
|
* @since 2.4.17
|
|
*/
|
|
protected static $misses = 0;
|
|
|
|
/**
|
|
* Amount of times the cache free was called.
|
|
* @var int
|
|
* @since 2.4.17
|
|
*/
|
|
protected static $freed = 0;
|
|
|
|
protected static function get_memory_limit()
|
|
{
|
|
$memory_limit = ini_get('memory_limit');
|
|
|
|
if ($memory_limit === '-1') { // This happens in WP CLI
|
|
return 256 * 1024 * 1024;
|
|
}
|
|
|
|
switch (substr($memory_limit, -1)) {
|
|
case 'M': return intval($memory_limit) * 1024 * 1024;
|
|
case 'K': return intval($memory_limit) * 1024;
|
|
case 'G': return intval($memory_limit) * 1024 * 1024 * 1024;
|
|
default: return intval($memory_limit) * 1024 * 1024;
|
|
}
|
|
}
|
|
|
|
protected static function memory_exceeded()
|
|
{
|
|
return memory_get_usage(false) >= self::get_memory_limit() - self::$min_free_memory;
|
|
|
|
// about memory_get_usage(false) http://stackoverflow.com/a/16239377/1794248
|
|
}
|
|
|
|
/**
|
|
* @internal
|
|
*/
|
|
public static function _init()
|
|
{
|
|
self::$not_found_value = new FW_Cache_Not_Found_Exception();
|
|
|
|
/**
|
|
* Listen often triggered hooks to clear the memory
|
|
* instead of tick function https://github.com/ThemeFuse/Unyson/issues/1197
|
|
* @since 2.4.17
|
|
*/
|
|
foreach (array(
|
|
'query' => true,
|
|
'plugins_loaded' => true,
|
|
'wp_get_object_terms' => true,
|
|
'created_term' => true,
|
|
'wp_upgrade' => true,
|
|
'added_option' => true,
|
|
'updated_option' => true,
|
|
'deleted_option' => true,
|
|
'wp_after_admin_bar_render' => true,
|
|
'http_response' => true,
|
|
'oembed_result' => true,
|
|
'customize_post_value_set' => true,
|
|
'customize_save_after' => true,
|
|
'customize_render_panel' => true,
|
|
'customize_render_control' => true,
|
|
'customize_render_section' => true,
|
|
'role_has_cap' => true,
|
|
'user_has_cap' => true,
|
|
'theme_page_templates' => true,
|
|
'pre_get_users' => true,
|
|
'request' => true,
|
|
'send_headers' => true,
|
|
'updated_usermeta' => true,
|
|
'added_usermeta' => true,
|
|
'image_memory_limit' => true,
|
|
'upload_dir' => true,
|
|
'wp_head' => true,
|
|
'wp_footer' => true,
|
|
'wp' => true,
|
|
'wp_init' => true,
|
|
'fw_init' => true,
|
|
'init' => true,
|
|
'updated_postmeta' => true,
|
|
'deleted_postmeta' => true,
|
|
'setted_transient' => true,
|
|
'registered_post_type' => true,
|
|
'wp_count_posts' => true,
|
|
'wp_count_attachments' => true,
|
|
'after_delete_post' => true,
|
|
'post_updated' => true,
|
|
'wp_insert_post' => true,
|
|
'deleted_post' => true,
|
|
'clean_post_cache' => true,
|
|
'wp_restore_post_revision' => true,
|
|
'wp_delete_post_revision' => true,
|
|
'get_term' => true,
|
|
'edited_term_taxonomies' => true,
|
|
'deleted_term_taxonomy' => true,
|
|
'edited_terms' => true,
|
|
'created_term' => true,
|
|
'clean_term_cache' => true,
|
|
'edited_term_taxonomy' => true,
|
|
'switch_theme' => true,
|
|
'wp_get_update_data' => true,
|
|
'clean_user_cache' => true,
|
|
'process_text_diff_html' => true,
|
|
) as $hook => $tmp) {
|
|
add_filter($hook, array(__CLASS__, 'free_memory'), 1);
|
|
}
|
|
|
|
/**
|
|
* Flush the cache when something major is changed (files or db values)
|
|
*/
|
|
foreach (array(
|
|
'switch_blog' => true,
|
|
'upgrader_post_install' => true,
|
|
'upgrader_process_complete' => true,
|
|
'switch_theme' => true,
|
|
) as $hook => $tmp) {
|
|
add_filter($hook, array(__CLASS__, 'clear'), 1);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This method does nothing @since 2.4.17
|
|
* but we can't delete it because it's public and maybe somebody is calling it
|
|
* @return bool
|
|
*/
|
|
public static function is_enabled()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @param mixed $dummy
|
|
* @return mixed
|
|
*/
|
|
public static function free_memory($dummy = null)
|
|
{
|
|
while (self::memory_exceeded() && !empty(self::$cache)) {
|
|
reset(self::$cache);
|
|
|
|
$key = key(self::$cache);
|
|
|
|
unset(self::$cache[$key]);
|
|
}
|
|
|
|
++self::$freed;
|
|
|
|
/**
|
|
* This method is used in add_filter() so to not break anything return filter value
|
|
*/
|
|
return $dummy;
|
|
}
|
|
|
|
/**
|
|
* @param $keys
|
|
* @param $value
|
|
* @param $keys_delimiter
|
|
*/
|
|
public static function set($keys, $value, $keys_delimiter = '/')
|
|
{
|
|
if (!self::is_enabled()) {
|
|
return;
|
|
}
|
|
|
|
self::free_memory();
|
|
|
|
fw_aks($keys, $value, self::$cache, $keys_delimiter);
|
|
|
|
self::free_memory();
|
|
}
|
|
|
|
/**
|
|
* Unset key from cache
|
|
* @param $keys
|
|
* @param $keys_delimiter
|
|
*/
|
|
public static function del($keys, $keys_delimiter = '/')
|
|
{
|
|
fw_aku($keys, self::$cache, $keys_delimiter);
|
|
|
|
self::free_memory();
|
|
}
|
|
|
|
/**
|
|
* @param $keys
|
|
* @param $keys_delimiter
|
|
* @return mixed
|
|
* @throws FW_Cache_Not_Found_Exception
|
|
*/
|
|
public static function get($keys, $keys_delimiter = '/')
|
|
{
|
|
$keys = (string)$keys;
|
|
$keys_arr = explode($keys_delimiter, $keys);
|
|
|
|
$key = $keys_arr;
|
|
$key = array_shift($key);
|
|
|
|
if ($key === '' || $key === null) {
|
|
trigger_error('First key must not be empty', E_USER_ERROR);
|
|
}
|
|
|
|
self::free_memory();
|
|
|
|
$value = fw_akg($keys, self::$cache, self::$not_found_value, $keys_delimiter);
|
|
|
|
self::free_memory();
|
|
|
|
if ($value === self::$not_found_value) {
|
|
++self::$misses;
|
|
|
|
throw new FW_Cache_Not_Found_Exception();
|
|
} else {
|
|
++self::$hits;
|
|
|
|
return $value;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Empty the cache
|
|
* @param mixed $dummy When method is used in add_filter()
|
|
* @return mixed
|
|
*/
|
|
public static function clear($dummy = null)
|
|
{
|
|
self::$cache = array();
|
|
|
|
/**
|
|
* This method is used in add_filter() so to not break anything return filter value
|
|
*/
|
|
return $dummy;
|
|
}
|
|
|
|
/**
|
|
* Debug information
|
|
* <?php add_action('admin_footer', function(){ FW_Cache::stats(); });
|
|
* @since 2.4.17
|
|
*/
|
|
public static function stats() {
|
|
echo '<div style="z-index: 10000; position: relative; background: #fff; padding: 15px;">';
|
|
echo '<p>';
|
|
echo '<strong>Cache Hits:</strong> '. self::$hits .'<br />';
|
|
echo '<strong>Cache Misses:</strong> '. self::$misses .'<br />';
|
|
echo '<strong>Cache Freed:</strong> '. self::$freed .'<br />';
|
|
echo '<strong>PHP Memory Peak Usage:</strong> '. fw_human_bytes(memory_get_peak_usage(false)) .'<br />';
|
|
echo '</p>';
|
|
echo '<ul>';
|
|
foreach (self::$cache as $group => $cache) {
|
|
echo "<li><strong>Group:</strong> $group - ( " . number_format( strlen( serialize( $cache ) ) / KB_IN_BYTES, 2 ) . 'k )</li>';
|
|
}
|
|
echo '</ul>';
|
|
echo '</div>';
|
|
}
|
|
}
|
|
|
|
class FW_Cache_Not_Found_Exception extends Exception {}
|
|
|
|
FW_Cache::_init(); |