首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP 數字驗證函數:正則表達式實作與現代最佳替代方案

     編輯:PHP 綜合知識

驗證輸入是否為合法數字是表單處理、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. 等非標準格式
    • 不支援科學記號、底線分隔、千分位符號
  • 直接輸出 HTML 並 exit:違反關注點分離,難以複用、測試、國際化
  • XSS 風險:直接輸出使用者輸入 $inid 未經跳脫

現代 PHP 驗證方案比較

方案一:內建過濾器 filter_var() (推薦通用)

<?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
?>

方案二:正則表達式 (PCRE/preg_*) - 精確控制格式

<?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 內建機制,能構建健壯的輸入防線。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved