單靠擴展名判斷 MIME 不可靠,單靠 mime_content_type 可能缺失。本文實現一個四層回退的 _getMimeType() 函數,確保在各種 PHP 環境下都能獲得最準確的 MIME 類型。
| 層級 | 方法 | 可用條件 | 準確度 |
|---|---|---|---|
| 1 | finfo_open(FILEINFO_MIME) | PHP 5.3+ Fileinfo 擴展 | ⭐⭐⭐⭐⭐ 魔術位元組檢測 |
| 2 | mime_content_type() | PHP 內建(舊版) | ⭐⭐⭐⭐ 同層級 1 |
| 3 | exec('file -ib') / file -Ib | Linux/BSD 有 file 命令 | ⭐⭐⭐⭐ 系統級檢測 |
| 4 | 擴展名映射表 | 兜底方案 | ⭐⭐ 僅參考副檔名 |
<?php
$mimeMap = [
// applications
'ai'=>'application/postscript', 'eps'=>'application/postscript', 'exe'=>'application/octet-stream',
'doc'=>'application/vnd.ms-word', 'xls'=>'application/vnd.ms-excel', 'ppt'=>'application/vnd.ms-powerpoint',
'pdf'=>'application/pdf', 'xml'=>'application/xml', 'odt'=>'application/vnd.oasis.opendocument.text',
'swf'=>'application/x-shockwave-flash',
// archives
'gz'=>'application/x-gzip', 'tgz'=>'application/x-gzip', 'bz'=>'application/x-bzip2',
'zip'=>'application/zip', 'rar'=>'application/x-rar', 'tar'=>'application/x-tar',
// texts
'txt'=>'text/plain', 'php'=>'text/x-php', 'html'=>'text/html', 'js'=>'text/javascript',
'css'=>'text/css', 'rtf'=>'text/rtf', 'py'=>'text/x-python', 'java'=>'text/x-java-source',
// images
'bmp'=>'image/x-ms-bmp', 'jpg'=>'image/jpeg', 'png'=>'image/png', 'gif'=>'image/gif',
'tif'=>'image/tiff', 'psd'=>'image/vnd.adobe.photoshop',
// audio/video ...
];
function _getMimeDetect() {
if (class_exists('finfo')) return 'finfo';
if (function_exists('mime_content_type')) return 'mime_content_type';
if (function_exists('exec')) {
$r = exec('file -ib ' . escapeshellarg(__FILE__));
if (strpos($r, 'text/x-php') === 0 || strpos($r, 'text/x-c++') === 0) return 'linux';
$r = exec('file -Ib ' . escapeshellarg(__FILE__));
if (strpos($r, 'text/x-php') === 0 || strpos($r, 'text/x-c++') === 0) return 'bsd';
}
return 'internal';
}
function _getMimeType($path) {
global $mimeMap;
$detect = _getMimeDetect();
$type = 'unknown';
switch ($detect) {
case 'finfo':
$finfo = finfo_open(FILEINFO_MIME);
if ($finfo) $type = @finfo_file($finfo, $path);
break;
case 'mime_content_type':
$type = @mime_content_type($path);
break;
case 'linux':
$type = exec('file -ib ' . escapeshellarg($path));
break;
case 'bsd':
$type = exec('file -Ib ' . escapeshellarg($path));
break;
default:
$ext = strtolower(pathinfo($path, PATHINFO_EXTENSION));
$type = $mimeMap[$ext] ?? 'unknown';
}
// 修正:若系統檢測返回 application/octet-stream,嘗試用擴展名修正
if ($detect !== 'internal' && $type === 'application/octet-stream') {
$ext = strtolower(pathinfo($path, PATHINFO_EXTENSION));
if ($ext && isset($mimeMap[$ext])) $type = $mimeMap[$ext];
}
return explode(';', $type)[0]; // 去除 charset 等參數
}
// 測試(文件不存在時走擴展名兜底)
var_dump(_getMimeType('1.txt')); // string(10) "text/plain"
?>
escapeshellarg() 防止命令注入。explode(';', $type)[0] 移除 charset=binary 等後綴。application/octet-stream 這類通用類型,二次嘗試擴展名映射修正。PHP 5.3+ 環境請確保安裝 php-fileinfo 擴展(通常預設開啟),直接使用 finfo 即可獲得最高準確度,無需複雜回退邏輯。
$finfo = new finfo(FILEINFO_MIME_TYPE);
echo $finfo->file('/path/to/file'); // 如 image/png
此多層回退函數適合「需相容未知 PHP 環境」的通用庫開發場景。