dokuwiki/inc/Cache/Cache.php

236 lines
6.6 KiB
PHP

<?php
namespace dokuwiki\Cache;
use \dokuwiki\Debug\PropertyDeprecationHelper;
use dokuwiki\Extension\Event;
/**
* Generic handling of caching
*/
class Cache
{
use PropertyDeprecationHelper;
public $key = ''; // primary identifier for this item
public $ext = ''; // file ext for cache data, secondary identifier for this item
public $cache = ''; // cache file name
public $depends = array(); // array containing cache dependency information,
// used by makeDefaultCacheDecision to determine cache validity
// phpcs:disable
/**
* @deprecated since 2019-02-02 use the respective getters instead!
*/
protected $_event = ''; // event to be triggered during useCache
protected $_time;
protected $_nocache = false; // if set to true, cache will not be used or stored
// phpcs:enable
/**
* @param string $key primary identifier
* @param string $ext file extension
*/
public function __construct($key, $ext)
{
$this->key = $key;
$this->ext = $ext;
$this->cache = getCacheName($key, $ext);
/**
* @deprecated since 2019-02-02 use the respective getters instead!
*/
$this->deprecatePublicProperty('_event');
$this->deprecatePublicProperty('_time');
$this->deprecatePublicProperty('_nocache');
}
public function getTime()
{
return $this->_time;
}
public function getEvent()
{
return $this->_event;
}
public function setEvent($event)
{
$this->_event = $event;
}
/**
* public method to determine whether the cache can be used
*
* to assist in centralisation of event triggering and calculation of cache statistics,
* don't override this function override makeDefaultCacheDecision()
*
* @param array $depends array of cache dependencies, support dependecies:
* 'age' => max age of the cache in seconds
* 'files' => cache must be younger than mtime of each file
* (nb. dependency passes if file doesn't exist)
*
* @return bool true if cache can be used, false otherwise
*/
public function useCache($depends = array())
{
$this->depends = $depends;
$this->addDependencies();
if ($this->_event) {
return $this->stats(Event::createAndTrigger($this->_event, $this, array($this, 'makeDefaultCacheDecision')));
} else {
return $this->stats($this->makeDefaultCacheDecision());
}
}
/**
* internal method containing cache use decision logic
*
* this function processes the following keys in the depends array
* purge - force a purge on any non empty value
* age - expire cache if older than age (seconds)
* files - expire cache if any file in this array was updated more recently than the cache
*
* Note that this function needs to be public as it is used as callback for the event handler
*
* can be overridden
*
* @internal This method may only be called by the event handler! Call \dokuwiki\Cache\Cache::useCache instead!
*
* @return bool see useCache()
*/
public function makeDefaultCacheDecision()
{
if ($this->_nocache) {
return false;
} // caching turned off
if (!empty($this->depends['purge'])) {
return false;
} // purge requested?
if (!($this->_time = @filemtime($this->cache))) {
return false;
} // cache exists?
// cache too old?
if (!empty($this->depends['age']) && ((time() - $this->_time) > $this->depends['age'])) {
return false;
}
if (!empty($this->depends['files'])) {
foreach ($this->depends['files'] as $file) {
if ($this->_time <= @filemtime($file)) {
return false;
} // cache older than files it depends on?
}
}
return true;
}
/**
* add dependencies to the depends array
*
* this method should only add dependencies,
* it should not remove any existing dependencies and
* it should only overwrite a dependency when the new value is more stringent than the old
*/
protected function addDependencies()
{
global $INPUT;
if ($INPUT->has('purge')) {
$this->depends['purge'] = true;
} // purge requested
}
/**
* retrieve the cached data
*
* @param bool $clean true to clean line endings, false to leave line endings alone
* @return string cache contents
*/
public function retrieveCache($clean = true)
{
return io_readFile($this->cache, $clean);
}
/**
* cache $data
*
* @param string $data the data to be cached
* @return bool true on success, false otherwise
*/
public function storeCache($data)
{
if ($this->_nocache) {
return false;
}
return io_savefile($this->cache, $data);
}
/**
* remove any cached data associated with this cache instance
*/
public function removeCache()
{
@unlink($this->cache);
}
/**
* Record cache hits statistics.
* (Only when debugging allowed, to reduce overhead.)
*
* @param bool $success result of this cache use attempt
* @return bool pass-thru $success value
*/
protected function stats($success)
{
global $conf;
static $stats = null;
static $file;
if (!$conf['allowdebug']) {
return $success;
}
if (is_null($stats)) {
$file = $conf['cachedir'] . '/cache_stats.txt';
$lines = explode("\n", io_readFile($file));
foreach ($lines as $line) {
$i = strpos($line, ',');
$stats[substr($line, 0, $i)] = $line;
}
}
if (isset($stats[$this->ext])) {
list($ext, $count, $hits) = explode(',', $stats[$this->ext]);
} else {
$ext = $this->ext;
$count = 0;
$hits = 0;
}
$count++;
if ($success) {
$hits++;
}
$stats[$this->ext] = "$ext,$count,$hits";
io_saveFile($file, join("\n", $stats));
return $success;
}
/**
* @return bool
*/
public function isNoCache()
{
return $this->_nocache;
}
}