当前位置:首页>php>PHP新手最常犯的10个错误,第7个我查了3小时

PHP新手最常犯的10个错误,第7个我查了3小时

  • 2026-10-11 08:11:46
PHP新手最常犯的10个错误,第7个我查了3小时
这篇文章总结了10个新手最容易犯的错误,建议收藏,下次遇到直接对照排查。

错误1:用字符串拼接SQL,把自己网站变成"公共厕所"

//错误写法$id = $_GET['id'];$sql = "SELECT * FROM users WHERE id = " . $id;$result = mysqli_query($conn, $sql);
为什么错: 这是SQL注入的经典入口。攻击者只需要在URL后面加个 ?id=1 OR 1=1,你的数据就全暴露了。
// 正确写法$stmt = $pdo->prepare("SELECT * FROM users WHERE id = :id");$stmt->execute(['id' => $_GET['id']]);$user = $stmt->fetch();
一句话总结: 永远不要用字符串拼接SQL,PDO预处理语句是底线。

错误2:线上环境开错误提示,把服务器配置暴露给全世界

// php.ini 里的危险配置display_errors = On
为什么错: 线上报错会直接显示文件路径、数据库结构、甚至SQL语句。攻击者看了都感动哭了,连扫描工具都省了。
//  正确配置; 线上环境display_errors = Offlog_errors = Onerror_log = /var/log/php_errors.log
一句话总结: 线上报错只写日志,不给用户看。

错误3:在函数里直接访问外部变量,然后怀疑人生

// 错误写法$name = "张三";functionsayHello() {    echo "你好," . $name;  // 报错:Undefined variable $name}sayHello();
为什么错: PHP的函数作用域是独立的,外部变量不会自动进入函数内部。
// 正确写法functionsayHello(string$name): void{    echo "你好," . $name;}sayHello($name);// 或者用匿名函数的use$greet = function() use ($name) {    echo "你好," . $name;};
一句话总结: PHP函数是"孤岛",变量传进去要么靠参数,要么靠 use。

错误4:用 @ 符号抑制错误,相当于把汽车故障灯用胶带贴上

// 错误写法$content = @file_get_contents('https://example.com/api');

为什么错:@ 只是让错误不显示,问题依然存在。API挂了、文件不存在、网络超时,你全都不知道,后面代码继续跑,产生连锁反应。

// 正确写法$content = file_get_contents('https://example.com/api');if($content === false) {    // 记录日志,返回友好提示    error_log('API请求失败');    return ['error' => '服务暂时不可用'];}

一句话总结:@ 是PHP最危险的运算符,能不用就不用。


错误5:不处理用户输入,让XSS攻击在你网站上开派对

// 错误写法echo "<div>用户留言:" . $_POST['comment'] . "</div>";

为什么错: 如果用户输入 <script>location.href='https://钓鱼网站.com'</script>,所有看这条留言的人都会被跳转。

// 正确写法echo "<div>用户留言:" . htmlspecialchars($_POST['comment'], ENT_QUOTES, 'UTF-8') . "</div>";

一句话总结: 所有输出到页面的用户输入,都要过一遍 htmlspecialchars()。


错误6:数组键名的"变形记",字符串数字键神秘消失

// 让你困惑的代码$arr = [    "1" => "字符串1",    1 => "整数1",    "01" => "字符串01"];print_r($arr);// 结果:// Array// (//     [1] => 整数1      ← "1" 被覆盖了!//     [01] => 字符串01  ← 这个保留,因为"01" != 1// )

为什么错: PHP会自动把纯数字字符串(如 "1"、"42")转换成整数作为数组键。"1" 和 1 被认为是同一个键,后者覆盖前者。

// 正确写法:如果需要保留字符串键,加前缀$arr = [    "id_1" => "字符串1",    "id_01" => "字符串01"];

一句话总结: PHP数组键遇到纯数字字符串会自动变整数,需要字符串键就加前缀。


错误7:== 和 === 的坑,strpos() 返回0被误判为"没找到"

这就是那个曾经让我查了3小时的bug。

事情是这样的。那天我在写一个敏感词过滤功能:

// 错误写法(伪代码)$content = "欢迎来到我的网站";$badWord = "欢迎";$pos = strpos($content, $badWord);if($pos == false) {    echo "内容安全,可以发布";} else {    echo "包含敏感词,位置:" . $pos;}

你猜输出什么?

内容安全,可以发布

我当场就懵了。"欢迎" 明明在开头啊!strpos 怎么会返回false?

真相是:

  • strpos("欢迎来到我的网站", "欢迎") 返回 0(表示在字符串第0个位置找到)

  • 0 == false 在PHP里是 true(因为松散比较会进行类型转换)

  • 所以代码判断为"没找到",直接放行了

我查了3小时,从编码问题怀疑到PHP源码,最后才发现是 == 和 === 的区别。

// 正确写法$pos = strpos($content, $badWord);if($pos === false) {   // 注意三个等号!    echo "内容安全,可以发布";} else {    echo "包含敏感词,位置:" . $pos;}

== 和 === 的核心区别:

表达式
结果
原因
0 == false
true
松散比较,0转bool是false
0 === false
false
严格比较,类型不同直接false
'0' == false
true
字符串'0'转bool是false
'0' === false
false
类型不同
'abc' == 0
true
字符串转整数,'abc'变成0
'1abc' == 1
true
字符串转整数,'1abc'变成1
//  另一个经典场景:接口返回值判断$response = file_get_contents('https://api.example.com/data'); // 返回 "0"if ($response == false) {    // 会进这里!因为 "0" == false 是 true    echo "请求失败";}if ($response === false) {    // 只有真正的失败(返回false)才会进这里    echo "请求失败";} else {    echo "返回内容是:" . $response; // 输出 0}

一句话总结:PHP里判断返回值,尤其是 strpos()、file_get_contents()、stripos() 这些,永远用 ===,否则0和false会让你怀疑人生。


错误8:用 == 比较浮点数,金额计算差一分钱

// 错误写法$a = 0.1;$b = 0.7;$sum = $a + $b;if($sum == 0.8) {    echo "相等";} else {    echo "不相等";  // 会输出这个!}

为什么错: 计算机用二进制存储浮点数,0.1 + 0.7 实际是 0.7999999999999999。

// 正确写法if(abs($sum - 0.8) < 0.00001) {    echo "相等";}// 金额计算用整数(分)$price = 1000; // 10.00元$discount = 150; // 1.50元$final = ($price - $discount) / 100; // 8.50元

一句话总结: 金额别用浮点数,用整数存"分";必须比较浮点数时,用 abs($a - $b) < 0.00001。


错误9:在循环里查数据库,服务器被拖垮的N+1问题

// 错误写法$users = $db->query("SELECT * FROM users LIMIT 100");foreach($users as $user) {    // 每次循环都查一次数据库!101次查询    $orders = $db->query("SELECT * FROM orders WHERE user_id = " . $user['id']);    $user['orders'] = $orders;}

为什么错: 100个用户就是101次查询。用户量一上来,数据库直接挂掉。

// 正确写法:用IN查询一次取出$userIds = array_column($users, 'id');$placeholders = implode(',', array_fill(0, count($userIds), '?'));$stmt = $pdo->prepare("SELECT * FROM orders WHERE user_id IN ($placeholders)");$stmt->execute($userIds);$allOrders = $stmt->fetchAll();// 用PHP分组$ordersMap = [];foreach($allOrders as $order) {    $ordersMap[$order['user_id']][] = $order;}foreach($users as &$user) {    $user['orders'] = $ordersMap[$user['id']] ?? [];}

一句话总结: 循环里别查数据库,能用一次查询解决的,绝不用两次。


错误10:还在用 mysql_* 函数

// 错误写法(已废弃,PHP7+直接报错)$conn = mysql_connect('localhost', 'root', '123456');mysql_select_db('test', $conn);$result = mysql_query('SELECT * FROM users');

为什么错:mysql_* 系列函数在PHP 5.5被废弃,PHP 7.0直接移除。还在用的代码一升级PHP版本就全崩。

//  正确写法// PDO(推荐)$pdo = new PDO('mysql:host=localhost;dbname=test', 'root', '123456');// 或者 mysqli$conn = new mysqli('localhost', 'root', '123456', 'test');

写在最后

虽然现在都是直接各种框架但是基本的常识还是必须要明白掌握的

编程这件事,踩坑不可怕,可怕的是同一个坑踩两次。

最新文章

随机文章