首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP 檔案快取類實作:支援過期時間、自動序列化、快取狀態回傳的輕量級方案

     編輯:PHP 綜合知識

專案初期或中小型應用,引入 Redis/Memcached 可能過重。一個設計良好的檔案快取類,支援 TTL 過期、序列化儲存、快取狀態判斷(無快取/有效/過期),即可滿足 80% 場景需求。本文提供生產就緒的 `FileCache` 類別完整程式碼與使用範例。

核心類別:XZCache(增強版)

<?php
/**
 * 輕量級檔案快取類
 * 功能:TTL 過期、serialize 序列化、原子寫入、狀態回傳(0無/1有效/2過期)
 */
class FileCache
{
    private string $cacheDir;
    private int $defaultTtl;

    public function __construct(string $cacheDir = './cache', int $defaultTtl = 3600)
    {
        $this->cacheDir = rtrim($cacheDir, '/') . '/';
        $this->defaultTtl = $defaultTtl;
        if (!is_dir($this->cacheDir)) {
            mkdir($this->cacheDir, 0755, true);
        }
    }

    /**
     * 寫入快取
     * @return bool 成功與否
     */
    public function set(string $key, mixed $value, int $ttl = null): bool
    {
        $ttl = $ttl ?? $this->defaultTtl;
        $file = $this->cacheDir . 'cache_' . md5($key) . '.dat';
        $tmpFile = $file . '.tmp.' . bin2hex(random_bytes(4));

        $data = [
            'expire' => $ttl > 0 ? time() + $ttl : 0,
            'value'  => $value,
        ];
        $serialized = serialize($data);

        if (file_put_contents($tmpFile, $serialized, LOCK_EX) === false) {
            @unlink($tmpFile);
            return false;
        }
        if (!@rename($tmpFile, $file)) {
            @unlink($tmpFile);
            return false;
        }
        @chmod($file, 0644);
        return true;
    }

    /**
     * 讀取快取
     * @return array ['cached' => 0|1|2, 'value' => mixed]
     *   cached: 0=無快取, 1=有效, 2=已過期
     */
    public function get(string $key): array
    {
        $file = $this->cacheDir . 'cache_' . md5($key) . '.dat';
        $result = ['cached' => 0, 'value' => null];

        if (!file_exists($file)) {
            return $result;
        }

        $content = @file_get_contents($file);
        if ($content === false) {
            return $result;
        }

        $data = @unserialize($content);
        if ($data === false || !isset($data['expire'], $data['value'])) {
            // 損壞的快取檔,刪除
            @unlink($file);
            return $result;
        }

        if ($data['expire'] > 0 && $data['expire'] < time()) {
            $result['cached'] = 2; // 過期
            // 可選:自動清理過期檔
            // @unlink($file);
            return $result;
        }

        $result['cached'] = 1;
        $result['value'] = $data['value'];
        return $result;
    }

    public function delete(string $key): bool
    {
        $file = $this->cacheDir . 'cache_' . md5($key) . '.dat';
        return file_exists($file) ? unlink($file) : false;
    }

    public function clear(): bool
    {
        $files = glob($this->cacheDir . 'cache_*.dat');
        $success = true;
        foreach ($files as $f) {
            if (!@unlink($f)) $success = false;
        }
        return $success;
    }

    // 便利方法:直接取得值或預設值
    public function fetch(string $key, mixed $default = null): mixed
    {
        $res = $this->get($key);
        return $res['cached'] === 1 ? $res['value'] : $default;
    }
}

使用範例

require_once 'FileCache.php';

$cache = new FileCache(__DIR__ . '/runtime/cache', 1800); // 30 分鐘預設 TTL

// 寫入
$cache->set('site_config', ['theme' => 'dark', 'lang' => 'zh-TW'], 3600);

// 讀取(判斷狀態)
$result = $cache->get('site_config');
switch ($result['cached']) {
    case 1:
        echo "快取命中: ";
        print_r($result['value']);
        break;
    case 2:
        echo "快取過期,重新查詢資料庫...";
        // 重新查詢 DB -> $cache->set(...)
        break;
    case 0:
    default:
        echo "無快取,首次查詢資料庫...";
        break;
}

// 簡易用法:直接取值或預設
$config = $cache->fetch('site_config', ['theme' => 'light', 'lang' => 'en']);

// 刪除
$cache->delete('site_config');

// 清空所有
$cache->clear();

關鍵設計細節解析

設計點做法理由
檔案命名`cache_{md5(key)}.dat`避免鍵值特殊字元導致路徑問題;長度固定利於目錄掃描
原子寫入寫入 `.tmp.xxxx` → `rename()` 覆蓋避免高併發讀到半寫入檔案;`rename` 在同檔案系統下為原子操作
鎖定機制`file_put_contents(..., LOCK_EX)`防止多進程同時寫入同一暫存檔(極低機率衝突)
資料結構陣列包 `expire` + `value`單一檔案自包含過期資訊,無需額外索引檔
序列化`serialize()` / `unserialize()`支援任意 PHP 型別(陣列、物件、資源除外);可換 `igbinary` 提升效能
狀態碼0/1/2 三態呼叫端可精確得知狀態,決定是否回源查詢

效能優化建議

  1. 啟用 igbinary:`pecl install igbinary`,將 `serialize`/`unserialize` 替換為 `igbinary_serialize`/`igbinary_unserialize`,序列化體積縮減 50%+、速度提升 2-3 倍。
  2. 掛載 tmpfs:`mount -t tmpfs -o size=256m tmpfs /var/www/runtime/cache`,將快取目錄置於記憶體,I/O 延遲降至微秒級。
  3. 定期清理過期檔:Cron 每小時執行 `find /cache -name 'cache_*.dat' -mmin +120 -delete`,避免 inode 耗盡。
  4. 避免大物件:單一快取項目建議 < 512KB,大資料拆分或改用 Redis。

擴充:支援標籤清除

// 在類別內新增
private array $tagIndex = [];
private string $tagIndexFile;

public function __construct(...) {
    // ...
    $this->tagIndexFile = $this->cacheDir . '.tags.json';
    if (file_exists($this->tagIndexFile)) {
        $this->tagIndex = json_decode(file_get_contents($this->tagIndexFile), true) ?? [];
    }
}

public function setWithTags(string $key, mixed $value, array $tags, int $ttl = null): bool
{
    if (!$this->set($key, $value, $ttl)) return false;
    foreach ($tags as $tag) {
        $this->tagIndex[$tag][$key] = true;
    }
    $this->saveTagIndex();
    return true;
}

public function deleteByTag(string $tag): int
{
    $keys = $this->tagIndex[$tag] ?? [];
    $count = 0;
    foreach (array_keys($keys) as $key) {
        if ($this->delete($key)) $count++;
    }
    unset($this->tagIndex[$tag]);
    $this->saveTagIndex();
    return $count;
}

private function saveTagIndex(): void
{
    file_put_contents($this->tagIndexFile, json_encode($this->tagIndex), LOCK_EX);
}

這個檔案快取類別零依賴、介面清晰、功能完備,適合直接納入專案共用庫,或作為 PSR-16 介面的簡易實作替代品。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved