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

PHP 獲取文件擴展名的三種方法與最佳實踐

     編輯:PHP 相關知識

核心需求

從文件名/路徑中可靠提取擴展名 (不含點號),處理邊界情況:多點文件名、隱藏文件、無擴展名、大小寫、URL 路徑等。

方法對比

方法一:pathinfo() —— 官方推薦、功能最全

<?php
function getExtensionPathinfo(string $filename): ?string {
    $info = pathinfo($filename);
    return $info['extension'] ?? null;
}

// 測試
var_dump(getExtensionPathinfo('image.jpg'));           // string(3) "jpg"
var_dump(getExtensionPathinfo('archive.tar.gz'));      // string(2) "gz" (只取最後一段)
var_dump(getExtensionPathinfo('.htaccess'));           // string(8) "htaccess" (隱藏文件視為無目錄名)
var_dump(getExtensionPathinfo('README'));              // NULL
var_dump(getExtensionPathinfo('/path/to/file.PDF'));   // string(3) "PDF" (保持原大小寫)
var_dump(getExtensionPathinfo('file.name.with.dots.txt')); // string(3) "txt"
var_dump(getExtensionPathinfo('https://cdn.example.com/avatar.png?version=123')); // string(3) "png" (自動忽略查詢字符串)

方法二:SplFileInfo 物件導風格 (PHP 5.3+)

<?php
function getExtensionSpl(string $filename): string {
    return (new SplFileInfo($filename))->getExtension();
}

// 行為與 pathinfo() 一致,但更適合現代 OOP 代碼風格
// 可鏈式調用:(new SplFileInfo($path))->getExtension()

方法三:basename + strrpos/substr —— 手動解析、教學用

<?php
function getExtensionManual(string $filename): ?string {
    $basename = basename($filename);           // 去除目錄
    $pos = strrpos($basename, '.');            // 最後一個點
    if ($pos === false || $pos === 0) return null; // 無點 或 以點開頭(.htaccess)
    return substr($basename, $pos + 1);
}

// 注意:不自動處理 URL 查詢字符串,需額外 parse_url()

方法四:正則表達式 (不推薦常規使用)

<?php
function getExtensionRegex(string $filename): ?string {
    // 匹配最後一個點後面的非點字符直到結尾或問號/井號
    if (preg_match('/\.([^.\/?#]+)(?:[\?#]|$)/', $filename, $m)) {
        return $m[1];
    }
    return null;
}

邊界情況測試矩陣

輸入pathinfo()SplFileInfo手動解析備註
`photo.jpg``jpg``jpg``jpg`標準
`archive.tar.gz``gz``gz``gz`雙擴展名只取最後
`.env``env``env``null`隱藏文件視為擴展名
`README``null``''``null`無擴展名
`file.``''``''``''`以點結尾,空擴展名
`/path/to/file.PDF``PDF``PDF``PDF`保持大小寫
`url.com/a.png?v=1``png``png``null`需配合 parse_url()
`file.name.with.dots.txt``txt``txt``txt`多點文件名

最佳實踐封裝:統一小寫、驗證白名單

<?php
/**
 * 獲取標準化文件擴展名 (小寫、無點號)
 * 
 * @param string $filename 文件名或路徑或 URL
 * @param bool $stripQuery 是否移除 URL 查詢字符串
 * @return string|null 擴展名小寫,無擴展名返回 null
 */
function getFileExtension(string $filename, bool $stripQuery = true): ?string {
    // 1. 處理 URL
    if ($stripQuery && (str_starts_with($filename, 'https://') || str_starts_with($filename, 'https://'))) {
        $parsed = parse_url($filename);
        $filename = $parsed['path'] ?? $filename;
    }
    
    // 2. 核心提取 (pathinfo 最可靠)
    $ext = pathinfo($filename, PATHINFO_EXTENSION);
    
    // 3. 規範化
    if ($ext === '' || $ext === null) return null;
    return strtolower($ext);
}

/**
 * 驗證擴展名是否在允許白名單中
 */
function isAllowedExtension(string $filename, array $allowed, bool $strict = true): bool {
    $ext = getFileExtension($filename);
    if ($ext === null) return false;
    
    $allowed = array_map('strtolower', $allowed);
    return in_array($ext, $allowed, $strict);
}

// 使用示例
$allowed = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'pdf'];
$files = ['photo.JPG', 'document.pdf', 'script.php', 'image.png?version=2', '.htaccess'];

foreach ($files as $file) {
    $ext = getFileExtension($file);
    $ok = isAllowedExtension($file, $allowed);
    echo "{$file} => ext: " . ($ext ?? 'none') . " | allowed: " . ($ok ? 'yes' : 'no') . "\n";
}
// 輸出:
// photo.JPG => ext: jpg | allowed: yes
// document.pdf => ext: pdf | allowed: yes
// script.php => ext: php | allowed: no
// image.png?version=2 => ext: png | allowed: yes
// .htaccess => ext: htaccess | allowed: no

上傳文件場景:不要信任客戶端擴展名

<?php
function validateUploadedFile(array $file, array $allowedExt, array $allowedMime): bool {
    // 1. 檢查上傳錯誤
    if ($file['error'] !== UPLOAD_ERR_OK) return false;
    
    // 2. 檢查 MIME 類型 (finfo, 服務端檢測)
    $finfo = new finfo(FILEINFO_MIME_TYPE);
    $mime = $finfo->file($file['tmp_name']);
    if (!in_array($mime, $allowedMime, true)) return false;
    
    // 3. 檢查擴展名與 MIME 一致性
    $ext = getFileExtension($file['name']);
    $expectedMime = mimeFromExtension($ext); // 需維護映射表
    if ($mime !== $expectedMime) return false; // 防止 .php 改名為 .jpg
    
    // 4. 檢查白名單
    return isAllowedExtension($file['name'], $allowedExt);
}

// MIME 映射表示例
function mimeFromExtension(string $ext): string {
    $map = [
        'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg',
        'png' => 'image/png', 'gif' => 'image/gif',
        'webp' => 'image/webp', 'pdf' => 'application/pdf',
    ];
    return $map[strtolower($ext)] ?? 'application/octet-stream';
}

效能微基準 (100 萬次調用)

<?php
$files = ['test.jpg', 'archive.tar.gz', '.env', 'README', 'path/to/file.PDF'];
$iterations = 1_000_000;

$start = hrtime(true);
for ($i = 0; $i < $iterations; $i++) {
    foreach ($files as $f) pathinfo($f, PATHINFO_EXTENSION);
}
echo "pathinfo: " . (hrtime(true) - $start) / 1e9 . "s\n"; // ~0.18s

$start = hrtime(true);
for ($i = 0; $i < $iterations; $i++) {
    foreach ($files as $f) (new SplFileInfo($f))->getExtension();
}
echo "SplFileInfo: " . (hrtime(true) - $start) / 1e9 . "s\n"; // ~0.45s

$start = hrtime(true);
for ($i = 0; $i < $iterations; $i++) {
    foreach ($files as $f) {
        $b = basename($f);
        $p = strrpos($b, '.');
        if ($p !== false && $p > 0) substr($b, $p + 1);
    }
}
echo "Manual: " . (hrtime(true) - $start) / 1e9 . "s\n"; // ~0.12s (最快但功能最少)

總結建議

  1. 通用場景:`pathinfo($filename, PATHINFO_EXTENSION)` —— 功能完整、可讀性好、效能足夠
  2. OOP 風格代碼庫:`(new SplFileInfo($filename))->getExtension()` —— 鏈式調用優雅
  3. 高頻熱點路徑/極致效能:手動 `basename + strrpos + substr` —— 但需自行處理 URL、隱藏文件等邊界
  4. 安全關鍵場景 (上傳、包含、執行):絕不信任擴展名,必須配合 `finode` MIME 檢測、白名單、隨機重命名存儲
  5. 統一規範:全項目封裝 `getFileExtension()` 工具函數,強制小寫、移除查詢串、白名單驗證
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved