在日常 PHP 開發中,反覆造輪子既低效又易出錯。本文整理了 7 個高頻場景的通用函數,涵蓋隨機字符串、中文截取、IP 獲取、目錄創建、郵箱驗證、跳轉提示、分頁組件,代碼經過現代化重構,可直接納入項目公共庫。
function random_string(int $length = 32): string {
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz';
$bytes = random_bytes($length);
$result = '';
for ($i = 0; $i < $length; $i++) {
$result .= $chars[$bytes[$i] % strlen($chars)];
}
return $result;
}
// 使用:random_string(16) → "aB3f9K..."
改進點: 棄用 `mt_rand`,改用 `random_bytes`(CSPRNG),安全性更高;移除 `mt_srand` 種子干擾。
function mb_cut_string(string $str, int $length, string $suffix = '...'): string {
if (mb_strlen($str) <= $length) return $str;
return mb_substr($str, 0, $length) . $suffix;
}
// 使用:mb_cut_string("你好世界", 2) → "你好..."
說明: 原代碼僅支援 GB2312 且邏輯脆弱。現代項目統一 UTF-8,直接用 `mb_substr` 最可靠。
function get_client_ip(): string {
$headers = [
'HTTP_CLIENT_IP',
'HTTP_X_FORWARDED_FOR',
'HTTP_X_FORWARDED',
'HTTP_X_CLUSTER_CLIENT_IP',
'HTTP_FORWARDED_FOR',
'HTTP_FORWARDED',
'REMOTE_ADDR'
];
foreach ($headers as $header) {
if (!empty($_SERVER[$header])) {
$ips = explode(',', $_SERVER[$header]);
foreach ($ips as $ip) {
$ip = trim($ip);
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';
}
增強: 遍歷多代理頭,過濾私有/保留地址,返回首個公網 IP。
function ensure_dir(string $path, int $mode = 0755): bool {
if (is_dir($path)) return true;
$parent = dirname($path);
if ($parent !== '.' && $parent !== '/') {
ensure_dir($parent, $mode);
}
return @mkdir($path, $mode, true);
}
優化: 使用遞歸 + `mkdir($path, $mode, true)` 原生遞歸參數,代碼極簡。
function is_valid_email(string $email): bool {
return (bool)filter_var($email, FILTER_VALIDATE_EMAIL);
}
// 內部使用正則符合 RFC 5322,比手寫正則可靠得多
function redirect_with_msg(string $msg, string $url = '', int $wait = 3, string $title = '系統提示'): never {
// 若為 AJAX 請求返回 JSON
if (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest') {
header('Content-Type: application/json');
exit(json_encode(['msg' => $msg, 'url' => $url, 'wait' => $wait]));
}
// 普通頁面輸出 HTML
$html = <<<HTML
<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>$title</title>
<meta http-equiv="refresh" content="$wait;url=$url">
<style>body{font-family:system-ui;padding:2rem;text-align:center;} .box{max-width:400px;margin:auto;border:1px solid #ddd;border-radius:8px;padding:2rem;box-shadow:0 2px 8px rgba(0,0,0,.1);} .btn{display:inline-block;margin-top:1rem;padding:.5rem 1.5rem;background:#007bff;color:#fff;border-radius:4px;text-decoration:none;}</style>
</head><body><div class="box"><h3>$title</h3><p>$msg</p>
HTML;
if ($url) {
$html .= "<a class=\"btn\" href=\"$url\">立即跳轉</a><p>{$wait}秒後自動跳轉...</p>";
} else {
$html .= "<a class=\"btn\" href=\"javascript:history.back()\">返回上一頁</a>";
}
$html .= "</div></body></html>";
exit($html);
}
function paginate(PDO $pdo, string $baseSql, int $page = 1, int $perPage = 20, array $params = []): array {
// 1. 總數查詢
$countSql = "SELECT COUNT(*) FROM ({$baseSql}) AS t";
$stmt = $pdo->prepare($countSql);
$stmt->execute($params);
$total = (int)$stmt->fetchColumn();
// 2. 計算分頁
$totalPages = max(1, (int)ceil($total / $perPage));
$page = max(1, min($page, $totalPages));
$offset = ($page - 1) * $perPage;
// 3. 數據查詢
$dataSql = "{$baseSql} LIMIT {$perPage} OFFSET {$offset}";
$stmt = $pdo->prepare($dataSql);
$stmt->execute($params);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
// 4. 生成鏈接
$links = [];
for ($i = max(1, $page - 2); $i <= min($totalPages, $page + 2); $i++) {
$links[$i] = $i === $page ? "[$i]" : "$i";
}
return [
'data' => $rows,
'pagination' => [
'current' => $page,
'total' => $total,
'per_page' => $perPage,
'last_page' => $totalPages,
'links' => $links,
]
];
}
// 使用:
// $result = paginate($pdo, "SELECT * FROM articles WHERE status=?", 1, 10, [1]);