最可靠方式是用 finfo_file() 获取 mime 类型后比对二进制类型列表;fallback 方案为读取前 256 字节检测 \0;禁用已废弃的 mime_content_type()。

用 finfo_file 判断文件是否为二进制最可靠
PHP 没有内置的「是/否二进制」布尔函数,直接读取前几百字节查 \0 也不够准——比如 UTF-16 文本里就有大量 \0,但它是文本。真正靠谱的方式是交由系统 MIME 类型检测引擎,即 finfo_open(FILEINFO_MIME_TYPE) 配合预设的二进制类型列表。
实操建议:
- 优先用
finfo_file()获取 MIME 类型,再比对常见二进制类型(如application/octet-stream、application/pdf、image/jpeg) - 避免只依赖
text/plain作为「非二进制」判据——有些纯文本文件可能被识别为text/x-shellscript或text/markdown - 若
finfo扩展未启用,php -m | grep fileinfo可确认;Docker 环境常需手动安装libfileinfo-dev并重编译
快速 fallback:检查文件头是否含空字节
当无法使用 finfo,且只需粗略区分(例如上传拦截明显二进制),可读取前 256 字节扫描 \0。这不是类型判断,而是「大概率含二进制内容」的经验规则。
注意点:
立即学习“PHP免费学习笔记(深入)”;
- 用
fopen($path, 'rb')打开,否则 Windows 下可能触发换行转换,误伤\r\n - 别用
file_get_contents()读大文件,内存爆掉;改用fread($fp, 256)+fclose() -
strpos($bytes, "\0") !== false足够,无需正则;但要注意\0在字符串中间就终止strlen(),所以必须用mb_strlen($bytes, '8bit')或直接strpos()
为什么 mime_content_type() 不推荐
mime_content_type() 是 finfo 的旧封装,已自 PHP 7.2 起标记为 deprecated,且行为更不稳定:它不支持自定义 magic 数据库路径,某些 Alpine Linux 镜像中甚至返回空字符串或 text/plain 假阳性。
明确避开:
- 不要在新项目里写
mime_content_type($path) - 如果遗留代码还在用,升级时务必替换为
finfo_open() → finfo_file()流程 - 它的返回值不区分
application/x-executable和application/x-sharedlib,而finfo可以(取决于系统 magic 库版本)
真实场景中的边界情况
所谓「二进制文件」本身没有绝对定义,关键看你的业务目标:是防恶意可执行上传?还是跳过日志分析?或是决定用 file_get_contents 还是 gzopen?不同目的,阈值不同。
几个容易忽略的点:
- ZIP 文件开头是
PK\003\004,但解压后全是文本 —— 它仍是二进制容器,finfo返回application/zip - PHP 文件带 BOM(
\xEF\xBB\xBF)会被某些工具误判为二进制,但实际是合法文本;此时finfo仍返回text/x-php - 某些嵌入式固件镜像无扩展名、无 magic 签名,
finfo返回application/octet-stream—— 这时候你得结合文件大小、来源路径等额外字段做二次判断











