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

ThinkPHP Dir 類檔案目錄操作實戰:快取清理與模板管理應用

     編輯:PHP 相關知識

ThinkPHP Dir 類檔案目錄操作實戰:快取清理與模板管理應用

ThinkPHP 擴展庫提供的 Dir 類別 (ORG.Dir) 封裝了目錄遞迴掃描、檔案刪除、空目錄判斷等常用操作。本文透過實際專案「後台快取清理功能」展示其實戰應用,並補充進階用法與注意事項。

Dir 類核心靜態方法

方法說明回傳值
Dir::isEmpty($path)檢查目錄是否為空 (無檔案、無子目錄)bool
Dir::del($path)遞迴刪除目錄及其所有內容bool
Dir::getFiles($path, $pattern='*')取得目錄下所有檔案 (支援 glob 模式)array
Dir::getDirs($path)取得目錄下所有子目錄array
Dir::create($path, $mode=0777)遞迴建立目錄bool
Dir::copy($src, $dst)遞迴複製目錄bool
Dir::getSize($path)計算目錄總大小 (bytes)int

實戰案例:後台快取分類清理功能

場景需求

專案運行久後 Runtime/ 目錄膨脹,需提供後台介面讓管理員按類型清理:

  • 全部快取
  • 模板編譯快取 (Runtime/Cache/)
  • 資料快取 (Runtime/Data/、Runtime/Temp/)
  • 日誌檔案 (Runtime/Logs/)
  • 前台/後台分別清理

Controller 實作

<?php
class CacheAction extends Action {
    // 快取類型對應路徑映射
    private $cachePaths = [
        'all_front'   => ['path' => 'WEB_PATH.Runtime',       'label' => '前台全部快取'],
        'template_f'  => ['path' => 'WEB_PATH.Runtime/Cache',  'label' => '前台模板快取'],
        'data_f'      => ['path' => 'WEB_PATH.Runtime/Data',   'label' => '前台資料快取'],
        'temp_f'      => ['path' => 'WEB_PATH.Runtime/Temp',   'label' => '前台臨時檔案'],
        'logs_f'      => ['path' => 'WEB_PATH.Runtime/Logs',   'label' => '前台日誌檔案'],
        'all_admin'   => ['path' => 'APP_PATH.Runtime',        'label' => '後台全部快取'],
        'template_a'  => ['path' => 'APP_PATH.Runtime/Cache',  'label' => '後台模板快取'],
        'data_a'      => ['path' => 'APP_PATH.Runtime/Data',   'label' => '後台資料快取'],
        'temp_a'      => ['path' => 'APP_PATH.Runtime/Temp',   'label' => '後台臨時檔案'],
        'logs_a'      => ['path' => 'APP_PATH.Runtime/Logs',   'label' => '後台日誌檔案'],
    ];
    
    public function index() {
        // 顯示快取狀態:大小、檔案數
        $status = [];
        foreach ($this->cachePaths as $key => $info) {
            $path = $info['path'];
            $status[$key] = [
                'label' => $info['label'],
                'exists' => is_dir($path),
                'size'   => is_dir($path) ? $this->formatBytes(Dir::getSize($path)) : 'N/A',
                'files'  => is_dir($path) ? count(Dir::getFiles($path)) : 0,
            ];
        }
        $this->assign('cacheStatus', $status);
        $this->display();
    }
    
    public function clear() {
        $type = I('get.type', '', 'trim');
        if (empty($type) || !isset($this->cachePaths[$type])) {
            $this->error('無效的快取類型');
        }
        
        $path = $this->cachePaths[$type]['path'];
        $label = $this->cachePaths[$type]['label'];
        
        // 載入 Dir 類別 (放在 ORG 目錄下)
        import('@.ORG.Dir');
        
        if (!Dir::isEmpty($path)) {
            if (Dir::del($path)) {
                // 重建目錄結構 (避免 ThinkPHP 找不到目錄報錯)
                $this->rebuildRuntimeStructure($path);
                $this->success("{$label} 清理成功");
            } else {
                $this->error("{$label} 清理失敗,請檢查權限");
            }
        } else {
            $this->success("{$label} 本身就是空的");
        }
    }
    
    private function rebuildRuntimeStructure($clearedPath) {
        // 根據清理的路徑重建必要子目錄
        $subDirs = ['Cache', 'Data', 'Temp', 'Logs', 'Data/_fields'];
        $base = str_replace(['WEB_PATH', 'APP_PATH'], ['', ''], $clearedPath);
        // 實際專案中根據 WEB_PATH/APP_PATH 判斷基準路徑
        foreach ($subDirs as $dir) {
            $fullPath = dirname($clearedPath) . '/' . $dir;
            if (!is_dir($fullPath)) Dir::create($fullPath);
        }
    }
    
    private function formatBytes($bytes, $precision = 2) {
        $units = ['B', 'KB', 'MB', 'GB', 'TB'];
        $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
        $pow = min($pow, count($units) - 1);
        return round($bytes / (1024 ** $pow), $precision) . ' ' . $units[$pow];
    }
}
?>

模板檔案 (Cache/index.html)

<table class="table table-striped">
    <thead>
        <tr><th>快取類型</th><th>狀態</th><th>大小</th><th>檔案數</th><th>操作</th></tr>
    </thead>
    <tbody>
        <volist name="cacheStatus" id="item">
        <tr>
            <td>{$item.label}</td>
            <td>
                <eq name="item.exists" value="true">
                    <span class="badge badge-success">存在</span>
                <else/>
                    <span class="badge badge-secondary">不存在</span>
                </eq>
            </td>
            <td>{$item.size}</td>
            <td>{$item.files}</td>
            <td>
                <eq name="item.exists" value="true">
                    <a href="<?php echo U('clear', ['type'=>$key]); ?>" 
                       class="btn btn-sm btn-danger" 
                       onclick="return confirm('確定清理 {$item.label} 嗎?')"
                    >清理</a>
                <else/>
                    <span class="text-muted">無需清理</span>
                </eq>
            </td>
        </tr>
        </volist>
    </tbody>
</table>

進階應用:模板檔案管理

結合 Dir::getFiles() 實作模板列表、編輯、備份功能:

<?php
class TemplateAction extends Action {
    public function listTemplates() {
        import('@.ORG.Dir');
        $tplPath = TMPL_PATH; // ThinkPHP 模板常數
        
        // 遞迴取得所有 .html 檔案
        $files = $this->getTemplateFiles($tplPath);
        
        $list = [];
        foreach ($files as $file) {
            $relPath = str_replace($tplPath, '', $file);
            $list[] = [
                'path'   => $relPath,
                'size'   => $this->formatBytes(filesize($file)),
                'mtime'  => date('Y-m-d H:i:s', filemtime($file)),
                'url'    => U('edit', ['file'=>urlencode($relPath)]),
            ];
        }
        $this->assign('templates', $list);
        $this->display();
    }
    
    private function getTemplateFiles($dir) {
        $files = [];
        $items = Dir::getFiles($dir, '*.html');
        foreach ($items as $item) {
            if (is_dir($item)) {
                $files = array_merge($files, $this->getTemplateFiles($item));
            } else {
                $files[] = $item;
            }
        }
        return $files;
    }
    
    public function edit() {
        $file = urldecode(I('get.file'));
        $fullPath = TMPL_PATH . $file;
        
        if (!file_exists($fullPath) || is_dir($fullPath)) {
            $this->error('模板檔案不存在');
        }
        
        // 安全檢查:防止目錄遍歷
        if (strpos($file, '..') !== false) {
            $this->error('非法路徑');
        }
        
        $content = file_get_contents($fullPath);
        $this->assign(['file'=>$file, 'content'=>$content]);
        $this->display();
    }
    
    public function save() {
        $file = urldecode(I('post.file'));
        $content = I('post.content');
        $fullPath = TMPL_PATH . $file;
        
        // 備份原檔
        $backupDir = RUNTIME_PATH . 'TemplateBackup/' . date('Ymd');
        Dir::create($backupDir);
        copy($fullPath, $backupDir . '/' . basename($file) . '_' . time() . '.bak');
        
        if (file_put_contents($fullPath, $content) !== false) {
            $this->success('儲存成功,已自動備份');
        } else {
            $this->error('儲存失敗');
        }
    }
}
?>

Dir 類實作原理參考 (若專案未內建)

<?php
// 簡易版 Dir 類核心邏輯
class Dir {
    public static function isEmpty($path) {
        if (!is_dir($path)) return true;
        $handle = opendir($path);
        while (($entry = readdir($handle)) !== false) {
            if ($entry !== '.' && $entry !== '..') {
                closedir($handle);
                return false;
            }
        }
        closedir($handle);
        return true;
    }
    
    public static function del($path) {
        if (!is_dir($path)) return false;
        $items = new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS),
            RecursiveIteratorIterator::CHILD_FIRST
        );
        foreach ($items as $item) {
            $item->isDir() ? rmdir($item->getPathname()) : unlink($item->getPathname());
        }
        return rmdir($path);
    }
    
    public static function getFiles($path, $pattern = '*') {
        $files = [];
        $iterator = new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS)
        );
        foreach ($iterator as $file) {
            if ($file->isFile() && ($pattern === '*' || fnmatch($pattern, $file->getFilename()))) {
                $files[] = $file->getPathname();
            }
        }
        return $files;
    }
    
    public static function getSize($path) {
        $size = 0;
        $iterator = new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS)
        );
        foreach ($iterator as $file) {
            if ($file->isFile()) $size += $file->getSize();
        }
        return $size;
    }
    
    public static function create($path, $mode = 0777) {
        return is_dir($path) || mkdir($path, $mode, true);
    }
}
?>

權限與安全注意事項

  1. 目錄權限:Runtime/ 需 Web 使用者寫入權限 (通常 755 或 775),Dir::create() 預設 0777 受 umask 影響
  2. 路徑注入防護:所有來自用戶輸入的路徑參數必須經過 basename()、realpath() 驗證,禁止 ../ 遍歷
  3. 操作日誌:清理快取、刪除模板等高風險操作務必記錄操作者、時間、IP、具體路徑
  4. 誤刪保護:生產環境建議啟用「軟刪除」機制,移至 Runtime/Trash/ 保留 7 天
  5. 併發安全:大量檔案刪除時可能阻塞,考慮非同步隊列處理 (如 Redis Queue + Worker)

效能優化建議

  • 大目錄刪除改用系統指令:exec('rm -rf ' . escapeshellarg($path)) (需權限評估)
  • getSize() 快取結果,避免重複掃描
  • 前台顯示快取狀態時使用 AJAX 分批載入,避免頁面卡頓
  • 定期清理任務寫入 Crontab,非依賴管理員手動操作
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved