整理常見 PHP 開發場景的最佳實踐代碼片段,涵蓋驗證、Session/快取控制、檔案下載、FTP 傳輸等。
<?php
function isValidIp($ip) {
// IPv4
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) return true;
// IPv6
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) return true;
return false;
}
// 或單行 (僅 IPv4)
if (!strcmp(long2ip(sprintf("%u", ip2long($ip))), $ip)) echo "is ip\n";
<?php
function isValidEmail($email) {
return (bool)filter_var($email, FILTER_VALIDATE_EMAIL);
}
// 正則版 (較嚴格)
$regex = '/^[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+$/';
if (preg_match($regex, $email)) { /* valid */ }
<?php
$ip = '192.168.0.84';
$mask = '255.255.255.0';
$network = '192.168.0.0'; // 網路位址
$ipLong = ip2long($ip);
$maskLong = ip2long($mask);
$netLong = ip2long($network);
if (($ipLong & $maskLong) === $netLong) {
echo "valid ip and mask\n";
}
// CIDR 寫法更簡潔
// 192.168.0.84/24 -> ip2long('192.168.0.84') & ~((1 << (32-24))-1) === ip2long('192.168.0.0')
問題:使用 session_start() 會發送 Cache-Control: no-cache,導致瀏覽器不快取頁面,回退時重新請求伺服器,若伺服器未保留 POST 資料則表單清空。
解決方案 (三選一):
<?php
// 方案 1: 允許私有快取 (推薦)
session_cache_limiter('private'); // 必須在 session_start() 前
session_start();
// 方案 2: 手動發送 Header (在 session_start 後)
session_start();
header("Cache-Control: private, max-age=10800"); // 3 小時
// 方案 3: 完全不快取 (敏感頁面)
session_cache_limiter('nocache');
session_start();
private:瀏覽器可快取,但 Proxy 不快取;回退時直接讀取快取,表單資料保留。public:Proxy 也可快取 (不建議含敏感資訊頁面)。nocache:完全不快取 (預設)。<?php
function downloadFile($filePath, $downloadName = null) {
if (!is_file($filePath) || !is_readable($filePath)) {
http_response_code(404);
exit('File not found');
}
$downloadName = $downloadName ?? basename($filePath);
$size = filesize($filePath);
// 清除輸出緩衝
if (ob_get_level()) ob_end_clean();
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . rawurlencode($downloadName) . '"');
header('Content-Transfer-Encoding: binary');
header('Accept-Ranges: bytes');
header('Content-Length: ' . $size);
header('Cache-Control: private, must-revalidate'); // 允許瀏覽器快取下載檔
header('Pragma: public'); // HTTP/1.0 相容
// 分塊輸出避免記憶體耗盡
$handle = fopen($filePath, 'rb');
while (!feof($handle)) {
echo fread($handle, 8192);
flush(); // 送出給 Web Server
}
fclose($handle);
exit;
}
<?php
function ftpDownloadRange($ftpConn, $remoteFile, $localFile = null) {
$localFile = $localFile ?? basename($remoteFile);
$fileSize = ftp_size($ftpConn, $remoteFile);
if ($fileSize === -1) die("Remote file not found");
$rangeStart = 0;
$rangeEnd = $fileSize - 1;
if (isset($_SERVER['HTTP_RANGE'])) {
// 解析 Range: bytes=START-END
preg_match('/bytes=(\d+)-(\d*)/', $_SERVER['HTTP_RANGE'], $matches);
$rangeStart = (int)$matches[1];
$rangeEnd = $matches[2] !== '' ? (int)$matches[2] : $fileSize - 1;
if ($rangeStart >= $fileSize || $rangeEnd >= $fileSize) {
header('HTTP/1.1 416 Range Not Satisfiable');
header('Content-Range: bytes */' . $fileSize);
exit;
}
http_response_code(206); // Partial Content
} else {
http_response_code(200);
}
$contentLength = $rangeEnd - $rangeStart + 1;
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . rawurlencode($localFile) . '"');
header('Accept-Ranges: bytes');
header('Content-Length: ' . $contentLength);
header('Content-Range: bytes ' . $rangeStart . '-' . $rangeEnd . '/' . $fileSize);
header('Cache-Control: private');
header('Pragma: public');
// FTP 不支援隨機讀取,需下載整檔再切片 (效能差)
// 生產環境建議用 HTTP 伺服器 (Nginx/X-Sendfile) 或雲存儲簽名 URL
$tmp = tmpfile();
ftp_fget($ftpConn, $tmp, $remoteFile, FTP_BINARY);
fseek($tmp, $rangeStart);
while (!feof($tmp) && $contentLength > 0) {
$read = min(8192, $contentLength);
echo fread($tmp, $read);
$contentLength -= $read;
flush();
}
fclose($tmp);
}
注意:FTP 協定無原生 Range 支援,上述做法需先下載整檔至暫存再切片,**效能極差**。實務上大檔案下載請直接使用 HTTP 伺服器 (Nginx sendfile/aio)、雲存儲 (S3 簽名 URL) 或 CDN。
function isAjax() {
return isset($_SERVER['HTTP_X_REQUESTED_WITH']) &&
strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest';
}