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

PHP 5 文件操作完整教學:讀寫、目錄、屬性、鎖定與最佳實踐

     編輯:PHP 相關知識

檔案系統基礎

函數用途回傳值
fopen()開啟檔案指標resource / false
fclose()關閉指標bool
feof()檢查結尾bool
fread()讀取指定位元組string / false
fwrite()寫入字串int / false
fgets()逐行讀取string / false
file_get_contents()一次讀全檔string / false
file_put_contents()一次寫全檔int / false
file()讀成陣列(每行一元素)array / false

目錄操作

// 掃描目錄 (推薦:SPL 迭代器)
$files = new FilesystemIterator('/var/www/uploads');
foreach ($files as $file) {
    if ($file->isFile()) {
        echo $file->getFilename() . ' (' . $file->getSize() . " bytes)\n";
    }
}

// 遞迴掃描
$rdi = new RecursiveDirectoryIterator('/var/www');
$rii = new RecursiveIteratorIterator($rdi);
foreach ($rii as $file) {
    if ($file->isFile()) echo $file->getPathname() . "\n";
}

// 建立/刪除目錄
mkdir('/path/to/dir', 0755, true);  // 遞迴建立
rmdir('/path/to/empty_dir');        // 僅空目錄
// 遞迴刪除非空目錄
function rrmdir($dir) {
    foreach (new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS),
        RecursiveIteratorIterator::CHILD_FIRST
    ) as $file) {
        $file->isDir() ? rmdir($file->getPathname()) : unlink($file->getPathname());
    }
    rmdir($dir);
}

檔案讀取完整範例

function readFileSafe(string $path): ?string {
    // 1. 存在性與可讀性檢查
    if (!is_readable($path)) {
        error_log("File not readable: $path");
        return null;
    }
    
    // 2. 大小檢查 (防止記憶體耗盡)
    $size = filesize($path);
    if ($size > 10 * 1024 * 1024) { // 10MB 限制
        error_log("File too large: $path ($size bytes)");
        return null;
    }
    
    // 3. 讀取 (PHP 5.4+ file_get_contents 支援 stream context)
    $content = @file_get_contents($path);
    if ($content === false) {
        error_log("Read failed: $path");
        return null;
    }
    return $content;
}

// 串流讀取大檔案 (不載入記憶體)
function processLargeFile(string $path, callable $lineHandler): void {
    $fp = fopen($path, 'r');
    if (!$fp) throw new RuntimeException("Cannot open $path");
    
    while (($line = fgets($fp)) !== false) {
        $lineHandler(rtrim($line));
    }
    fclose($fp);
}

檔案寫入與鎖定 (併發安全)

function writeFileAtomic(string $path, string $data): bool {
    $dir = dirname($path);
    $tmp = $dir . '/.' . basename($path) . '.tmp.' . uniqid();
    
    $fp = fopen($tmp, 'w');
    if (!$fp) return false;
    
    // 獨佔鎖
    if (!flock($fp, LOCK_EX)) {
        fclose($fp);
        @unlink($tmp);
        return false;
    }
    
    $written = fwrite($fp, $data);
    fflush($fp);
    flock($fp, LOCK_UN);
    fclose($fp);
    
    if ($written === false) {
        @unlink($tmp);
        return false;
    }
    
    // 原子性重命名 (POSIX 保證)
    return @rename($tmp, $path);
}

// 追加寫入 (日誌適用)
function appendFileLocked(string $path, string $data): bool {
    $fp = fopen($path, 'a');
    if (!$fp) return false;
    if (!flock($fp, LOCK_EX)) { fclose($fp); return false; }
    $ok = fwrite($fp, $data) !== false;
    flock($fp, LOCK_UN);
    fclose($fp);
    return $ok;
}

檔案屬性與資訊

$stat = stat('/path/to/file');
/* 回傳陣列鍵值:
dev, ino, mode, nlink, uid, gid, rdev, size,
atime, mtime, ctime, blksize, blocks
*/

// 常用封裝函數
function fileInfo(string $path): array {
    if (!file_exists($path)) return ['exists' => false];
    $s = stat($path);
    return [
        'exists' => true,
        'size' => $s['size'],
        'size_human' => formatBytes($s['size']),
        'mime' => mime_content_type($path),
        'modified' => date('Y-m-d H:i:s', $s['mtime']),
        'accessed' => date('Y-m-d H:i:s', $s['atime']),
        'changed' => date('Y-m-d H:i:s', $s['ctime']),
        'permissions' => substr(sprintf('%o', $s['mode']), -4),
        'is_readable' => is_readable($path),
        'is_writable' => is_writable($path),
        'owner' => posix_getpwuid($s['uid'])['name'] ?? $s['uid'],
        'group' => posix_getgrgid($s['gid'])['name'] ?? $s['gid'],
    ];
}

function formatBytes(int $bytes): string {
    $units = ['B', 'KB', 'MB', 'GB', 'TB'];
    $i = 0;
    while ($bytes >= 1024 && $i < 4) { $bytes /= 1024; $i++; }
    return round($bytes, 2) . ' ' . $units[$i];
}

複製、移動、刪除

// 複製
copy('/src/file.txt', '/dst/file.txt') or die('Copy failed');

// 移動/重命名 (跨檔案系統 rename 會失敗,需 copy+unlink)
function moveFile(string $src, string $dst): bool {
    if (@rename($src, $dst)) return true;
    // 跨裝置 fallback
    if (copy($src, $dst)) {
        unlink($src);
        return true;
    }
    return false;
}

// 刪除
unlink('/path/to/file') or die('Delete failed');

最佳實踐總結

  1. 永遠檢查回傳值:所有檔案函數失敗回傳 false
  2. 使用絕對路徑:避免工作目錄變更導致錯誤
  3. 大檔案用串流:fopen + fgets 避免記憶體爆炸
  4. 併發寫入必鎖:flock(LOCK_EX) 獨佔、LOCK_SH 共享
  5. 原子性寫入:暫存檔 + rename() 保證一致性
  6. 權限最小化:目錄 0755、檔案 0644、敏感設定檔 0600
  7. 路徑注入防護:basename()、realpath() 驗證在允許目錄內
  8. 錯誤記錄:error_log() 記錄失敗詳情,勿直接 die()

PHP 8.1+ 現代化寫法

// 使用 FilesystemIterator + SplFileObject
$file = new SplFileObject('/path/to/file', 'r');
$file->setFlags(SplFileObject::READ_AHEAD | SplFileObject::SKIP_EMPTY);
foreach ($file as $lineNum => $line) {
    // $lineNum 從 0 開始
    processLine($line);
}

// 類型安全
function readJson(string $path): array|object {
    $content = file_get_contents($path);
    $data = json_decode($content, true, 512, JSON_THROW_ON_ERROR);
    return $data;
}
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved