設計目標
- 聲明式規則定義:欄位名、必要性、型別/格式、錯誤訊息
- 支援陣列欄位 (核取方塊、多選)
- 內建常用驗證:長度、數字、Email、URL、中文、英文、字母數字、電話、敏感詞
- 輸出過濾:HTML 轉義、換行轉換、MD5、資料庫跳脫
- 錯誤回饋:JavaScript 彈窗/欄位標示
核心類別架構
class FormValidator {
private $rules = []; // 驗證規則
private $data = []; // 通過驗證的資料
private $errors = []; // 錯誤訊息
// 規則格式: 'field_name' => 'required|type:param|type2:param2'
public function setRules(array $rules): self {
$this->rules = $rules;
return $this;
}
public function validate(array $input = null): bool {
$input = $input ?? $_POST;
$this->data = [];
$this->errors = [];
foreach ($this->rules as $field => $ruleString) {
$value = $input[$field] ?? null;
$rules = explode('|', $ruleString);
foreach ($rules as $rule) {
if (!$this->checkRule($field, $value, $rule)) {
break; // 該欄位驗證失敗,停止後續規則
}
}
}
return empty($this->errors);
}
private function checkRule(string $field, $value, string $rule): bool {
// 解析 rule: "type:param1,param2"
if (strpos($rule, ':') !== false) {
[$type, $params] = explode(':', $rule, 2);
$params = explode(',', $params);
} else {
$type = $rule;
$params = [];
}
$method = 'validate' . ucfirst(strtolower($type));
if (!method_exists($this, $method)) {
$this->errors[$field] = "未知驗證規則: $type";
return false;
}
return $this->$method($field, $value, $params);
}
// 內建驗證方法...
protected function validateRequired($field, $value): bool {
if (is_array($value)) return !empty($value);
return $value !== '' && $value !== null;
}
protected function validateLength($field, $value, array $params): bool {
[$min, $max] = $params + [0, PHP_INT_MAX];
$len = is_array($value) ? count($value) : mb_strlen((string)$value);
if ($len < $min || $len > $max) {
$this->errors[$field] = "長度需在 $min-$max 之間";
return false;
}
return true;
}
protected function validateEmail($field, $value): bool {
if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
$this->errors[$field] = 'Email 格式錯誤';
return false;
}
return true;
}
// ... url, numeric, chinese, alpha, alphanum, phone, sensitive 等
// 取得結果
public function getData(): array { return $this->data; }
public function getErrors(): array { return $this->errors; }
public function getErrorString(string $glue = ', '): string {
return implode($glue, $this->errors);
}
// 輸出 JS 錯誤提示 (舊版相容)
public function getErrorScript(): string {
if (empty($this->errors)) return '';
$js = "<script>";
foreach ($this->errors as $field => $msg) {
$js .= "document.getElementById('err_$field').textContent=" . json_encode($msg) . ";";
}
$js .= "</script>";
return $js;
}
}
使用範例
$validator = new FormValidator();
$validator->setRules([
'username' => 'required|length:3,20|alphanum',
'email' => 'required|email',
'password' => 'required|length:8,32',
'age' => 'numeric|length:1,3',
'bio' => 'length:0,500',
'tags' => 'array', // 多選
]);
if ($validator->validate()) {
$cleanData = $validator->getData();
// 存入資料庫...
// $cleanData 已經過驗證,但入庫仍需預處理/參數綁定
} else {
// 顯示錯誤
foreach ($validator->getErrors() as $field => $msg) {
echo "<div class='error' id='err_$field'>$msg</div>";
}
}
輸出過濾方法
class OutputFilter {
public static function html($str): string {
return htmlspecialchars((string)$str, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
public static function nl2br($str): string {
return nl2br(self::html($str));
}
public static function sql(PDO $pdo, $str): string {
return $pdo->quote($str); // 或使用預處理
}
public static function md5($str): string {
return md5($str);
}
}
現代替代方案
- Symfony Validator:獨立元件,支援註解/屬性/XML/YAML 定義規則
- Laravel Validation:流暢介面、豐富規則、自動重導攜帶錯誤
- Respect\Validation:鏈式語法、可擴充
- PHP 內建 filter_var/filter_input:簡單驗證無需依賴