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

PHP 註冊表單驗證器完整實作:類別設計、錯誤處理與安全實踐

     編輯:PHP 相關知識

用戶註冊是 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() 支援多位元組字元(中文)。
  • 密碼策略改為「含字母+數字」,移除特殊字元強制,提升使用者體驗並符合 NIST 建議。
  • Email 驗證採用內建 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>

五、安全與最佳實踐補充

  1. CSRF 保護:表單務必包含 <input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">,提交時驗證。
  2. 速率限制:同一 IP/裝置短時間內限制註冊次數,防暴力註冊。
  3. 密碼強度計量器:前端即時提示(如 zxcvbn),引導使用者設定強密碼。
  4. 雙因子認證(2FA)預留:User 表預留 totp_secret、backup_codes 欄位。
  5. 日誌記錄:註冊成功/失敗均記錄 IP、User-Agent、耗時,便於異常分析。
  6. 單元測試:針對各驗證規則、邊界值、資料庫唯一性衝突編寫測試案例。

六、擴展建議

  • 驗證器介面化 ValidatorInterface,支援不同場景(後台建立用戶、API 註冊、社交登入綁定)。
  • 引入 Symfony Validator 或 Laravel Validation 等成熟組件,獲得國際化、規則複用、異步驗證等高階功能。
  • 採用 CQRS/Event Sourcing:註冊命令發送至命令總線,領域事件觸發歡迎郵件、初始化個人資料等副作用。

這個驗證器實作展示了「單一職責、錯誤收集、延後副作用」的現代 PHP 設計風格。實際專案中,建議逐步遷移至成熟驗證組件,專注於業務邏輯而非基礎設施重造。

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