AntCMS/src/AntCMS/AntCache.php

166 lines
6.0 KiB
PHP

<?php
namespace AntCMS;
use AntCMS\AntConfig;
use Symfony\Component\Yaml\Exception\ParseException;
class AntCache
{
private int $cacheType = 0;
private string $cacheKeyApcu = '';
const noCache = 0;
const fileCache = 1;
const apcuCache = 2;
/**
* Creates a new cache object, sets the correct caching type. ('auto', 'filesystem', 'apcu', or 'none')
*/
public function __construct()
{
$config = AntConfig::currentConfig();
$mode = $config['cacheMode'] ?? 'auto';
switch ($mode) {
case 'none':
$this->cacheType = self::noCache;
break;
case 'auto':
if (extension_loaded('apcu') && apcu_enabled()) {
$this->cacheType = self::apcuCache;
$this->cacheKeyApcu = 'AntCMS_' . hash('md5', __DIR__) . '_';
} else {
$this->cacheType = self::fileCache;
}
break;
case 'filesystem':
$this->cacheType = self::fileCache;
break;
case 'apcu':
$this->cacheType = self::apcuCache;
$this->cacheKeyApcu = 'AntCMS_' . hash('md5', __DIR__) . '_';
break;
default:
throw new \Exception("Invalid cache type. Must be 'auto', 'filesystem', 'apcu', or 'none'.");
}
}
/**
* Caches a value for a given cache key.
*
* @param string $key The cache key to use for the cached value.
* @param string $content The value to cache.
* @return bool True if the value was successfully cached, false otherwise.
* @throws ParseException If there is an error parsing the AntCMS configuration file.
*/
public function setCache(string $key, string $content)
{
switch ($this->cacheType) {
case self::noCache:
return false;
case self::fileCache:
$cachePath = AntCachePath . DIRECTORY_SEPARATOR . "{$key}.cache";
return file_put_contents($cachePath, $content);
case self::apcuCache:
$apcuKey = $this->cacheKeyApcu . $key;
return apcu_store($apcuKey, $content, 7 * 24 * 60 * 60); // Save it for one week.
default:
return false;
}
}
/**
* Retrieves the cached value for a given cache key.
*
* @param string $key The cache key used to retrieve the cached value.
* @return string|false The cached value, or false if there was an error loading it or if caching is disabled.
* @throws ParseException If there is an error parsing the AntCMS configuration file.
*/
public function getCache(string $key)
{
switch ($this->cacheType) {
case self::noCache:
return false;
case self::fileCache:
$cachePath = AntCachePath . DIRECTORY_SEPARATOR . "{$key}.cache";
return file_get_contents($cachePath);
case self::apcuCache:
$apcuKey = $this->cacheKeyApcu . $key;
if (apcu_exists($apcuKey)) {
return apcu_fetch($apcuKey);
} else {
return false;
}
default:
return false;
}
}
/**
* Determines if a cache key has a corresponding cached value.
*
* @param string $key The cache key to check.
* @return bool True if the cache key has a corresponding cached value, false otherwise. Will also return false if caching is disabled.
* @throws ParseException If there is an error parsing the AntCMS configuration file.
*/
public function isCached(string $key)
{
switch ($this->cacheType) {
case self::noCache:
return false;
case self::fileCache:
$cachePath = AntCachePath . DIRECTORY_SEPARATOR . "{$key}.cache";
return file_exists($cachePath);
case self::apcuCache:
$apcuKey = $this->cacheKeyApcu . $key;
return apcu_exists($apcuKey);
default:
return false;
}
}
/**
* Generates a unique cache key for the associated content and a salt value.
* The salt is used to ensure that each cache key is unique to each component, even if multiple components are using the same source content but caching different results.
*
* @param string $content The content to generate a cache key for.
* @param string $salt An optional salt value to use in the cache key generation. Default is 'cache'.
* @return string The generated cache key.
*/
public function createCacheKey(string $content, string $salt = 'cache')
{
/**
* If the server is modern enough to have xxh128, use that. It is really fast and still produces long hashes
* If not, use MD4 since it's still quite fast.
* Source: https://php.watch/articles/php-hash-benchmark
*/
if (defined('HAS_XXH128')) {
return hash('xxh128', $content . $salt);
} else {
return hash('md4', $content . $salt);
}
}
public static function clearCache(): void
{
$di = new \RecursiveDirectoryIterator(AntCachePath, \FilesystemIterator::SKIP_DOTS);
$ri = new \RecursiveIteratorIterator($di, \RecursiveIteratorIterator::CHILD_FIRST);
foreach ($ri as $file) {
$file->isDir() ? rmdir($file->getRealPath()) : unlink($file->getRealPath());
}
if (extension_loaded('apcu') && apcu_enabled()) {
$prefix = 'AntCMS_' . hash('md5', __DIR__) . '_';
$cacheInfo = apcu_cache_info();
$keys = $cacheInfo['cache_list'];
foreach ($keys as $keyInfo) {
$key = $keyInfo['info'];
if (str_starts_with($key, $prefix)) {
apcu_delete($key);
}
}
}
}
}