檔案快取是中小型專案無需安裝 Redis/Memcached 即可使用的輕量快取方案。本文提供生產級的 XZCache 類別,支援自訂快取目錄、過期時間、序列化儲存、快取狀態判斷(無快取/有效/過期)。
<?php
/**
* 簡單且實用的檔案快取類別
*/
class XZCache {
// 預設快取時間:1 小時(秒)
private $cache_time = 3600;
// 預設快取目錄
private $cache_dir = './cache';
public function __construct($cache_dir = null, $cache_time = null) {
$this->cache_dir = $cache_dir ?? $this->cache_dir;
$this->cache_time = $cache_time ?? $this->cache_time;
// 確保快取目錄存在
if (!is_dir($this->cache_dir)) {
mkdir($this->cache_dir, 0755, true);
}
}
/**
* 儲存快取
* @param string $key 快取鍵值
* @param mixed $value 快取資料(支援陣列、物件、純量)
* @return bool 成功與否
*/
public function saveCache($key, $value): bool {
$cache_file = $this->cache_dir . '/xzcache_' . md5($key);
// 檢查現有快取是否過期
$timediff = @(time() - filemtime($cache_file));
if ($timediff >= $this->cache_time || !is_file($cache_file)) {
// 過期或不存在,建立新快取
$serialized = serialize($value);
$bytes = @file_put_contents($cache_file, $serialized, LOCK_EX);
return $bytes !== false;
}
return true; // 快取未過期,無需更新
}
/**
* 讀取快取
* @param string $key 快取鍵值
* @return array ['cached' => 0|1|2, 'value' => mixed]
* cached: 0=無快取, 1=有效, 2=過期
*/
public function getCache($key): array {
$cache_file = $this->cache_dir . '/xzcache_' . md5($key);
$result = ['cached' => 0, 'value' => null];
if (is_file($cache_file)) {
$timediff = @(time() - filemtime($cache_file));
if ($timediff >= $this->cache_time) {
$result['cached'] = 2; // 過期
} else {
$content = @file_get_contents($cache_file);
$result['value'] = @unserialize($content);
$result['cached'] = 1; // 有效
}
}
return $result;
}
/**
* 刪除快取
*/
public function deleteCache($key): bool {
$cache_file = $this->cache_dir . '/xzcache_' . md5($key);
return @unlink($cache_file);
}
/**
* 清空所有快取
*/
public function clearAll(): int {
$count = 0;
$files = glob($this->cache_dir . '/xzcache_*');
foreach ($files as $file) {
if (@unlink($file)) $count++;
}
return $count;
}
/**
* 取得快取資訊(除錯用)
*/
public function getCacheInfo($key): array {
$cache_file = $this->cache_dir . '/xzcache_' . md5($key);
if (!is_file($cache_file)) {
return ['exists' => false];
}
$stat = stat($cache_file);
return [
'exists' => true,
'size' => $stat['size'],
'mtime' => $stat['mtime'],
'age' => time() - $stat['mtime'],
'expired' => (time() - $stat['mtime']) >= $this->cache_time
];
}
}
<?php
require_once 'XZCache.php';
// 建立快取實例(自訂目錄與時間)
$cache = new XZCache('./my_cache', 1800); // 30 分鐘過期
// 儲存複雜資料結構
$key = 'user_profile_123';
$value = [
'id' => 123,
'name' => '張三',
'roles' => ['admin', 'editor'],
'settings' => (object)['theme' => 'dark', 'lang' => 'zh-TW'],
'last_login' => time()
];
// 寫入快取
if ($cache->saveCache($key, $value)) {
echo "快取寫入成功\n";
}
// 讀取快取
$result = $cache->getCache($key);
switch ($result['cached']) {
case 1:
echo "快取命中:\n";
print_r($result['value']);
break;
case 2:
echo "快取過期,需重新載入資料源\n";
break;
case 0:
echo "無快取,首次載入\n";
break;
}
// 手動刪除
// $cache->deleteCache($key);
// 清空所有
// $cache->clearAll();
function getUserProfile($userId, XZCache $cache, PDO $pdo) {
$key = "user_profile_{$userId}";
$result = $cache->getCache($key);
if ($result['cached'] === 1) {
return $result['value']; // 快取命中
}
// 快取未命中或過期,查詢資料庫
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$userId]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
if ($user) {
// 寫入快取(設定較短過期時間防止熱點)
$cache->saveCache($key, $user);
return $user;
}
// 查無資料:快取空值防止穿透(短期快取)
$cache->saveCache($key, null);
return null;
}
class TaggedCache extends XZCache {
private $tag_index_file;
public function __construct($dir, $time) {
parent::__construct($dir, $time);
$this->tag_index_file = $this->cache_dir . '/_tag_index.php';
}
public function saveWithTags($key, $value, array $tags) {
parent::saveCache($key, $value);
// 更新標籤索引
$index = $this->loadTagIndex();
foreach ($tags as $tag) {
$index[$tag][$key] = true;
}
$this->saveTagIndex($index);
}
public function invalidateTag($tag) {
$index = $this->loadTagIndex();
if (isset($index[$tag])) {
foreach (array_keys($index[$tag]) as $key) {
$this->deleteCache($key);
}
unset($index[$tag]);
$this->saveTagIndex($index);
}
}
private function loadTagIndex() {
return is_file($this->tag_index_file) ? include $this->tag_index_file : [];
}
private function saveTagIndex(array $index) {
file_put_contents($this->tag_index_file, '
效能與限制說明
| 優點 | 缺點/限制 |
|---|---|
| 零依賴、部署簡單 | 單機架構,不支援分散式/多伺服器共享 |
| 支援任意可序列化資料 | 大量小檔案會消耗 inode,需定期清理 |
| 原子寫入(LOCK_EX)防競態 | 高併發讀寫效能不如記憶體快取 |
| 檔案系統天然支援 TTL | 無原生標籤/群組失效功能(需自行擴充) |
tmpfs(記憶體檔案系統)大幅提升 IO 效能cron 定期清理過期檔案,避免殘留佔用磁碟