用戶註冊是 Web 應用的核心入口,嚴謹的輸入驗證能防止垃圾帳號、資料不一致與安全漏洞。本文提供一個採用靜態方法、錯誤收集模式的 RegisterValidator 類別,搭配 Error 值物件,展示從驗證規則、資料庫唯一性檢查到控制器整合的完整流程。
<?php
final class Error {
private string $source;
private string $message;
public function __construct(string $source, string $message) {
$this->source = $source;
$this->message = $message;
}
public function getSource(): string { return $this->source; }
public function getMessage(): string { return $this->message; }
}
設計優點: 不可變、攜帶欄位來源(source),前端可精準顯示欄位級錯誤。
<?php
final class RegisterValidator {
private function __construct() {} // 禁止實例化
/**
* @param string $username
* @param string $password
* @param string $repeatPassword
* @param string $email
* @return Error[] 錯誤集合,空陣列表示通過
*/
public static function validate(
string $username,
string $password,
string $repeatPassword,
string $email
): array {
$errors = [];
$username = trim($username);
$password = trim($password);
$repeatPassword = trim($repeatPassword);
$email = trim($email);
// 1. 用戶名驗證
if ($username === '') {
$errors[] = new Error('username', '用戶名不能為空。');
} elseif (mb_strlen($username) < 3) {
$errors[] = new Error('username', '用戶名長度不能小於 3 個字符。');
} elseif (mb_strlen($username) > 30) {
$errors[] = new Error('username', '用戶名長度不能超過 30 個字符。');
} elseif (!preg_match('/^[A-Za-z]/', $username)) {
$errors[] = new Error('username', '用戶名必須以字母開頭。');
} elseif (!preg_match('/^[A-Za-z0-9_]+$/', $username)) {
$errors[] = new Error('username', '用戶名只能包含字母、數字和下劃線。');
}
// 2. 密碼驗證
if ($password === '') {
$errors[] = new Error('password', '密碼不能為空。');
} elseif (mb_strlen($password) < 8) { // 建議最小 8 碼
$errors[] = new Error('password', '密碼長度不能小於 8 個字符。');
} elseif (mb_strlen($password) > 72) { // bcrypt 限制
$errors[] = new Error('password', '密碼長度不能超過 72 個字符。');
} elseif (!preg_match('/[A-Za-z]/', $password) || !preg_match('/\d/', $password)) {
$errors[] = new Error('password', '密碼必須包含字母和數字。');
} elseif ($password !== $repeatPassword) {
$errors[] = new Error('password', '兩次輸入的密碼不一致。');
}
// 3. Email 驗證
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errors[] = new Error('email', '郵箱格式不正確。');
} elseif (mb_strlen($email) > 254) { // RFC 5321
$errors[] = new Error('email', '郵箱長度不能超過 254 字符。');
}
// 4. 業務唯一性檢查(僅在格式通過後執行,減少資料庫查詢)
if (empty($errors)) {
$dao = new UserDao(); // 假設已實作
if ($dao->findByName($username)) {
$errors[] = new Error('username', '該用戶名已被使用。');
}
if ($dao->findByEmail($email)) {
$errors[] = new Error('email', '該郵箱已被註冊。');
}
}
return $errors;
}
}
mb_strlen() 支援多位元組字元(中文)。filter_var(),符合 RFC 標準。Error[] 而非拋出異常,便於控制器收集所有錯誤一次回顯。<?php
require_once 'RegisterValidator.php';
require_once 'UserDao.php';
require_once 'User.php';
require_once 'Utils.php';
require_once 'Flash.php';
$username = $password = $repeatPassword = $email = '';
$messages = [];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$username = trim($_POST['username'] ?? '');
$password = trim($_POST['password'] ?? '');
$repeatPassword = trim($_POST['repeat_password'] ?? '');
$email = trim($_POST['email'] ?? '');
// 執行驗證
$errors = RegisterValidator::validate($username, $password, $repeatPassword, $email);
if (empty($errors)) {
// 通過驗證,建立用戶
$dao = new UserDao();
$user = new User();
$user->setEmail($email);
$user->setUsername($username);
$user->setLastLoginIp(Utils::getIpAddress());
// 密碼雜湊:使用 password_hash(PHP 5.5+),自動處理鹽值與演算法升級
$user->setPassword(password_hash($password, PASSWORD_BCRYPT, ['cost' => 12]));
$savedUser = $dao->save($user);
if ($savedUser) {
UserLogin::setUserInfo($savedUser); // 設定登入 Session
Flash::addFlash('註冊成功!歡迎加入。');
Utils::redirect('welcome');
} else {
$messages[] = '系統錯誤,請稍後再試。';
}
} else {
// 收集錯誤訊息
foreach ($errors as $e) {
$messages[] = $e->getMessage();
}
}
}
// 視圖渲染(簡化)
include 'register_view.php';
<!-- register_view.php -->
<form method="post" action="">
<div class="field">
<label>用戶名</label>
<input type="text" name="username" value="<?= htmlspecialchars($username) ?>">
<?php if (isset($errors['username'])): ?>
<span class="error"><?= $errors['username'] ?></span>
<?php endif; ?>
</div>
<div class="field">
<label>Email</label>
<input type="email" name="email" value="<?= htmlspecialchars($email) ?>">
<?php if (isset($errors['email'])): ?>
<span class="error"><?= $errors['email'] ?></span>
<?php endif; ?>
</div>
<div class="field">
<label>密碼</label>
<input type="password" name="password">
<?php if (isset($errors['password'])): ?>
<span class="error"><?= $errors['password'] ?></span>
<?php endif; ?>
</div>
<div class="field">
<label>確認密碼</label>
<input type="password" name="repeat_password">
</div>
<button type="submit">註冊</button>
</form>
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">,提交時驗證。totp_secret、backup_codes 欄位。ValidatorInterface,支援不同場景(後台建立用戶、API 註冊、社交登入綁定)。Symfony Validator 或 Laravel Validation 等成熟組件,獲得國際化、規則複用、異步驗證等高階功能。這個驗證器實作展示了「單一職責、錯誤收集、延後副作用」的現代 PHP 設計風格。實際專案中,建議逐步遷移至成熟驗證組件,專注於業務邏輯而非基礎設施重造。