函數簡介
filter_var($ip, FILTER_VALIDATE_IP, $flags) 是 PHP 內建的 IP 地址驗證函數,支援 IPv4/IPv6、標誌位過濾私有/保留地址,比正則表達式更可靠、效能更好。
基本用法
<?php
$ip = "192.168.0.1";
if (filter_var($ip, FILTER_VALIDATE_IP)) {
echo "IP 格式合法";
} else {
echo "IP 格式不合法";
}
// 輸出: IP 格式合法
旗標參數詳細表
| 常數 | 值 | 作用 |
FILTER_FLAG_IPV4 | 1048576 | 僅允許 IPv4 格式 |
FILTER_FLAG_IPV6 | 1048577 | 僅允許 IPv6 格式 |
FILTER_FLAG_NO_PRIV_RANGE | 524288 | 拒絕私有地址(RFC 1918) |
FILTER_FLAG_NO_RES_RANGE | 262144 | 拒絕保留地址(RFC 5735/5156) |
私有/保留地址範圍對照
| 類別 | 範圍 | 旗標攔截 |
| IPv4 私有 (RFC 1918) | 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 | NO_PRIV_RANGE |
| IPv4 回環 | 127.0.0.0/8 | NO_RES_RANGE |
| IPv4 Link-local | 169.254.0.0/16 | NO_RES_RANGE |
| IPv4 組播 | 224.0.0.0/4 | NO_RES_RANGE |
| IPv4 廣播/保留 | 240.0.0.0/4, 255.255.255.255 | NO_RES_RANGE |
| IPv6 回環 | ::1/128 | NO_RES_RANGE |
| IPv6 Link-local | fe80::/10 | NO_RES_RANGE |
| IPv6 唯一本地 | fc00::/7 | NO_PRIV_RANGE |
| IPv6 組播 | ff00::/8 | NO_RES_RANGE |
| IPv6 文檔前綴 | 2001:db8::/32 | NO_RES_RANGE |
實戰範例
1. 僅允許公網 IPv4
<?php
function isPublicIPv4($ip) {
return filter_var($ip, FILTER_VALIDATE_IP,
FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE
) !== false;
}
var_dump(isPublicIPv4("8.8.8.8")); // true (Google DNS)
var_dump(isPublicIPv4("192.168.1.1")); // false (私有)
var_dump(isPublicIPv4("127.0.0.1")); // false (回環)
var_dump(isPublicIPv4("2001:4860:4860::8888")); // false (IPv6)
2. 允許公網 IPv4 + IPv6
<?php
function isPublicIP($ip) {
return filter_var($ip, FILTER_VALIDATE_IP,
FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE
) !== false;
}
var_dump(isPublicIP("2001:4860:4860::8888")); // true (Google IPv6 DNS)
var_dump(isPublicIP("::1")); // false (IPv6 回環)
var_dump(isPublicIP("fe80::1")); // false (Link-local)
3. 表單驗證整合
<?php
function validateIpInput($input) {
$ip = trim($input);
// 基礎格式驗證
if (!filter_var($ip, FILTER_VALIDATE_IP)) {
return ['valid' => false, 'error' => 'IP 格式錯誤'];
}
// 業務邏輯:拒絕私有/保留
if (!filter_var($ip, FILTER_VALIDATE_IP,
FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
return ['valid' => false, 'error' => '不允許私有/保留 IP'];
}
return ['valid' => true, 'ip' => $ip];
}
// 測試
$tests = ["192.168.1.1", "8.8.8.8", "2001:db8::1", "not-an-ip", "::1"];
foreach ($tests as $t) {
print_r(validateIpInput($t));
}
4. 獲取客戶端真實 IP(含代理處理)
<?php
function getClientIp() {
$headers = [
'HTTP_CF_CONNECTING_IP', // Cloudflare
'HTTP_X_FORWARDED_FOR', // 標準代理
'HTTP_X_REAL_IP', // Nginx proxy
'HTTP_CLIENT_IP',
'REMOTE_ADDR' // 直連
];
foreach ($headers as $header) {
if (!empty($_SERVER[$header])) {
$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'] ?? 'unknown';
}
常見陷阱
| 陷阱 | 說明 | 解決 |
| IPv6 壓縮寫法 | ::1、2001:db8:: 合法但易被忽略 | 直接用 filter_var,原生支援 |
| IPv4 映射 IPv6 | ::ffff:192.168.1.1 會被視為 IPv6 | 需配合 FILTER_FLAG_IPV4 排除 |
| 前導零 | 192.168.001.001 在某些版本被視為八進位 | PHP 7.1+ 已修正,建議升級 |
| CIDR 記法 | 192.168.1.0/24 會驗證失敗 | 需自行解析 CIDR,filter_var 不支援 |
效能對比
<?php
$ips = array_fill(0, 100000, '192.168.1.1');
$t = microtime(true);
foreach ($ips as $ip) filter_var($ip, FILTER_VALIDATE_IP);
echo "filter_var: " . (microtime(true)-$t) . "s\n";
$t = microtime(true);
foreach ($ips as $ip) preg_match('/^(\d{1,3}\.){3}\d{1,3}$/', $ip);
echo "preg_match: " . (microtime(true)-$t) . "s\n";
// 典型結果: filter_var 約 0.02s, preg_match 約 0.08s (快 4 倍)
總結
- 首選 filter_var:內建、快、支援 IPv6、旗標靈活
- 組合旗標:
FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE 是公網 IP 驗證黃金組合
- 不要自己寫正則:IPv6 複雜度極高,極易漏情況
- 代理環境:務必解析
X-Forwarded-For 並再次驗證