驗證輸入是否為合法數字是表單處理、API 參數檢查的基礎需求。早期開發者常用 ereg() 正則表達式,但此函數已於 PHP 5.3 棄用、PHP 7.0 移除。本文解析經典正則寫法的問題,並提供現代、安全、效能更好的驗證方案。
<?php
// 原始碼:使用已移除的 ereg()
function checknum($id, $say) {
$inid = $id;
// ^(-{0,1}|\+{0,1})[0-9]+(\.{0,1}[0-9]+)$
// 問題:{0,1} 等同 ?,寫法冗長;ereg 不支援現代正則特性
if (!ereg("^(-{0,1}|\+{0,1})[0-9]+(\.{0,1}[0-9]+)$", $inid)) {
echo $say . ",入口:" . "" . $inid . "";
exit;
}
}
// 呼叫範例
$tid = "111fff";
checknum($tid, "數字非法"); // 會輸出錯誤並 exit
?>
ereg() 自 PHP 7.0 不存在,直接報 Fatal Error[-+]? 較 ({0,1}|{0,1}) 簡潔\.? 較 \.{0,1} 簡潔+.、-.、123. 等非標準格式$inid 未經跳脫<?php
function is_valid_number($value, $options = []) {
// 預設:允許整數、浮點數、正負號、科學記號
$flags = FILTER_FLAG_ALLOW_FRACTION | FILTER_FLAG_ALLOW_THOUSAND | FILTER_FLAG_ALLOW_SCIENTIFIC;
if (isset($options['integer_only']) && $options['integer_only']) {
$flags = 0; // 僅整數
}
if (isset($options['min']) || isset($options['max'])) {
$opts = [];
if (isset($options['min'])) $opts['min_range'] = $options['min'];
if (isset($options['max'])) $opts['max_range'] = $options['max'];
return filter_var($value, FILTER_VALIDATE_FLOAT, ['flags' => $flags, 'options' => $opts]) !== false;
}
return filter_var($value, FILTER_VALIDATE_FLOAT, ['flags' => $flags]) !== false;
}
// 測試
var_dump(is_valid_number('123')); // true
var_dump(is_valid_number('-45.67')); // true
var_dump(is_valid_number('+3.14')); // true
var_dump(is_valid_number('1,234.56')); // true (千分位)
var_dump(is_valid_number('1.5e-3')); // true (科學記號)
var_dump(is_valid_number('abc')); // false
var_dump(is_valid_number('123abc')); // false
var_dump(is_valid_number('')); // false
var_dump(is_valid_number('12.34.56')); // false
// 僅整數
var_dump(is_valid_number('123', ['integer_only' => true])); // true
var_dump(is_valid_number('12.3', ['integer_only' => true])); // false
// 範圍限制
var_dump(is_valid_number('50', ['min' => 0, 'max' => 100])); // true
var_dump(is_valid_number('150', ['min' => 0, 'max' => 100])); // false
?>
<?php
/**
* 精確數字格式驗證
* @param string $value 輸入值
* @param array $rules 驗證規則
* @return bool
*/
function validate_number_format($value, array $rules = []) {
$defaults = [
'allow_sign' => true, // 允許 +/-
'allow_decimal' => true, // 允許小數點
'allow_thousands' => false, // 允許千分位分隔符
'allow_scientific' => false, // 允許科學記號
'decimal_places' => null, // 強制小數位數 (null=不限, 0=整數, 2=兩位)
'min' => null,
'max' => null,
];
$rules = array_merge($defaults, $rules);
// 建構正則
$pattern = '/^';
if ($rules['allow_sign']) $pattern .= '[-+]?';
if ($rules['allow_thousands']) {
// 1,234,567 或 1234567
$pattern .= '(?:\d{1,3}(?:,\d{3})*|\d+)';
} else {
$pattern .= '\d+';
}
if ($rules['allow_decimal']) {
if ($rules['decimal_places'] === 0) {
// 整數,不允許小數點
} elseif ($rules['decimal_places'] !== null) {
$pattern .= '\.\d{' . (int)$rules['decimal_places'] . '}';
} else {
$pattern .= '(?:\.\d+)?'; // 可選小數部分
}
}
if ($rules['allow_scientific']) {
$pattern .= '(?:[eE][-+]?\d+)?';
}
$pattern .= '$/';
if (!preg_match($pattern, $value)) {
return false;
}
// 數值範圍檢查(轉換後比較,避免字串比較錯誤)
$num = (float)$value;
if ($rules['min'] !== null && $num < $rules['min']) return false;
if ($rules['max'] !== null && $num > $rules['max']) return false;
return true;
}
// 使用範例
$rules = ['allow_thousands' => true, 'decimal_places' => 2, 'min' => 0, 'max' => 999999.99];
var_dump(validate_number_format('1,234.56', $rules)); // true
var_dump(validate_number_format('1234.56', $rules)); // true
var_dump(validate_number_format('1,234.567', $rules)); // false (小數位超過)
var_dump(validate_number_format('1234', $rules)); // false (需兩位小數)
?>
<?php
/**
* 簡單數字檢查:利用 PHP 型別轉換特性
* 注意:"123abc" -> 123 (會截取前導數字),通常不建議用於嚴格驗證
*/
function is_numeric_strict($value) {
// is_numeric() 支援十六進位、科學記號、前後空白
// 但 "123abc" 回傳 false
return is_numeric($value) && !is_array($value);
}
/**
* 嚴格整數檢查
*/
function is_int_strict($value) {
// 字串整數 "123" 也視為合法
return filter_var($value, FILTER_VALIDATE_INT) !== false;
}
/**
* 嚴格浮點數檢查
*/
function is_float_strict($value) {
return filter_var($value, FILTER_VALIDATE_FLOAT) !== false;
}
// ctype_digit:僅限純數字字串 (不含符號、小數點、空白)
var_dump(ctype_digit('12345')); // true
var_dump(ctype_digit('-123')); // false
var_dump(ctype_digit('12.3')); // false
var_dump(ctype_digit(' 123')); // false
?>
<?php
class NumberValidator {
public static function validate($value, array $rules = []) {
$result = [
'valid' => false,
'value' => null,
'type' => null, // 'int', 'float'
'errors' => []
];
// 1. 基本型別檢查
if (is_array($value) || is_object($value) || is_resource($value)) {
$result['errors'][] = '不支援的資料型別';
return $result;
}
$str = (string)$value;
if ($str === '') {
$result['errors'][] = '輸入不可為空';
return $result;
}
// 2. 格式驗證 (使用 filter_var 為主)
$filter = $rules['integer_only'] ?? false ? FILTER_VALIDATE_INT : FILTER_VALIDATE_FLOAT;
$flags = 0;
if (!($rules['integer_only'] ?? false)) {
$flags |= FILTER_FLAG_ALLOW_FRACTION;
if ($rules['allow_thousands'] ?? false) $flags |= FILTER_FLAG_ALLOW_THOUSAND;
if ($rules['allow_scientific'] ?? false) $flags |= FILTER_FLAG_ALLOW_SCIENTIFIC;
}
$options = [];
if (isset($rules['min'])) $options['min_range'] = $rules['min'];
if (isset($rules['max'])) $options['max_range'] = $rules['max'];
$filtered = filter_var($str, $filter, ['flags' => $flags, 'options' => $options ?: null]);
if ($filtered === false) {
$result['errors'][] = '數字格式不合法';
return $result;
}
// 3. 額外精確檢查 (如強制小數位數)
if (isset($rules['decimal_places']) && !$rules['integer_only']) {
if (!preg_match('/^\d+(\.\d+)?$/', $str)) {
// 簡化:實際應用需處理千分位、符號
$result['errors'][] = '格式不符合規範';
return $result;
}
if (strpos($str, '.') !== false) {
$decimals = strlen(substr(strrchr($str, '.'), 1));
if ($decimals !== $rules['decimal_places']) {
$result['errors'][] = "小數位數必須為 {$rules['decimal_places']} 位";
return $result;
}
} elseif ($rules['decimal_places'] > 0) {
$result['errors'][] = "必須包含 {$rules['decimal_places']} 位小數";
return $result;
}
}
// 4. 成功
$result['valid'] = true;
$result['value'] = $filtered;
$result['type'] = $rules['integer_only'] ?? false ? 'int' : 'float';
return $result;
}
}
// 使用範例
$input = $_POST['price'] ?? '';
$validation = NumberValidator::validate($input, [
'integer_only' => false,
'allow_thousands' => true,
'decimal_places' => 2,
'min' => 0.01,
'max' => 999999.99
]);
if ($validation['valid']) {
$price = $validation['value']; // 安全的 float 值
// 寫入資料庫...
} else {
// 回傳錯誤訊息
echo json_encode(['errors' => $validation['errors']]);
}
?>
<!-- HTML5 原生驗證 -->
<input type="number" name="price" step="0.01" min="0.01" max="999999.99" required>
<!-- JavaScript 即時驗證 -->
<script>
function validateNumber(input, rules) {
const value = input.value.trim();
if (!value) return '請輸入數字';
// 簡單正則
const regex = /^[-+]?\d*\.?\d+$/;
if (!regex.test(value)) return '格式不正確';
const num = parseFloat(value);
if (rules.min !== undefined && num < rules.min) return `最小值為 ${rules.min}`;
if (rules.max !== undefined && num > rules.max) return `最大值為 ${rules.max}`;
return null; // 通過
}
</script>
| 場景 | 推薦方案 | 理由 |
|---|---|---|
| 通用表單/API 參數驗證 | filter_var() + 選項 | 內建、安全、支援範圍、科學記號、千分位 |
| 嚴格格式控制 (貨幣、精度) | 自訂正則 + filter_var | 精確控制小數位、千分位、符號位置 |
| 極高效能、簡單整數檢查 | ctype_digit / filter_var INT | 無正則開銷、C 層級實作 |
| 前端即時回饋 | HTML5 type=number + JS | 使用者體驗佳、減少無效請求 |
| 避免使用 | ereg、eregi、自寫簡易正則 | 已移除、不安全、缺陷多 |
數字驗證看似簡單,實則涉及格式、範圍、精度、區域化、安全等多層面。採用分層驗證策略(前端體驗 → 後端安全 → 業務邏輯),並善用 PHP 內建機制,能構建健壯的輸入防線。