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

PHP 初學者常見 14 大坑與解決方案:從變數傳遞到資料庫操作

     編輯:PHP 相關知識

前言

PHP 門檻低、上手快,但「能跑」不代表「正確」。本文整理初學者最常踩的 14 個坑,附上原因分析與最佳實踐解法,助你避開新手村陷阱。

坑 1:超全域變數未開啟,變數「消失」

現象

<?php
// form.html → process.php
echo $username; // Notice: Undefined variable
echo $_POST['username']; // 正常

原因

PHP 5.4+ 預設 register_globals = Off,不再自動將 `$_GET`、`$_POST`、`$_SESSION` 註冊為全域變數。

解法

  • 永遠使用 $_GET、$_POST、$_REQUEST、$_SESSION、$_COOKIE、$_SERVER、$_FILES、$_ENV
  • 封裝輸入處理類別:Input::get('username')

坑 2:Windows 下 GET 中文參數亂碼

現象

// URL: test.php?name=中文
echo $_GET['name']; // 亂碼或空白

原因

瀏覽器編碼(UTF-8)與伺服器解碼(系統預設 ANSI/GBK)不一致。

解法

// 前端編碼
連結

// 後端解碼(若必要)
$name = urldecode($_GET['name']);

// 根本解決:統一 UTF-8
// 1. HTML: 
// 2. PHP: header('Content-Type: text/html; charset=UTF-8');
// 3. 資料庫: CHARSET utf8mb4
// 4. Apache: AddDefaultCharset UTF-8

坑 3:Windows 下 Session 無法寫入

現象

session_start();
$_SESSION['user'] = 'john';
// 重新整理 → $_SESSION 為空

原因

php.ini 預設 session.save_path = "/tmp"(Linux 路徑),Windows 無此目錄。

解法

; php.ini
session.save_path = "C:/Windows/Temp"  ; 或自訂可寫目錄

; 程式碼中動態設定(需在 session_start() 前)
ini_set('session.save_path', __DIR__ . '/sessions');

坑 4:錯誤顯示設定導致資安風險

現象

生產環境直接顯示堆疊追蹤、資料庫憑證、路徑資訊。

解法

; php.ini 生產環境
display_errors = Off
display_startup_errors = Off
log_errors = On
error_log = /var/log/php_errors.log
error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT

; 開發環境
display_errors = On
error_reporting = E_ALL

程式碼中:error_reporting(E_ALL); ini_set('display_errors', '1');

坑 5:header() 已送出標頭錯誤

現象

Warning: Cannot modify header information - headers already sent by...

原因

  • 輸出內容(echo、HTML、空白行)在 header()/session_start()/setcookie() 之前
  • 檔案開頭有 BOM (Byte Order Mark)
  • include 檔案尾端有關閉標籤 ?> 後的空白行

解法

  • PHP 檔案省略結尾 ?>
  • 使用輸出緩衝:ob_start() 在腳本最開頭
  • 檢查編輯器設定:儲存為「UTF-8 無 BOM」

坑 6:修改 php.ini 不生效

解法

  • 重啟 Web 伺服器:systemctl restart php-fpm / apachectl restart
  • 確認正確的 php.ini:php --ini 或 phpinfo() 查看 Loaded Configuration File
  • OpCache 啟用時需重啟或 opcache_reset()

坑 7:SQL 語句失效卻無錯誤訊息

錯誤寫法

$sql = "INSERT INTO users (name) VALUES ('$name')";
mysqli_query($conn, $sql); // 失敗卻不報錯

正確寫法

$sql = "INSERT INTO users (name) VALUES (?)";
$stmt = mysqli_prepare($conn, $sql);
mysqli_stmt_bind_param($stmt, 's', $name);
if (!mysqli_stmt_execute($stmt)) {
    error_log('SQL Error: ' . mysqli_stmt_error($stmt));
    // 處理失敗
}

調試技巧

// 開發環境直接顯示
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);

// 或 PDO
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

坑 8:include vs require 差異誤解

最佳實踐:現代 PHP 使用 require_once 或 Composer 自動載入,避免重複載入與路徑混亂。

坑 9:isset() vs empty() 語義混淆

特性includerequire
檔案不存在E_WARNING,繼續執行E_COMPILE_ERROR,腳本終止
回傳值成功回傳 1,可被 include 檔案覆蓋同
建議用途選用性檔案(樣板、廣告)核心依賴(設定、函數庫、類別)

實用判斷模式

// 判斷「有值且非空白字串」
if (isset($var) && $var !== '') { ... }

// 判斷「陣列鍵存在且非空」
if (isset($arr['key']) && $arr['key'] !== '') { ... }

// 刪除變數
unset($var); // 真正移除
$var = null; // 僅設為 null,isset 仍為 false

坑 10:MySQL 保留字當作欄位/表名

現象

// order 是保留字
$sql = "SELECT * FROM order WHERE id = 1"; // Syntax error

解法

// 反引號包裹(MySQL 專用)
$sql = "SELECT * FROM `order` WHERE `id` = 1";

// 或 PDO 參數化(不需處理識別碼)
$stmt = $pdo->prepare("SELECT * FROM `order` WHERE `id` = ?");

保留字清單:order, group, select, insert, update, delete, where, key, index, ...

坑 11:多檔案上傳 $_FILES 結構誤解

單檔 vs 多檔結構差異

<!-- 單檔 -->
<input type="file" name="avatar">
// $_FILES['avatar'] = ['name'=>..., 'type'=>..., 'tmp_name'=>..., 'error'=>..., 'size'=>...]

<!-- 多檔(陣列命名) -->
<input type="file" name="photos[]">
<input type="file" name="photos[]">
// $_FILES['photos'] = [
//     'name' => ['photo1.jpg', 'photo2.jpg'],
//     'type' => ['image/jpeg', 'image/jpeg'],
//     'tmp_name' => ['/tmp/phpX1', '/tmp/phpX2'],
//     'error' => [0, 0],
//     'size' => [1024, 2048]
// ]

正確遍歷多檔

$files = $_FILES['photos'];
$count = count($files['name']);
for ($i = 0; $i < $count; $i++) {
    if ($files['error'][$i] === UPLOAD_ERR_OK) {
        $tmpName = $files['tmp_name'][$i];
        $name = $files['name'][$i];
        // 處理...
    }
}

坑 12:檔案讀寫未加鎖導致資料損毀

危險寫法

file_put_contents('data.txt', $data); // 併發下資料遺失

安全寫法

function safeWrite($file, $data, $mode = 'w') {
    $fp = fopen($file, $mode);
    if (flock($fp, LOCK_EX)) { // 獨佔鎖
        fwrite($fp, $data);
        fflush($fp);
        flock($fp, LOCK_UN);
    }
    fclose($fp);
}

function safeRead($file) {
    $fp = fopen($file, 'r');
    if (flock($fp, LOCK_SH)) { // 共享鎖
        $data = stream_get_contents($fp);
        flock($fp, LOCK_UN);
    }
    fclose($fp);
    return $data;
}

坑 13:浮點數運算精度陷阱

echo 0.1 + 0.2; // 0.30000000000000004
echo (0.1 + 0.2) === 0.3; // false

// 正確:BCMath 或整數運算
echo bcadd('0.1', '0.2', 1); // 0.3
// 金額用「分」作單位
$price = 1999; // 19.99 元

坑 14:迴圈中資料庫查詢(N+1 問題)

錯誤

$users = User::all();
foreach ($users as $user) {
    $posts = Post::where('user_id', $user->id)->get(); // N 次查詢
}

正確

$users = User::with('posts')->get(); // 2 次查詢(Eager Loading)
// 或手動
$userIds = $users->pluck('id');
$posts = Post::whereIn('user_id', $userIds)->get()->groupBy('user_id');
foreach ($users as $user) {
    $userPosts = $posts[$user->id] ?? [];
}

總結:新手生存清單

  1. 永遠使用超全域陣列存取輸入
  2. 全專案統一 UTF-8(檔案、資料庫、HTML、HTTP 標頭)
  3. Session 路徑指向可寫目錄
  4. 生產環境關閉錯誤顯示、開啟錯誤日誌
  5. 輸出前無任何內容(含空白、BOM)
  6. 修改設定後重啟服務
  7. 資料庫操作:預處理語句 + 例外模式
  8. 核心檔案用 require_once、採用自動載入
  9. 分清 isset/empty,精確判斷變數狀態
  10. 識別碼用反引號、避開保留字
  11. 正確處理 $_FILES 多檔結構
  12. 併發寫入加檔案鎖
  13. 金額用整數或 BCMath
  14. 避免迴圈查詢,善用 Eager Loading / JOIN

PHP 的寬容性是雙面刃:讓新手快速見到成果,也讓錯誤隱藏在「能跑」的假象中。養成「嚴格模式」思維——開啟所有警告、使用靜態分析、寫測試、Code Review——才是從新手進階的關鍵。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
變數狀態isset()empty()
未定義falsetrue
nullfalsetrue
'' (空字串)truetrue
0 (整數)truetrue
'0' (字串)truetrue
[] (空陣列)truetrue
falsetruetrue