俗话说:“PHP 是世界上最好的语言。”
这话虽然是个梗,但也侧面说明 PHP 的入门门槛低、开发速度快。不过,门槛低不代表没坑,甚至有时候因为它的“灵活”和“弱类型”,反而能踩出一些让你怀疑人生的 Bug。
做了这么多年 PHP 后端,从 ThinkPHP 到 Laravel,从 PHP 5.6 蹭到 PHP 8.0,我总结了一些出现频率最高、最搞心态的问题。这篇手册建议各位 PHPer 收藏一下,下次遇到报错别慌,翻翻这里,说不定能帮你省下几个小时的排查时间。

1. “Headers already sent” —— 经典的“头已发送”
这绝对是 PHP 新手(甚至老手)最容易遇到的“下马威”。当你试图用 header('Location: xxx') 进行页面跳转,或者设置 Cookie 时,页面突然白屏或者报错:
*Warning: Cannot modify header information – headers already sent by…*
原因剖析:
HTTP 协议规定,头部信息必须在内容体之前发送。只要你在这个 header() 之前输出了哪怕一个空格、一个回车,或者 BOM 头,服务器就会先发送内容体,这时候你再想改头,就不可能了。
避坑指南:
- 检查 BOM 头: 很多用 Windows 记事本编辑过的文件,会在文件头偷偷加上 BOM,肉眼看不见,但 PHP 能读到。用编辑器(如 VS Code 或 Sublime)把文件编码改为
UTF-8 without BOM。 - 检查
?>结束标签: 这是一个好习惯:在纯 PHP 文件中,尽量不要写文件结束的?>标签。写了之后,文件末尾多敲个空格你就惨了。 - 开启输出缓冲: 在
php.ini里开启output_buffering = On,或者在代码最开头加ob_start(),这样能把内容先存在内存里,等程序全跑完了再一次性发出去。
2. Undefined array key / variable —— 也就是曾经的 Notice
以前 PHP 5 时代,这种 Notice 很多人直接选择无视,或者用那个万恶的 @ 符号把错误压下去。但到了 PHP 7 和 PHP 8,很多警告直接升级为异常,程序直接就崩了。
比如你直接 $name =$_GET['name'];,如果 URL 里没传 name 参数,在 PHP 8 里就会报错。
正确姿势:
别再懒了,请务必使用空合并运算符 ??:
$name =$_GET['name'] ?? 'default_name';
如果你还要判断存不存在,用 isset() 或者 array_key_exists()。千万别滥用 @,它会极大地降低性能,而且掩盖了真正的逻辑错误。
3. Allowed memory size exhausted —— 内存不够用
当你导出几万行 Excel,或者处理大图片时,这个报错简直太熟悉了:
*Fatal error: Allowed memory size of 134217728 bytes exhausted…*
原因剖析:
默认的 PHP 内存限制通常只有 128M 或者 256M。当你一次性把数据库里的几万条数据 fetchAll() 到内存数组里,或者用 GD 库处理高清大图,内存瞬间爆炸。
怎么破?
- 下策(临时救急): 在代码里加
ini_set('memory_limit', '512M');。但这只是掩耳盗铃,数据量再大点还是得炸。 - 上策(根本解决):
- 读数据用生成器(Generator)或者游标,不要一次性
loadAll。 - 导出 Excel 不要用那种一次性生成全文件的库,尝试用分批写入或者流式写入的方式。
- 图片处理能用 Imagick 就别用 GD,前者处理大图内存控制得好一点。
- 读数据用生成器(Generator)或者游标,不要一次性
4. 时区不对,时间差了 8 个小时
上线后发现,用户评论的时间要么是明天的,要么是昨天的,跟数据库实际存储对不上。
通常是因为 php.ini 里默认时区没配,或者服务器时区是 UTC。
解决方法:
不要依赖服务器环境。在你的入口文件(如 public/index.php)或者公共配置文件里,强制指定时区:
date_default_timezone_set('Asia/Shanghai');
如果你在用 Laravel 或 ThinkPHP 等框架,它们通常在 config/app.php 里也有配置项,记得去那里改,别改错地方了。
5. Laravel/框架中:配置缓存没清(Cache Hell)
这个 Bug 不限于 PHP,但在 Laravel 开发中特别常见。
你明明改了 .env 文件里的数据库配置,或者改了 config/app.php,结果跑起来一点反应没有,甚至还连着旧的数据库。
原因:
为了性能,很多框架会把配置文件缓存成 PHP 数组。你改了源文件,但程序读的还是旧的缓存文件。
急救命令:
如果你怀疑是缓存的问题,跑一下这几条命令,通常能解决 90% 的“玄学”问题:
php artisan cache:clear
php artisan config:clear
php artisan route:clear
php artisan view:clear
特别是刚上完线,忘了跑 config:clear 导致配置没生效,这种坑我踩过不止一次。
写在最后
PHP 虽然灵活,但正因为太灵活,如果不规范写法,维护起来简直就是灾难。
遇到 Bug 别慌,先看报错日志,再对照上面的情况排查。大部分时候,问题都出在“以为它应该这样,结果它实际上那样”的细节上。
