「獲取文件擴展名」是高頻需求,網上流傳著多種寫法。但實際業務中,**路徑可能包含多個點、可能無擴展名、可能以點開頭(隱藏文件)**,稍有不慎就會產生 Bug。本文對比 5 種常見方法,揭示其邊界條件差異,給出**唯一推薦的標準寫法**。
| # | 代碼 | 核心邏輯 |
|---|---|---|
| 1 | substr(strrchr($file, '.'), 1) | 找最後一個點,取其右側 |
| 2 | substr($file, strrpos($file, '.') + 1) | 找最後一點位置,截取後綴 |
| 3 | end(explode('.', $file)) | 按點分割,取最後一段 |
| 4 | pathinfo($file)['extension'] | 用 pathinfo 解析,取 extension 鍵 |
| 5 | pathinfo($file, PATHINFO_EXTENSION) | pathinfo 直接返回擴展名字符串 |
| 測試路徑 | 預期結果 | 場景說明 |
|---|---|---|
/home/test.d/test.txt | txt | 路徑中含點(目錄名),文件有擴展名 |
/home/test.d/test | '' (空串) | 路徑中含點,文件**無**擴展名 |
/home/user/file | '' (空串) | 純文件名,無擴展名、無路徑點 |
.htaccess | '' (空串) | 隱藏文件,以點開頭,無擴展名 |
archive.tar.gz | gz | 多重擴展名,取最後一段 |
| 方法 | /home/test.d/test.txt | /home/test.d/test | /home/user/file | .htaccess | archive.tar.gz | 致命缺陷 |
|---|---|---|---|---|---|---|
| 1. strrchr | txt ✅ | test ❌ | file ❌ | htaccess ❌ | gz ✅ | 無擴展名時返回整個文件名/路徑尾段 |
| 2. strrpos+substr | txt ✅ | test ❌ | file ❌ | htaccess ❌ | gz ✅ | 同方法 1,strrpos 找到目錄點 |
| 3. explode+end | txt ✅ | test ❌ | file ❌ | htaccess ❌ | gz ✅ | 無擴展名時返回文件名;多點路徑錯誤 |
| 4. pathinfo()['extension'] | txt ✅ | Notice: Undefined index ❌ | Notice ❌ | Notice ❌ | gz ✅ | 無擴展名時鍵不存在,觸發 Notice |
| 5. pathinfo(..., PATHINFO_EXTENSION) | txt ✅ | '' ✅ | '' ✅ | '' ✅ | gz ✅ | 無缺陷,全場景正確 |
function pathinfo($path, $options = null) {
$basename = basename($path); // 1. 先取文件名(去除目錄)
$pos = strrpos($basename, '.'); // 2. 在文件名中找最後一個點
if ($pos === false || $pos === 0) { // 3. 無點 或 點在首位(.htaccess)
$extension = '';
} else {
$extension = substr($basename, $pos + 1);
}
$result = ['dirname' => dirname($path), 'basename' => $basename];
if ($extension !== '') { // 4. **只有有擴展名時才加入鍵**
$result['extension'] = $extension;
}
if ($options) return $result[$options]; // 5. 直接返回指定部分(不觸發 Notice)
return $result;
}
關鍵差異:
pathinfo($file)['extension'] 先獲取完整陣列,**無擴展名時鍵根本不存在**,直接訪問觸發 Undefined index Notice;pathinfo($file, PATHINFO_EXTENSION) 走內部 $options 分支,**直接返回字符串(空串或擴展名)**,完全繞過陣列鍵訪問,**零 Notice、零判斷、最快**。| 常量 | 值 | 含義 | 示例返回 |
|---|---|---|---|
PATHINFO_DIRNAME | 1 | 目錄路徑 | /home/test.d |
PATHINFO_BASENAME | 2 | 文件名(含擴展名) | test.txt |
PATHINFO_EXTENSION | 4 | 擴展名(不含點) | txt |
PATHINFO_FILENAME | 8 | 文件名(不含擴展名,PHP 5.2+) | test |
⚠ 注意: 這四個常量值為 1、2、4、8(位掩碼),**不能位或組合**(PATHINFO_EXTENSION | PATHINFO_FILENAME 會返回值較小的那個),每次只能查詢單一部分。
/**
* 安全獲取文件擴展名(小寫、無點)
* @param string $path 文件路徑或文件名
* @return string 擴展名,無則返回空字符串
*/
function get_file_extension($path) {
// 1. 核心:pathinfo 直接返回字符串,無 Notice、處理所有邊界
$ext = pathinfo($path, PATHINFO_EXTENSION);
// 2. 統一小寫(擴展名不區分大小寫)
return strtolower($ext);
}
// 測試
var_dump(get_file_extension('/home/test.d/test.txt')); // string(3) "txt"
var_dump(get_file_extension('/home/test.d/test')); // string(0) ""
var_dump(get_file_extension('.htaccess')); // string(0) ""
var_dump(get_file_extension('archive.tar.gz')); // string(2) "gz"
$info = new SplFileInfo('/home/test.d/test.txt');
echo $info->getExtension(); // "txt",同 pathinfo(..., PATHINFO_EXTENSION)
echo $info->getBasename('.txt'); // "test",獲取不含指定擴展名的文件名
優勢: 物件式 API、可鏈式調用、配合 Iterator 批量處理文件更優雅。
pathinfo($path, PATHINFO_EXTENSION) —— 標準庫、全場景正確、無 Notice、效能最優;explode('.', $path)、strrchr、strrpos+substr、pathinfo()['extension'] —— 均在「路徑含點、無擴展名、隱藏文件」場景失效;get_file_extension() 統一入口,返回小寫,配合白名單驗證(in_array($ext, ['jpg','png','gif','pdf']))完成上傳校驗。這個「小功能」折射出的工程素養:**永遠優先信任標準庫、深度理解邊界條件、封裝統一入口防止重複造輪子**。