mc-cms-namelessmc/core/classes/Core/Cache.php

423 lines
10 KiB
PHP
Raw Permalink Normal View History

<?php
/**
* Handles caching for NamelessMC.
2020-12-13 19:38:04 +01:00
*
* @package NamelessMC\Core
* @author Christian Metz
2017-06-06 22:31:13 +01:00
* @version 1.6-Nameless
* @license BSD http://www.opensource.org/licenses/bsd-license.php
*/
class Cache
{
2020-12-13 19:38:04 +01:00
/**
* The path to the cache file folder.
2020-12-13 19:38:04 +01:00
*/
2021-10-07 14:02:46 -07:00
private string $_cachepath = 'cache/';
2020-12-13 19:38:04 +01:00
/**
* The name of the default cache file.
2020-12-13 19:38:04 +01:00
*/
2021-10-07 14:02:46 -07:00
private string $_cachename = 'default';
2020-12-13 19:38:04 +01:00
/**
* The cache file extension.
2020-12-13 19:38:04 +01:00
*/
2021-10-07 14:02:46 -07:00
private string $_extension = '.cache';
/**
* Whether to collect cache data.
*/
private bool $_record_collector;
2020-12-13 19:38:04 +01:00
/**
* Create a new Cache instance.
2020-12-13 19:38:04 +01:00
*
* @param string|array $config (optional)
2020-12-13 19:38:04 +01:00
* @return void
*/
public function __construct($config = null)
{
2020-12-13 20:42:04 -08:00
if (isset($config)) {
2020-12-13 19:38:04 +01:00
if (is_string($config)) {
$this->setCache($config);
} elseif (is_array($config)) {
$this->setCache($config['name']);
$this->setCachePath($config['path']);
$this->setExtension($config['extension']);
2020-12-13 19:38:04 +01:00
}
}
}
private function recordCollector(): bool
{
return $this->_record_collector ??= defined('PHPDEBUGBAR') && PHPDEBUGBAR && class_exists('DebugBar\DebugBar');
}
2021-12-07 22:14:12 -08:00
/**
* Cache name Setter.
2021-12-07 22:14:12 -08:00
*
* @param string $name Name of cache file to use
2021-12-07 22:14:12 -08:00
* @return Cache
*/
public function setCache(string $name): Cache
{
2021-12-07 22:14:12 -08:00
$this->_cachename = $name;
2021-12-07 22:14:12 -08:00
return $this;
}
2020-12-13 19:38:04 +01:00
/**
* Check whether data is accociated with a key.
2021-12-07 22:14:12 -08:00
*
* @param string $key The key to check
2021-10-07 14:02:46 -07:00
* @return bool
2020-12-13 19:38:04 +01:00
*/
public function isCached(string $key): bool
{
2020-12-13 20:42:04 -08:00
if ($this->_loadCache()) {
2020-12-13 19:38:04 +01:00
$cachedData = $this->_loadCache();
2020-12-13 20:42:04 -08:00
if (isset($cachedData[$key])) {
$entry = $cachedData[$key];
if ($entry && $this->_checkExpired($entry['time'], $entry['expire'])) {
$is_cached = false;
} else {
$is_cached = isset($cachedData[$key]['data']);
2020-12-13 20:42:04 -08:00
}
2020-12-13 19:38:04 +01:00
}
}
2021-10-07 14:02:46 -07:00
if (!isset($is_cached)) {
$is_cached = false;
}
if ($this->recordCollector()) {
CacheCollector::getInstance()->recordCheck("{$this->_cachename}:{$key}", $is_cached);
}
return $is_cached;
}
/**
* Retrieve the cache data or persist it if not found.
*
* @param string $key The key to retrieve
* @param mixed $data The data to persist if not found
* @param int $expiration Expiration time in seconds
* @return mixed The cached data or the persisted data
*/
public function fetch(string $key, $data, int $expiration = 0)
{
if ($this->isCached($key)) {
return $this->retrieve($key);
}
if (is_callable($data)) {
$data = $data();
}
$this->store($key, $data, $expiration);
return $data;
}
2021-12-07 22:14:12 -08:00
/**
* Load appointed cache.
2021-12-07 22:14:12 -08:00
*
* @return mixed
*/
private function _loadCache()
{
2021-12-07 22:14:12 -08:00
if (file_exists($this->getCacheDir())) {
$file = file_get_contents($this->getCacheDir());
2021-12-07 22:14:12 -08:00
return json_decode($file, true);
}
2022-01-15 15:18:05 -08:00
return false;
2021-12-07 22:14:12 -08:00
}
/**
* Get the cache directory path.
2021-12-07 22:14:12 -08:00
*
* @return string
*/
public function getCacheDir(): string
{
2021-12-07 22:14:12 -08:00
if ($this->_checkCacheDir()) {
$filename = $this->getCache();
$filename = preg_replace('/[^0-9a-z\.\_\-]/i', '', strtolower($filename));
2021-12-07 22:14:12 -08:00
return $this->getCachePath() . $this->_getHash($filename) . $this->getExtension();
}
return '';
}
/**
* Check if a writable cache directory exists and if not create a new one.
2021-12-07 22:14:12 -08:00
*
* @return bool
2021-12-07 22:14:12 -08:00
*/
private function _checkCacheDir(): bool
{
2021-12-07 22:14:12 -08:00
if (!is_dir($this->getCachePath()) && !mkdir($this->getCachePath(), 0775, true)) {
2022-01-15 15:18:05 -08:00
throw new RuntimeException('Unable to create cache directory ' . $this->getCachePath());
}
if (!is_readable($this->getCachePath()) || !is_writable($this->getCachePath())) {
2021-12-07 22:14:12 -08:00
if (!chmod($this->getCachePath(), 0775)) {
2022-01-15 15:18:05 -08:00
throw new RuntimeException('Your <b>' . $this->getCachePath() . '</b> directory must be readable and writeable. Check your file permissions.');
2021-12-07 22:14:12 -08:00
}
}
2021-12-07 22:14:12 -08:00
return true;
}
/**
* Cache path Getter.
2021-12-07 22:14:12 -08:00
*
* @return string The path to the cache file folder
2021-12-07 22:14:12 -08:00
*/
public function getCachePath(): string
{
2021-12-07 22:14:12 -08:00
return $this->_cachepath;
}
/**
* Cache path Setter.
2021-12-07 22:14:12 -08:00
*
* @param string $path
2021-12-07 22:14:12 -08:00
* @return Cache
*/
public function setCachePath(string $path): Cache
{
2021-12-07 22:14:12 -08:00
$this->_cachepath = $path;
2021-12-07 22:14:12 -08:00
return $this;
}
/**
* Cache name Getter.
2021-12-07 22:14:12 -08:00
*
* @return string Cache name
2021-12-07 22:14:12 -08:00
*/
public function getCache(): string
{
2021-12-07 22:14:12 -08:00
return $this->_cachename;
}
/**
* Get the filename hash.
2021-12-07 22:14:12 -08:00
*
* @param string $filename
* @return string The hashed filename
2021-12-07 22:14:12 -08:00
*/
private function _getHash(string $filename): string
{
2021-12-07 22:14:12 -08:00
return sha1($filename);
}
/**
* Cache file extension Getter.
2021-12-07 22:14:12 -08:00
*
* @return string Cache file extension
2021-12-07 22:14:12 -08:00
*/
public function getExtension(): string
{
2021-12-07 22:14:12 -08:00
return $this->_extension;
}
/**
* Cache file extension Setter.
2021-12-07 22:14:12 -08:00
*
* @param string $ext Extension to use
2021-12-07 22:14:12 -08:00
* @return Cache
*/
public function setExtension(string $ext): Cache
{
2021-12-07 22:14:12 -08:00
$this->_extension = $ext;
2021-12-07 22:14:12 -08:00
return $this;
}
/**
* Check whether a timestamp is still in the duration.
2021-12-07 22:14:12 -08:00
*
* @param int $timestamp Timestamp to check
* @param int $expiration Duration to check
* @return bool True if still in duration
2021-12-07 22:14:12 -08:00
*/
private function _checkExpired(int $timestamp, int $expiration): bool
{
2021-12-07 22:14:12 -08:00
$result = false;
if ($expiration !== 0) {
$timeDiff = time() - $timestamp;
$result = $timeDiff > $expiration;
2021-12-07 22:14:12 -08:00
}
2021-12-07 22:14:12 -08:00
return $result;
}
2020-12-13 19:38:04 +01:00
/**
* Store data in the cache.
2020-12-13 19:38:04 +01:00
*
* @param string $key Key to store data under
* @param mixed $data Data to store
* @param int $expiration Expiration time in seconds
2021-12-07 22:14:12 -08:00
*
2021-10-07 14:02:46 -07:00
* @return Cache
2020-12-13 19:38:04 +01:00
*/
public function store(string $key, $data, int $expiration = 0): Cache
{
2021-10-29 22:00:05 -07:00
$storeData = [
2021-12-07 22:14:12 -08:00
'time' => time(),
2020-12-13 19:38:04 +01:00
'expire' => $expiration,
'data' => serialize($data),
2021-10-29 22:00:05 -07:00
];
2020-12-13 19:38:04 +01:00
$dataArray = $this->_loadCache();
2020-12-13 20:42:04 -08:00
if (is_array($dataArray)) {
2020-12-13 19:38:04 +01:00
$dataArray[$key] = $storeData;
} else {
2021-10-29 22:00:05 -07:00
$dataArray = [$key => $storeData];
2020-12-13 19:38:04 +01:00
}
$cacheData = json_encode($dataArray);
file_put_contents($this->getCacheDir(), $cacheData);
if ($this->recordCollector()) {
CacheCollector::getInstance()->recordSet("{$this->_cachename}:{$key}", $data, $expiration);
}
2020-12-13 19:38:04 +01:00
return $this;
}
2020-12-13 19:38:04 +01:00
/**
* Retrieve cached data by its key.
2020-12-13 19:38:04 +01:00
*
* @param string $key The key to retrieve
* @param bool $timestamp Whether to check if the cache is expired
2021-12-07 22:14:12 -08:00
*
* @return mixed The cached data or null if not found/expired
2020-12-13 19:38:04 +01:00
*/
public function retrieve(string $key, bool $timestamp = false)
{
2020-12-13 19:38:04 +01:00
$cachedData = $this->_loadCache();
$type = $timestamp ? 'time' : 'data';
2021-10-07 14:02:46 -07:00
if (!isset($cachedData[$key][$type])) {
if ($this->recordCollector()) {
CacheCollector::getInstance()->recordMiss("{$this->_cachename}:{$key}");
}
2021-10-07 14:02:46 -07:00
return null;
}
2020-12-13 20:42:04 -08:00
if (!$timestamp) {
2020-12-13 19:38:04 +01:00
$entry = $cachedData[$key];
2020-12-13 20:42:04 -08:00
if ($entry && $this->_checkExpired($entry['time'], $entry['expire'])) {
if ($this->recordCollector()) {
CacheCollector::getInstance()->recordMiss("{$this->_cachename}:{$key}");
}
2020-12-13 19:38:04 +01:00
return null;
2020-12-13 20:42:04 -08:00
}
2020-12-13 19:38:04 +01:00
}
2021-10-07 14:02:46 -07:00
$data = unserialize($cachedData[$key][$type]);
if ($this->recordCollector()) {
CacheCollector::getInstance()->recordHit("{$this->_cachename}:{$key}", $data);
}
return $data;
}
2020-12-13 19:38:04 +01:00
/**
* Retrieve all cached data.
2020-12-13 19:38:04 +01:00
*
* @param bool $meta (optional)
* @return array The cached data
2020-12-13 19:38:04 +01:00
*/
public function retrieveAll(bool $meta = false): array
{
if ($meta) {
return $this->_loadCache();
}
2022-01-15 15:18:05 -08:00
$results = [];
$cachedData = $this->_loadCache();
if ($cachedData) {
foreach ($cachedData as $k => $v) {
$results[$k] = unserialize($v['data']);
}
}
return $results;
}
2020-12-13 19:38:04 +01:00
/**
* Erase cached entry by its key.
2020-12-13 19:38:04 +01:00
*
* @param string $key The key to erase
2021-10-07 14:02:46 -07:00
* @return Cache
2020-12-13 19:38:04 +01:00
*/
public function erase(string $key): Cache
{
2020-12-13 19:38:04 +01:00
$cacheData = $this->_loadCache();
2020-12-13 20:42:04 -08:00
if (is_array($cacheData)) {
if (isset($cacheData[$key])) {
2020-12-13 19:38:04 +01:00
unset($cacheData[$key]);
$cacheData = json_encode($cacheData);
file_put_contents($this->getCacheDir(), $cacheData);
} else {
2022-01-15 15:18:05 -08:00
throw new RuntimeException("Error: erase() - Key '$key' not found.");
2020-12-13 19:38:04 +01:00
}
}
2020-12-13 19:38:04 +01:00
return $this;
}
2020-12-13 19:38:04 +01:00
/**
* Erase all expired entries.
2020-12-13 19:38:04 +01:00
*
* @return int Number of entries erased
2020-12-13 19:38:04 +01:00
*/
public function eraseExpired(): int
{
2020-12-13 19:38:04 +01:00
$cacheData = $this->_loadCache();
2020-12-13 20:42:04 -08:00
if (is_array($cacheData)) {
2020-12-13 19:38:04 +01:00
$counter = 0;
foreach ($cacheData as $key => $entry) {
2020-12-13 20:42:04 -08:00
if ($this->_checkExpired($entry['time'], $entry['expire'])) {
2020-12-13 19:38:04 +01:00
unset($cacheData[$key]);
$counter++;
}
}
if ($counter > 0) {
$cacheData = json_encode($cacheData);
file_put_contents($this->getCacheDir(), $cacheData);
}
2020-12-13 19:38:04 +01:00
return $counter;
}
2021-10-08 07:49:43 -07:00
return -1;
}
2020-12-13 19:38:04 +01:00
/**
* Erase all cached entries.
2020-12-13 19:38:04 +01:00
*
2021-10-07 14:02:46 -07:00
* @return Cache
2020-12-13 19:38:04 +01:00
*/
public function eraseAll(): Cache
{
2020-12-13 19:38:04 +01:00
$cacheDir = $this->getCacheDir();
2020-12-13 20:42:04 -08:00
if (file_exists($cacheDir)) {
$cacheFile = fopen($cacheDir, 'wb');
2020-12-13 19:38:04 +01:00
fclose($cacheFile);
}
2020-12-13 19:38:04 +01:00
return $this;
}
}