mirror of
https://github.com/moodle/moodle.git
synced 2025-02-13 03:45:49 +01:00
432 lines
15 KiB
PHP
432 lines
15 KiB
PHP
<?php
|
|
// This file is part of Moodle - http://moodle.org/
|
|
//
|
|
// Moodle is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// Moodle is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
/**
|
|
* Cache helper class
|
|
*
|
|
* This file is part of Moodle's cache API, affectionately called MUC.
|
|
* It contains the components that are requried in order to use caching.
|
|
*
|
|
* @package core
|
|
* @category cache
|
|
* @copyright 2012 Sam Hemelryk
|
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
*/
|
|
|
|
defined('MOODLE_INTERNAL') || die();
|
|
|
|
/**
|
|
* The cache helper class.
|
|
*
|
|
* The cache helper class provides common functionality to the cache API and is useful to developers within to interact with
|
|
* the cache API in a general way.
|
|
*
|
|
* @package core
|
|
* @category cache
|
|
* @copyright 2012 Sam Hemelryk
|
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
*/
|
|
class cache_helper {
|
|
|
|
/**
|
|
* Statistics gathered by the cache API during its operation will be used here.
|
|
* @static
|
|
* @var array
|
|
*/
|
|
protected static $stats = array();
|
|
|
|
/**
|
|
* The instance of the cache helper.
|
|
* @var cache_helper
|
|
*/
|
|
protected static $instance;
|
|
|
|
/**
|
|
* Returns true if the cache API can be initialised before Moodle has finished initialising itself.
|
|
*
|
|
* This check is essential when trying to cache the likes of configuration information. It checks to make sure that the cache
|
|
* configuration file has been created which allows use to set up caching when ever is required.
|
|
*
|
|
* @return bool
|
|
*/
|
|
public static function ready_for_early_init() {
|
|
return cache_config::config_file_exists();
|
|
}
|
|
|
|
/**
|
|
* Returns an instance of the cache_helper.
|
|
*
|
|
* This is designed for internal use only and acts as a static store.
|
|
* @staticvar null $instance
|
|
* @return cache_helper
|
|
*/
|
|
protected static function instance() {
|
|
if (is_null(self::$instance)) {
|
|
self::$instance = new cache_helper();
|
|
}
|
|
return self::$instance;
|
|
}
|
|
|
|
/**
|
|
* Constructs an instance of the cache_helper class. Again for internal use only.
|
|
*/
|
|
protected function __construct() {
|
|
// Nothing to do here, just making sure you can't get an instance of this.
|
|
}
|
|
|
|
/**
|
|
* Used as a data store for initialised definitions.
|
|
* @var array
|
|
*/
|
|
protected $definitions = array();
|
|
|
|
/**
|
|
* Used as a data store for initialised cache stores
|
|
* We use this because we want to avoid establishing multiple instances of a single store.
|
|
* @var array
|
|
*/
|
|
protected $stores = array();
|
|
|
|
/**
|
|
* Returns the class for use as a cache loader for the given mode.
|
|
*
|
|
* @param int $mode One of cache_store::MODE_
|
|
* @return string
|
|
* @throws coding_exception
|
|
*/
|
|
public static function get_class_for_mode($mode) {
|
|
switch ($mode) {
|
|
case cache_store::MODE_APPLICATION :
|
|
return 'cache_application';
|
|
case cache_store::MODE_REQUEST :
|
|
return 'cache_request';
|
|
case cache_store::MODE_SESSION :
|
|
return 'cache_session';
|
|
}
|
|
throw new coding_exception('Unknown cache mode passed. Must be one of cache_store::MODE_*');
|
|
}
|
|
|
|
/**
|
|
* Returns the cache stores to be used with the given definition.
|
|
* @param cache_definition $definition
|
|
* @return array
|
|
*/
|
|
public static function get_cache_stores(cache_definition $definition) {
|
|
$instance = cache_config::instance();
|
|
$stores = $instance->get_stores_for_definition($definition);
|
|
$stores = self::initialise_cachestore_instances($stores, $definition);
|
|
return $stores;
|
|
}
|
|
|
|
/**
|
|
* Internal function for initialising an array of stores against a given cache definition.
|
|
*
|
|
* @param array $stores
|
|
* @param cache_definition $definition
|
|
* @return array
|
|
*/
|
|
protected static function initialise_cachestore_instances(array $stores, cache_definition $definition) {
|
|
$return = array();
|
|
$factory = cache_factory::instance();
|
|
foreach ($stores as $name => $details) {
|
|
$store = $factory->create_store_from_config($name, $details, $definition);
|
|
if ($store !== false) {
|
|
$return[] = $store;
|
|
}
|
|
}
|
|
return $return;
|
|
}
|
|
|
|
/**
|
|
* Returns the cache store to be used for locking or false if there is not one.
|
|
* @return cache_store|boolean
|
|
*/
|
|
public static function get_cachestore_for_locking() {
|
|
$factory = cache_factory::instance();
|
|
$definition = $factory->create_definition('core', 'locking');
|
|
$instance = cache_config::instance();
|
|
$stores = $instance->get_stores_for_definition($definition);
|
|
foreach ($stores as $name => $details) {
|
|
if ($details['useforlocking']) {
|
|
$instances = self::initialise_cachestore_instances(array($name => $details), $definition);
|
|
return reset($instances);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Returns an array of plugins without using core methods.
|
|
*
|
|
* This function explicitly does NOT use core functions as it will in some circumstances be called before Moodle has
|
|
* finished initialising. This happens when loading configuration for instance.
|
|
*
|
|
* @return string
|
|
*/
|
|
public static function early_get_cache_plugins() {
|
|
global $CFG;
|
|
$result = array();
|
|
$ignored = array('CVS', '_vti_cnf', 'simpletest', 'db', 'yui', 'tests');
|
|
$fulldir = $CFG->dirroot.'/cache/stores';
|
|
$items = new DirectoryIterator($fulldir);
|
|
foreach ($items as $item) {
|
|
if ($item->isDot() or !$item->isDir()) {
|
|
continue;
|
|
}
|
|
$pluginname = $item->getFilename();
|
|
if (in_array($pluginname, $ignored)) {
|
|
continue;
|
|
}
|
|
$pluginname = clean_param($pluginname, PARAM_PLUGIN);
|
|
if (empty($pluginname)) {
|
|
// better ignore plugins with problematic names here
|
|
continue;
|
|
}
|
|
$result[$pluginname] = $fulldir.'/'.$pluginname;
|
|
unset($item);
|
|
}
|
|
unset($items);
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Invalidates a given set of keys from a given definition.
|
|
*
|
|
* @todo Invalidating by definition should also add to the event cache so that sessions can be invalidated (when required).
|
|
*
|
|
* @param string $component
|
|
* @param string $area
|
|
* @param array $identifiers
|
|
* @param array $keys
|
|
* @return boolean
|
|
*/
|
|
public static function invalidate_by_definition($component, $area, array $identifiers = array(), $keys = array()) {
|
|
$cache = cache::make($component, $area, $identifiers);
|
|
if (is_array($keys)) {
|
|
$cache->delete_many($keys);
|
|
} else if (is_scalar($keys)) {
|
|
$cache->delete($keys);
|
|
} else {
|
|
throw new coding_exception('cache_helper::invalidate_by_definition only accepts $keys as array, or scalar.');
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Invalidates a given set of keys by means of an event.
|
|
*
|
|
* @todo add support for identifiers to be supplied and utilised.
|
|
*
|
|
* @param string $event
|
|
* @param array $keys
|
|
*/
|
|
public static function invalidate_by_event($event, array $keys) {
|
|
$instance = cache_config::instance();
|
|
$invalidationeventset = false;
|
|
$factory = cache_factory::instance();
|
|
foreach ($instance->get_definitions() as $name => $definitionarr) {
|
|
$definition = cache_definition::load($name, $definitionarr);
|
|
if ($definition->invalidates_on_event($event)) {
|
|
// OK at this point we know that the definition has information to invalidate on the event.
|
|
// There are two routes, either its an application cache in which case we can invalidate it now.
|
|
// or it is a session cache in which case we need to set something to the "Event invalidation" definition.
|
|
// No need to deal with request caches, we don't want to change data half way through a request.
|
|
if ($definition->get_mode() === cache_store::MODE_APPLICATION) {
|
|
$cache = $factory->create_cache($definition);
|
|
$cache->delete_many($keys);
|
|
}
|
|
|
|
// We need to flag the event in the "Event invalidation" cache if it hasn't already happened.
|
|
if ($invalidationeventset === false) {
|
|
// Get the event invalidation cache.
|
|
$cache = cache::make('core', 'eventinvalidation');
|
|
// Get any existing invalidated keys for this cache.
|
|
$data = $cache->get($event);
|
|
if ($data === false) {
|
|
// There are none.
|
|
$data = array();
|
|
}
|
|
// Add our keys to them with the current cache timestamp.
|
|
foreach ($keys as $key) {
|
|
$data[$key] = cache::now();
|
|
}
|
|
// Set that data back to the cache.
|
|
$cache->set($event, $data);
|
|
// This only needs to occur once.
|
|
$invalidationeventset = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Purges the cache for a specific definition.
|
|
*
|
|
* @param string $component
|
|
* @param string $area
|
|
* @param array $identifiers
|
|
* @return bool
|
|
*/
|
|
public static function purge_by_definition($component, $area, array $identifiers = array()) {
|
|
// Create the cache
|
|
$cache = cache::make($component, $area, $identifiers);
|
|
// Purge baby, purge.
|
|
$cache->purge();
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Purges a cache of all information on a given event.
|
|
*
|
|
* @param string $event
|
|
*/
|
|
public static function purge_by_event($event) {
|
|
$instance = cache_config::instance();
|
|
$invalidationeventset = false;
|
|
$factory = cache_factory::instance();
|
|
foreach ($instance->get_definitions() as $name => $definitionarr) {
|
|
$definition = cache_definition::load($name, $definitionarr);
|
|
if ($definition->invalidates_on_event($event)) {
|
|
// Purge the cache.
|
|
$cache = $factory->create_cache($definition);
|
|
$cache->purge();
|
|
|
|
// We need to flag the event in the "Event invalidation" cache if it hasn't already happened.
|
|
if ($invalidationeventset === false) {
|
|
// Get the event invalidation cache.
|
|
$cache = cache::make('core', 'eventinvalidation');
|
|
// Create a key to invalidate all
|
|
$data = array(
|
|
'purged' => cache::now()
|
|
);
|
|
// Set that data back to the cache.
|
|
$cache->set($event, $data);
|
|
// This only needs to occur once.
|
|
$invalidationeventset = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Ensure that the stats array is ready to collect information for the given store and definition.
|
|
* @param string $store
|
|
* @param string $definition
|
|
*/
|
|
protected static function ensure_ready_for_stats($store, $definition) {
|
|
if (!array_key_exists($definition, self::$stats)) {
|
|
self::$stats[$definition] = array(
|
|
$store => array(
|
|
'hits' => 0,
|
|
'misses' => 0,
|
|
'sets' => 0,
|
|
)
|
|
);
|
|
} else if (!array_key_exists($store, self::$stats[$definition])) {
|
|
self::$stats[$definition][$store] = array(
|
|
'hits' => 0,
|
|
'misses' => 0,
|
|
'sets' => 0,
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Record a cache hit in the stats for the given store and definition.
|
|
*
|
|
* @param string $store
|
|
* @param string $definition
|
|
*/
|
|
public static function record_cache_hit($store, $definition) {
|
|
self::ensure_ready_for_stats($store, $definition);
|
|
self::$stats[$definition][$store]['hits']++;
|
|
}
|
|
|
|
/**
|
|
* Record a cache miss in the stats for the given store and definition.
|
|
*
|
|
* @param string $store
|
|
* @param string $definition
|
|
*/
|
|
public static function record_cache_miss($store, $definition) {
|
|
self::ensure_ready_for_stats($store, $definition);
|
|
self::$stats[$definition][$store]['misses']++;
|
|
}
|
|
|
|
/**
|
|
* Record a cache set in the stats for the given store and definition.
|
|
*
|
|
* @param string $store
|
|
* @param string $definition
|
|
*/
|
|
public static function record_cache_set($store, $definition) {
|
|
self::ensure_ready_for_stats($store, $definition);
|
|
self::$stats[$definition][$store]['sets']++;
|
|
}
|
|
|
|
/**
|
|
* Return the stats collected so far.
|
|
* @return array
|
|
*/
|
|
public static function get_stats() {
|
|
return self::$stats;
|
|
}
|
|
|
|
/**
|
|
* Purge all of the cache stores of all of their data.
|
|
*/
|
|
public static function purge_all() {
|
|
$config = cache_config::instance();
|
|
$stores = $config->get_all_stores();
|
|
$definition = cache_definition::load_adhoc(cache_store::MODE_REQUEST, 'core', 'cache_purge');
|
|
foreach ($stores as $store) {
|
|
$class = $store['class'];
|
|
$instance = new $class($store['name'], $store['configuration']);
|
|
if (!$instance->is_ready()) {
|
|
continue;
|
|
}
|
|
$instance->initialise($definition);
|
|
$instance->purge();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the translated name of the definition.
|
|
*
|
|
* @param cache_definition $definition
|
|
* @return lang_string
|
|
*/
|
|
public static function get_definition_name($definition) {
|
|
if ($definition instanceof cache_definition) {
|
|
return $definition->get_name();
|
|
}
|
|
$identifier = 'cachedef_'.clean_param($definition['area'], PARAM_STRINGID);
|
|
$component = $definition['component'];
|
|
if ($component === 'core') {
|
|
$component = 'cache';
|
|
}
|
|
return new lang_string($identifier, $component);
|
|
}
|
|
|
|
/**
|
|
* Hashes a descriptive key to make it shorter and stil unique.
|
|
* @param string $key
|
|
* @return string
|
|
*/
|
|
public static function hash_key($key) {
|
|
return crc32($key);
|
|
}
|
|
} |