file_exists() 是 PHP 判斷文件/目錄是否存在的核心函數,但在處理中文路徑時經常「失靈」——明明文件存在卻返回 false。這篇文章深入剖析編碼衝突的根本原因,提供三種不同層面的解決方案,並給出生產環境最佳實踐。
<?php
// 頁面編碼:UTF-8
$file = '/attachment/21/0/中文.rar';
$fullPath = __DIR__ . $file;
var_dump(file_exists($fullPath)); // bool(false) —— 但文件明明存在!
| 層面 | 典型編碼 | 說明 |
|---|---|---|
| PHP 腳本文件 | UTF-8 | 現代框架、編輯器默認編碼 |
| 字符串字面量 | UTF-8 | '中文.rar' 在 UTF-8 文件中即為 UTF-8 字節序列 |
| 文件系統層 | 視作業系統而定 | Windows: GBK/CP936;Linux: UTF-8(現代發行版) |
核心衝突: PHP 將字符串作為字節序列直接傳遞給系統調用。若腳本編碼(UTF-8)與文件系統期望編碼(Windows 下 GBK)不一致,系統找不到對應字節序列的文件,返回不存在。
<?php
$file = '/attachment/21/0/中文.rar';
$fullPath = __DIR__ . $file;
// 將 UTF-8 路徑轉為系統期望編碼(Windows 中文環境通常為 GBK/CP936)
$systemPath = iconv('UTF-8', 'GBK//IGNORE', $fullPath);
var_dump(file_exists($systemPath)); // bool(true)
//IGNORE 可能靜默丟失無法轉換字符iconv 行為更嚴格,可能拋出異常<?php
function fileExistsUnicode(string $path): bool
{
$dir = dirname($path);
$basename = basename($path);
if (!is_dir($dir)) return false;
// DirectoryIterator 內部正確處理 Unicode 路徑
foreach (new DirectoryIterator($dir) as $fileInfo) {
if (!$fileInfo->isDot() && $fileInfo->getFilename() === $basename) {
return true;
}
}
return false;
}
// 或使用 glob(需注意特殊字符轉義)
function fileExistsGlob(string $path): bool
{
$dir = dirname($path);
$pattern = $dir . DIRECTORY_SEPARATOR . addcslashes(basename($path), '[]*?\\');
return (bool)glob($pattern);
}
PHP 8.0 重寫了文件系統函數內部實現,在 Windows 上調用 Unicode 版 API (Wide API, *W 系列),原生支持 UTF-8 路徑:
<?php
// PHP 8.0+ 直接使用 UTF-8 路徑即可
$file = '/attachment/21/0/中文.rar';
$fullPath = __DIR__ . $file;
var_dump(file_exists($fullPath)); // bool(true) ✅ 無需任何轉換
iconv 轉換或用 DirectoryIterator<?php
class PathResolver
{
/**
* 判斷文件/目錄是否存在(跨平台、Unicode 安全)
*/
public static function exists(string $path): bool
{
// PHP 8.0+ 直接調用原生函數
if (PHP_VERSION_ID >= 80000) {
return file_exists($path);
}
// 7.x 及以下:Windows 用 DirectoryIterator,Linux 直接用
if (DIRECTORY_SEPARATOR === '\\') {
return self::existsWindows($path);
}
return file_exists($path);
}
private static function existsWindows(string $path): bool
{
$dir = dirname($path);
$name = basename($path);
if (!is_dir($dir)) return false;
// 嘗試 DirectoryIterator(PHP 內部處理 Unicode)
try {
foreach (new DirectoryIterator($dir) as $item) {
if (!$item->isDot() && $item->getFilename() === $name) {
return true;
}
}
} catch (Throwable) {
// 降級:嘗試 iconv 轉 GBK
$converted = @iconv('UTF-8', 'GBK//IGNORE', $path);
return $converted !== false && file_exists($converted);
}
return false;
}
/**
* 安全讀取文件內容(自動處理編碼)
*/
public static function readFile(string $path): string|false
{
if (!self::exists($path)) return false;
if (PHP_VERSION_ID >= 80000 || DIRECTORY_SEPARATOR !== '\\') {
return file_get_contents($path);
}
$converted = @iconv('UTF-8', 'GBK//IGNORE', $path);
return $converted !== false ? @file_get_contents($converted) : false;
}
}
// 使用示例
$path = __DIR__ . '/uploads/用戶頭像/張三_2024.jpg';
if (PathResolver::exists($path)) {
$content = PathResolver::readFile($path);
// 處理...
}
以下函數在舊版 PHP Windows 環境下同樣存在中文路徑問題,統一使用上述工具類封裝:
is_file(), is_dir(), is_readable(), is_writable()fopen(), file_get_contents(), file_put_contents()unlink(), mkdir(), rmdir(), rename()glob(), scandir()DirectoryIterator 實現跨平台 Unicode 安全訪問iconv('UTF-8', 'GBK'),會導致 Linux 部署失效文件系統操作是 I/O 密集型且平台敏感的領域,統一封裝路徑處理邏輯,既能解決中文亂碼,也能為未來遷移雲存儲 (S3/OSS)、分布式文件系統預留接口。