PHP 處理本地檔案系統功能強大,但常遇到權限、路徑、跨平台相容性問題。本文系統整理 8 大核心操作場景,提供最佳實務範例與避坑指南。
resource fopen ( string $filename, string $mode [, bool $use_include_path [, resource $context ]] )
| 模式 | 讀/寫 | 指標位置 | 截斷 | 建立 | 適用場景 |
|---|---|---|---|---|---|
| r | 唯讀 | 開頭 | 否 | 否 | 讀取現有檔案 |
| r+ | 讀寫 | 開頭 | 否 | 否 | 讀寫現有檔案 |
| w | 唯寫 | 開頭 | 是 | 是 | 覆寫/新建檔案 |
| w+ | 讀寫 | 開頭 | 是 | 是 | 讀寫覆寫/新建 |
| a | 唯寫 | 結尾 | 否 | 是 | 記錄檔、日誌追加 |
| a+ | 讀寫 | 結尾 | 否 | 是 | 追加並需讀取 |
| x | 唯寫 | 開頭 | 否 | 獨佔建立 | 防止覆寫既有檔(PHP 4.3.2+) |
| x+ | 讀寫 | 開頭 | 否 | 獨佔建立 | 同上,可讀寫 |
'rb', 'wb' 避免 Windows \r\n 轉換損壞圖片/二進位資料https://, ftp:// 需 allow_url_fopen=On)$fp = @fopen($file, 'rb') ?: throw new Exception("無法開啟: $file")bool fclose ( resource $handle )
確保緩衝區資料寫入磁碟、釋放檔案鎖定、歸還系統檔案描述符。良好習慣:配合 try/finally 或 register_shutdown_function 確保關閉。
| 函式 | 行為 | 適用場景 | 注意事項 |
|---|---|---|---|
| fgets() | 讀一行(含換行符) | 文字檔逐行處理 | 預設 1KB,可指定長度;二進位檔結果不可預期 |
| fgetss() | 讀一行並移除 HTML 標籤 | 解析含標籤文字 | 可指定允許保留標籤 |
| fgetc() | 讀單一字元 | 字元級處理 | 效能極差,避免大檔使用 |
| fread() | 讀指定位元組 | 二進位檔、大檔分塊讀取 | 需指定長度,配合 filesize() 或循環讀取 |
| file() | 整檔讀入陣列(每行一元素) | 小型設定檔、CSV | 大檔會耗盡記憶體 |
| file_get_contents() | 整檔讀入字串 | 小檔快速讀取、URL 內容 | 同上,記憶體風險 |
$handle = fopen('large.log', 'rb');
while (!feof($handle)) {
$chunk = fread($handle, 8192); // 8KB 緩衝區
process($chunk); // 處理邏輯
}
fclose($handle);
feof($handle):到達 EOF 回傳 trueftell($handle):取得當前指標位移(位元組)fseek($handle, $offset, SEEK_SET|CUR|END):移動指標rewind($handle):指標歸零filesize($filename):取得檔案大小(快取結果,明確需 clearstatcache())int fwrite ( resource $handle, string $string [, int $length ] )
$file = 'data.txt';
$content = "新資料\n";
$fp = fopen($file, 'c+'); // 開啟讀寫、不截斷、建立新檔
if (flock($fp, LOCK_EX)) { // 獨佔鎖定
ftruncate($fp, 0); // 清空內容(如需覆寫)
fwrite($fp, $content);
fflush($fp); // 強制刷新到作業系統緩衝區
flock($fp, LOCK_UN); // 釋放鎖定
} else {
throw new Exception("無法取得檔案鎖定");
}
fclose($fp);
$binary = file_get_contents('image.png');
$result = fwrite($fp, $binary, strlen($binary)); // PHP 5.6+ 可省略長度
// 或 mb_strlen($binary, '8bit') 確保位元組長度
| 函式 | 回傳值 | 說明 |
|---|---|---|
| is_readable() | bool | 可讀(含權限、存在性) |
| is_writable() / is_writeable() | bool | 可寫 |
| file_exists() | bool | 存在(檔案/目錄/連結) |
| is_file() | bool | 一般檔案 |
| is_dir() | bool | 目錄 |
| fileperms() | int | Unix 權限位元組(配合 decoct() 顯示) |
| fileowner() / filegroup() | int | 擁有者 UID/GID(需 POSIX 擴充) |
| filemtime() / fileatime() / filectime() | int | 修改/存取/狀態改變時間戳 |
bool unlink ( string $filename ) // 刪除檔案
bool rename ( string $old, string $new ) // 移動/重命名(跨檔案系統可能失敗)
注意: 刪除前建議 is_writable() 檢查目錄權限;重命名可用於原子性更新(寫暫存檔 → rename 覆蓋目標)。
basename($path) // 檔案名
dirname($path) // 目錄路徑
pathinfo($path) // 關聯陣列:dirname, basename, extension, filename
realpath($path) // 絕對路徑(解析連結、相對路徑)
$dh = opendir('/path/to/dir');
while (($entry = readdir($dh)) !== false) {
if ($entry === '.' || $entry === '..') continue;
$full = '/path/to/dir/' . $entry;
echo is_dir($full) ? "[DIR] $entry\n" : "[FILE] $entry\n";
}
closedir($dh);
// 現代寫法:DirectoryIterator (SPL)
foreach (new DirectoryIterator('/path') as $file) {
if ($file->isDot()) continue;
echo $file->getFilename() . ($file->isDir() ? '/' : '') . "\n";
}
mkdir($path, 0755, true) // 遞迴建立,權限受 umask 影響
rmdir($path) // 僅限空目錄
chdir($path) // 改變當前工作目錄(影響相對路徑)
function rcopy($src, $dst) {
$dir = opendir($src);
@mkdir($dst, 0755, true);
while (($file = readdir($dir)) !== false) {
if ($file == '.' || $file == '..') continue;
$srcPath = "$src/$file";
$dstPath = "$dst/$file";
if (is_dir($srcPath)) rcopy($srcPath, $dstPath);
else copy($srcPath, $dstPath);
}
closedir($dir);
}
function rdelete($dir) {
$items = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($items as $item) {
$item->isDir() ? rmdir($item->getPathname()) : unlink($item->getPathname());
}
rmdir($dir);
}
/,PHP 在 Windows 也能正確解析DIRECTORY_SEPARATOR 常數組合路徑C:\path,改用 __DIR__、getcwd()、環境變數現代 PHP(7.1+)建議使用 SplFileObject 物件導向檔案操作,內建迭代器、CSV 解析、鎖定支援,程式碼更優雅、型別安全。大量檔案處理考慮非同步 I/O(Swoole、ReactPHP)或外部工具。