PHP 開發中有許多「知道會用、不懂原理」的常用片段。本文精選 8 大高頻場景,提供現代寫法 (PHP 7.4+)、安全性分析、效能考量與最佳實踐,替換舊式 $_POST['key']、mysql_*、$_SERVER['REMOTE_ADDR'] 等過時用法。
// 變數/屬性/方法:camelCase
$userName = 'Alice';
$orderItems = [];
function getUserName() {}
// 類別/介面/Trait:StudlyCase
class UserController {}
interface LoggerInterface {}
trait Loggable {}
// 常數/Enum:SCREAMING_SNAKE_CASE
const MAX_RETRY = 3;
enum Status: string { case PENDING = 'pending'; }
// 命名空間:小寫、單數、反映業務域
namespace App\Http\Controllers\Api\V1;
namespace App\Models;
namespace App\Services\Payment;
<form method="post">
<input type="checkbox" name="ids[]" value="1"> 項目 1
<input type="checkbox" name="ids[]" value="2"> 項目 2
<button type="submit" name="action" value="delete">刪除</button>
</form>
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$ids = $_POST['ids'] ?? [];
// 驗證:全為整數
$ids = array_filter($ids, 'is_numeric');
$ids = array_map('intval', $ids);
if ($ids) {
$placeholders = implode(',', array_fill(0, count($ids), '?'));
$sql = "DELETE FROM items WHERE id IN ($placeholders)";
$stmt = $pdo->prepare($sql);
$stmt->execute($ids); // 參數化,完全防注入
}
}
// 方式一:檢查 REQUEST_METHOD (最通用)
if ($_SERVER['REQUEST_METHOD'] === 'POST') { ... }
// 方式二:檢查特定按鈕 name (多按鈕表單)
if (isset($_POST['action'])) {
match ($_POST['action']) {
'save' => saveData(),
'delete' => deleteData(),
'preview' => previewData(),
};
}
// 方式三:隱藏欄位標識 (避免按鈕 name 衝突)
<input type="hidden" name="form_submitted" value="user_profile">
if (($_POST['form_submitted'] ?? '') === 'user_profile') { ... }
// ❌ 不推薦:isset($HTTP_POST_VARS['Button1']) (已移除)
$str = '中文 English 123';
// 字元數 (視覺長度)
$len = mb_strlen($str, 'UTF-8'); // 17
// 位元組數 (儲存長度)
$bytes = strlen($str); // 23
// ❌ 舊式 strlen($str) 直接用於中文會得到位元組數,導致截斷錯誤
// 截取時務必用 mb_substr
$short = mb_substr($str, 0, 10, 'UTF-8');
// 內部重導向
$url = '/dashboard';
header("Location: $url", true, 302);
exit;
// 絕對 URL (防開放重導向攻擊)
$allowedHosts = ['example.com', 'api.example.com'];
$target = $_POST['redirect'] ?? '/';
$parsed = parse_url($target);
if ($parsed && isset($parsed['host']) && in_array($parsed['host'], $allowedHosts)) {
header("Location: $target", true, 302);
} else {
header("Location: /", true, 302);
}
exit;
// ❌ 舊式 Header("Location: " . $_SERVER['HTTP_REFERER']) 易遭 Referer 注入攻擊
| 舊式 (不推薦) | 現代 (推薦) | 說明 |
|---|---|---|
$HTTP_POST_VARS['key'] | $_POST['key'] | PHP 4.1+ 引入超全域 |
$HTTP_GET_VARS['key'] | $_GET['key'] | |
$HTTP_SERVER_VARS['REMOTE_ADDR'] | $_SERVER['REMOTE_ADDR'] | |
global $var; $GLOBALS['var'] | 依賴注入 / 單例 / 靜態屬性 | 避免全域狀態汙染 |
$_REQUEST | 明確用 $_GET / $_POST | $_REQUEST 合併順序不固定,安全風險 |
function getClientIp(): string
{
$headers = [
'HTTP_CF_CONNECTING_IP', // Cloudflare
'HTTP_X_FORWARDED_FOR', // 標準代理
'HTTP_X_REAL_IP', // Nginx proxy_set_header
'HTTP_CLIENT_IP', // 部分代理
'REMOTE_ADDR', // 直接連線
];
foreach ($headers as $header) {
if (!empty($_SERVER[$header])) {
// X-Forwarded-For 可能包含多個 IP,取第一個
$ips = explode(',', $_SERVER[$header]);
$ip = trim($ips[0]);
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
return $ip;
}
}
}
return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
}
// ⚠️ 僅在「信任代理」情況下使用 HTTP_X_FORWARDED_FOR
// Nginx 需設定:proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
// PHP 信任代理 IP 白名單機制 (如 Symfony Request::setTrustedProxies)
// 當前時間 (不可變物件,執行緒安全)
$now = new DateTimeImmutable('now', new DateTimeZone('Asia/Taipei'));
// 格式化輸出
echo $now->format('Y-m-d H:i:s'); // 2024-01-15 14:30:00
echo $now->format('Y年m月d日 H:i:s'); // 2024年01月15日 14:30:00
echo $now->format('c'); // ISO 8601: 2024-01-15T14:30:00+08:00
echo $now->format('r'); // RFC 2822: Mon, 15 Jan 2024 14:30:00 +0800
// 時間運算
$tomorrow = $now->modify('+1 day');
$nextWeek = $now->add(new DateInterval('P1W'));
$timestamp = $now->getTimestamp(); // Unix timestamp
// ❌ 舊式 date('Y-m-d') 依賴全域時區設定,易出錯
// ✅ 推薦:統一使用 DateTimeImmutable + 明確時區
filter_var, assert, 表單請求類別。htmlspecialchars、json_encode。DateTimeImmutable + 明確時區。$_REQUEST、global、$HTTP_*_VARS。mb_* 函式。這 8 大技巧看似基礎,卻是資安漏洞、編碼亂碼、時區錯誤、重導向攻擊的高發區。建立團隊編碼規範、引入靜態分析 (PHPStan/Psalm)、使用現代框架內建機制,能從根本上消除這類低級錯誤。