最稳妥方式是用pathinfo($path, pathinfo_extension),它专为解析路径设计,能正确处理查询参数、多点文件名、空后缀等边界情况,且跨平台安全。

用 pathinfo() 提取后缀最稳妥
PHP 里最可靠、最常用的方式就是 pathinfo(),它专为解析路径设计,能正确处理各种边界情况,比如带查询参数的 URL 路径、多点文件名(archive.tar.gz)、空后缀或无后缀文件。
关键点是必须传入 PATHINFO_EXTENSION 第二个参数,否则默认返回整个数组,容易误读:
$ext = pathinfo('/var/www/image.jpg', PATHINFO_EXTENSION); // 'jpg'
$ext = pathinfo('data.json?cache=1', PATHINFO_EXTENSION); // 'json'(自动忽略 query string)
$ext = pathinfo('README', PATHINFO_EXTENSION); // ''(空字符串,不是 null)
- 别用
substr(strrchr($path, '.'), 1):遇到file.name.txt会错拿成name.txt - 别对
$_FILES['file']['name']直接用explode('.', ...):用户上传my.photo.jpeg时,最后一个点才是真实后缀,但explode拿的是jpeg—— 表面碰巧对,实际逻辑错 -
pathinfo()不修改原始路径,也不依赖当前系统目录分隔符,跨 Windows/Linux 安全
遇到 .tar.gz 这类复合后缀怎么办
原生 pathinfo() 只认最后一个点之后的部分,所以 backup.tar.gz 返回 gz,不是 tar.gz。这不 bug,是设计如此——它提取的是「扩展名」,不是「归档类型」。
如果你真需要识别复合后缀(比如做上传白名单校验),得自己加一层判断:
立即学习“PHP免费学习笔记(深入)”;
$filename = 'app-v2.1.0.tar.gz';
$ext = pathinfo($filename, PATHINFO_EXTENSION); // 'gz'
$second_ext = pathinfo(pathinfo($filename, PATHINFO_FILENAME), PATHINFO_EXTENSION); // 'tar'
if ($ext === 'gz' && $second_ext === 'tar') {
$full_ext = 'tar.gz';
}
- 不要硬写正则匹配
.tar.gz$:用户可能传TAR.GZ、.Tar.Gz,大小写和点位置不可控 - 优先用
mime_content_type()或finfo_open()做二次校验:后缀可伪造,文件头更可信 - 多数业务场景(如 Web 上传)只校验最终后缀就够了,
tar.gz和gz通常走同一套解压逻辑,不必强求“完整后缀”
basename() + strrpos() 是轻量替代方案
如果项目禁用 pathinfo()(极少见),或者你只想从纯文件名里取后缀(已知不含路径),可以用 basename() 配合 strrpos() 手动截取:
$name = 'photo.png'; $dot_pos = strrpos($name, '.'); $ext = ($dot_pos === false) ? '' : substr($name, $dot_pos + 1); // 'png'
- 必须用
strrpos()(反向查找),不能用strpos():避免my.config.json错取成config.json - 要显式判断
$dot_pos === false:PHP 7+ 中strrpos('', '.') === false,但===比==更安全 - 这个组合不处理路径分隔符,如果输入含
/path/to/file.txt,结果是空——得先过一遍basename()
注意 $_FILES 中的 name 可能带路径(IE 旧版遗留)
虽然现代浏览器只传文件名,但老 IE 曾经传完整本地路径(如 C:UsersMePicturescat.jpg)。PHP 不会自动清理,pathinfo() 在 Windows 下仍能正确识别扩展名,但某些自定义逻辑可能出错。
- 别直接拼接
$_FILES['x']['name']到file_put_contents():路径注入风险 - 始终用
pathinfo(..., PATHINFO_EXTENSION)提取后缀,而不是靠explode('\', ...)或substr(..., -4) - 上传前建议先用
basename($_FILES['x']['name'])归一化文件名,再取后缀
真正麻烦的不是怎么取后缀,而是取完之后要不要验证 MIME 类型、要不要重命名防止覆盖、要不要限制长度——这些环节比后缀提取本身更容易出问题。











