新視野行銷企劃

pattern 正則表達式驗證實戰範例

扁平化插圖風格的教學封面,主題為 pattern 正則表達式驗證實戰範例,畫面呈現開發者操作 HTML 表單與正則表達式的視覺概念,配色溫暖且具現代感,適合作為網頁設計與程式教學文章的代表圖。

正則表達式(regular expression,簡稱 regex)是一種強大的文本匹配工具,它允許我們以簡潔的「模式」定義字符串應符合的格式。在 PHP 中,透過 Perl 相容的正則引擎 PCRE 及其函式(如 preg_match() 等),我們可以方便地驗證使用者輸入是否符合特定格式。良好的輸入驗證不僅能確保資料正確性,還有助於防止 SQL 注入、XSS 等安全問題。

本文將以實戰範例介紹如何在 PHP 中運用正則表達式進行驗證,包括常見的電子郵件地址、手機號碼、密碼強度和網址格式驗證。每個範例都附有可直接複製的 PHP 程式碼與詳盡說明。此外,我們也會講解非貪婪模式和先行/後行斷言等進階技巧,幫助你處理更複雜的驗證需求。讓我們從最常見的電子郵件驗證開始吧!

電子郵件地址驗證

電子郵件地址的格式看似簡單,實則有一定規則。我們可以用正則表達式明確限定電子郵件的各部分內容。以下是一個基本的電子郵件驗證模式範例:

PHP 程式碼
$email = "test@example.com";
$pattern = "/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/";

if (preg_match($pattern, $email)) {
    echo "有效的電子郵件地址。";  // 輸出:有效的電子郵件地址。
} else {
    echo "電子郵件地址格式不正確。";
}

說明:

^ 和 $:分別標誌字串的開頭與結尾,確保整個字串完全匹配模式(對於驗證非常重要,避免只匹配到部分字串)。

[A-Za-z0-9._%+-]+:匹配電子郵件「本地部分」(在 @ 前的使用者名稱)。允許一個或多個英文字母、數字或 ._%+- 等符號。

@:要求在本地部分後出現一個 @ 符號。

[A-Za-z0-9.-]+:匹配網域名稱的主體部分。允許字母、數字、點號和連字號,+ 表示一個以上的字元。

\.[A-Za-z]{2,}:網域的頂級域名(TLD),由一個「.」加上至少2個英文字母組成(例如 .com、.org、.edu 等)。

以上模式涵蓋了一般的電子郵件格式需求,但真實世界中的電郵格式可能更複雜。如果需要更嚴謹的驗證,可以考慮使用 PHP filter_var 函式或更完整的正則,但上述範例已足以應對大多數情況。

手機號碼驗證

電話號碼格式因地區而異。作為示範,我們以北美常見的手機號碼格式(含可選的國碼 +1 和各種分隔符號)為例進行驗證:

PHP 程式碼
$phone = "+1-555-123-4567";
$pattern = "/^\+?1?[-.\s]?\(?([0-9]{3})\)?[-.\s]?([0-9]{3})[-.\s]?([0-9]{4})$/";

if (preg_match($pattern, $phone)) {
    echo "手機號碼格式正確。";  // 輸出:手機號碼格式正確。
} else {
    echo "手機號碼格式不符合要求。";
}

說明:

^\+?1?:允許電話號碼以「+1」國碼開始,其中「+」號是可選的,1 也可選(支持寫「1」或「+1」,也允許無國碼直接開始)。

[-.\s]?:國碼後可選擇出現一個分隔符號,允許 連字號(-)、句點(.) 或空白作為區隔。

\(?[0-9]{3}\)?:接下來是3位數字,代表區碼。模式允許這3位數字使用括號括住 (...),括號本身也是可選的 \(? \)?。

[-.\s]?:區碼後再允許一個可選的分隔符號(同樣是 -、. 或空格)。

[0-9]{3}:再來是3位數字(電話號碼的前綴)。

[-.\s]?:可選的分隔符號,用於隔開最後一部分號碼。

[0-9]{4}:最後是4位數字(電話號碼的末4碼)。

$:整串結束,確保號碼沒有多餘字元。

這個正則範例允許的輸入範例包括:5551234567、555-123-4567、(555) 123-4567、+1 555 123 4567 等格式。若輸入不符合這些模式(例如長度不對或含有非數字的無效字元),驗證將不通過。

密碼強度驗證

為了提升密碼安全性,常要求密碼包含各種類型的字元並達到一定長度。正則表達式中的先行斷言非常適合用來同時檢查多項密碼條件。下面的模式要求密碼至少8個字元,且包含大寫字母、小寫字母、數字和特殊符號各至少一個:

PHP 程式碼
$password = "StrongP@ssword123";
$pattern  = "/^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/";

if (preg_match($pattern, $password)) {
    echo "密碼強度符合要求。";  // 輸出:密碼強度符合要求。
} else {
    echo "密碼必須包含大小寫字母、數字和特殊符號。";
}

說明:

(?=.*[A-Z]):正向先行斷言,確保字串中至少出現一個大寫字母。

(?=.*[a-z]):確保至少有一個小寫字母。

(?=.*\d):確保至少有一個數字(\d 等同於 [0-9])。

(?=.*[@$!%*?&]):確保至少有一個特殊符號(此處列出了常見的特殊字元集 @$!%*?&,可視需求擴充)。

[A-Za-z\d@$!%*?&]{8,}:整個密碼字串只能包含英文字母、數字及上述特殊符號,且長度至少為 8 個字元({8,} 表示8以上不限長度)。

^ 和 $:同樣用於錨定整個字串,要求整個密碼完全符合整個模式。

透過多個先行斷言的組合,上述 regex 能同時檢查多種條件。如果某項條件不滿足,整體匹配就會失敗。比如,"Password123" 缺少特殊符號、"hello@world" 沒有大寫字母,這些都無法通過驗證。

網址驗證

驗證 URL 比前幾個範例複雜一些,因為網址可能包含協議、子網域、路徑、查詢參數等多部分。下面提供一個相對通用的網址正則模式,它能匹配常見的 HTTP/HTTPS 網址:

PHP 程式碼
$url = "https://www.example.com/path?query=1#anchor";
$pattern = "/^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)$/";

if (preg_match($pattern, $url)) {
    echo "網址格式正確。";  // 輸出:網址格式正確。
} else {
    echo "網址格式不合法。";
}

說明:

^https?:\/\/:網址必須以 http:// 或 https:// 開頭。? 表示 s 是可選的。\/ 用反斜線轉義,表示實際的斜線符號。

(www\.)?:緊接著可選的 www. 子網域部分。整組以 (...)? 表示可有可無。

[-a-zA-Z0-9@:%._+~#=]{1,256}:主網域名稱允許的字元集(字母、數字及部分符號),長度在 1 到 256 個字元之間。這部分包括了網域名稱的不同段(例如 sub.domain 中的 sub),連字號和點都在允許範圍內。

\.[a-zA-Z0-9()]{1,6}:一個「.」後接 1 至 6 個字母或數字,表示頂級域名部分(例如 .com、.net、.org 等)。範例中限制為最多6個字元長是出於一般情況考量。

\b:單字邊界,確保前面的頂級域名結束(避免網域部分多出不應有的字母或數字)。

([-a-zA-Z0-9()@:%_\+.~#?&//=]*):網域後的路徑及參數部分是可選的(用整個括號 (...)* 表示可以出現零次或多次)。允許的字元包含一般 URL 中合法的各種符號,如斜線 /、問號 ?、& 符號等。這部分將匹配例如 /path?query=1#anchor 整段內容。

$:字串結尾,確保整個輸入從頭到尾都是有效的 URL 格式。

這個模式能匹配絕大多數標準網址,例如:http://example.com、https://www.example.com/index.php?a=1 等。同時,它會拒絕不合法的網址(比如不允許的協議、缺少頂級域名等)。請注意,此模式對頂級域名長度的限制是6個字元,若需要支援更長的域名(如 .museum 7 個字元),可以調整 {1,6} 為更大的上限甚至去除上限。此外,如果要驗證非 ASCII 的國際網域(IDN)或其他特殊 URL,還需要對模式進一步擴充。

進階技巧:非貪婪模式與斷言

在掌握了上述基礎範例後,了解一些正則表達式的進階技巧,能讓我們編寫出更強大靈活的驗證規則。本節將介紹兩個重要概念:非貪婪(Lazy)量詞和先行/後行斷言,並說明它們在實戰中的應用。

非貪婪模式 (Lazy Quantifiers)

正則表達式中的量詞(例如 *, +, {m,n})預設是貪婪的(Greedy),意思是匹配時會盡可能多地囊括字元。相對地,非貪婪模式則是在量詞後加上 ?,讓匹配儘可能少(也稱「懶惰匹配」)。運用非貪婪模式,可以避免過度匹配帶來的問題。

例子:假設我們要移除字串中的所有 HTML 標籤,如果使用貪婪模式的正則例如 /<.*>/,套用在 "Hello <b>World</b>!" 時,貪婪的 .* 可能會從第一個 < 一直匹配到最後一個 >,結果把整段字串都匹配了,這並不是我們想要的。解決方法是使用非貪婪量詞 /<.*?>/:

PHP 程式碼
$input = "Hello <b>World</b>!";
$output = preg_replace("/<.*?>/", "", $input);
echo $output;  // 輸出:Hello World!

在這裡,模式 <.*?> 能夠「最小化」地匹配每一對 < 和 > 之間的內容:它首先匹配 <,接著 .*? 以非貪婪方式匹配盡可能少的字元,直到遇到下一個 > 為止。如此一來,<b> 和 </b> 會被分別匹配並替換,而不會把中間的「World」吞掉。由此可見,非貪婪模式特別適合用在要匹配有限邊界內容的場合,避免不小心匹配過頭。

先行與後行斷言 (Lookahead/Lookbehind)

先行斷言(Lookahead)和後行斷言(Lookbehind)統稱為環視(Lookaround),是一種不消耗字元的零寬度匹配,它們允許我們在不將某段文本納入最終匹配結果的前提下,檢查該段文本是否存在(正向)或不存在(負向)。

正向先行斷言 (?=...):要求在當前匹配位置後方有特定模式存在。比如正則 /Java(?=Script)/ 作用是在找到 "Java" 後檢查其後是否緊接著 "Script"。應用這個模式去搜索字串 "Java and JavaScript",只會匹配到 Java(後面跟著 Script 的那部分),而不會匹配獨立出現的 "Java"。

負向先行斷言 (?!...):要求當前位置後方沒有特定模式。例如 /Java(?!Script)/ 只匹配不在 "JavaScript" 後面的 "Java",用於排除特定後綴的情況。

先行斷言適合在驗證時設定「必須包含或不得包含某段字串」的規則。我們在前述密碼驗證的例子中,就利用了多個正向先行斷言來同時檢查不同條件。

正向後行斷言 (?<=...):與先行相對,它要求在當前匹配位置前方有特定內容存在。例如,/(?<=\$)\d+$/ 能匹配以 $ 符號開頭的金額數字串(如字串 $100 會匹配到 100),但不包含 $ 本身。後行斷言常用於確保某段文本前面有特定前綴。

負向後行斷言 (?<!...):要求匹配的部分前方沒有某內容。比如 /(?<!\$)\d+/ 可以匹配不以 $ 符號緊鄰在前的數字。

需要注意的是,在 PHP 的 PCRE 引擎中,後行斷言內的模式必須是固定長度的,不能含有不確定長度的量詞(例如 (?<=\d{2,}) 這種就不被允許)。這是後行斷言的一個限制,在設計正則時需要留意。如果遇到此限制,可以嘗試轉換思路,用其他辦法實現相同條件。

透過先行/後行斷言,我們可以制定更加靈活的驗證規則。例如,先行斷言可檢查整個字串中是否包含至少一種必要成分(如特定字元或子字串),後行斷言則適合檢查前後關係(如某單詞是否緊跟在另一單詞後,或某輸入不應該有某前綴等)。由於它們不會消耗字串內容,所以能夠在不影響主要匹配結果的情況下,增加額外的約束條件。

常見問答 Q&A

問:為什麼有時用正則驗證時會出現部分匹配的情況?我的模式似乎匹配到了不該匹配的字串。

答:這通常是因為缺少了行首 (^) 和行尾 ($) 錨點。若沒有使用 ^...$,preg_match 在預設情況下會在輸入字串中尋找任意一段符合模式的子串。例如,正則 /cat/ 在字串 "catapult" 中也會返回匹配。對於驗證整個欄位輸入,我們應當將模式以 ^ 開頭、$ 結尾,使其從頭到尾匹配整個字串。如此一來,只有完全符合格式的輸入才會通過驗證。

問:PHP 已經有內建的驗證方法,例如驗證 email 有 filter_var,那為什麼還需要用正則表達式?

答:內建函式(如 filter_var($email, FILTER_VALIDATE_EMAIL) 驗證電子郵件、或 FILTER_VALIDATE_URL 驗證網址)確實方便且遵循標準,在大多數情況下應優先考慮。但正則表達式的意義在於靈活性和可定制。當你有特殊的格式需求或想整合多項驗證條件時,正則能讓你在一個模式中描述複雜的規則。例如我們上面的密碼強度檢查,透過一行模式就同時完成了多項驗證。需要注意的是,過於複雜的正則表達式可能難以維護,執行效能也可能下降。因此對於非常複雜的驗證場合,若有替代方案(例如多步驟檢查、使用現成函式庫等),也值得權衡考慮。

問:如果我要驗證包含中文或其他非英數字元的輸入,例如使用者姓名,該怎麼處理正則表達式?

答:正則表達式預設的\w、[A-Za-z0-9]等只匹配英文字母和阿拉伯數字,對中文等 Unicode 字元不適用。幸好 PCRE 支援 Unicode 類別和修飾符。我們可以使用 Unicode 字元屬性 來匹配特定語系的文字,例如 \p{Han} 代表中日韓漢字,\p{L} 代表任意語言的字母。同時,在模式末尾加上修飾符 u(如 /[\p{Han}]{2,5}/u),啟用 Unicode 模式。比如,驗證中文姓名可以使用 /^[\p{Han}]{2,5}$/u,這會匹配2到5個連續的漢字。若需要中英混合姓名,則模式中可以包含 \p{L}(字母)以及空格等。總之,使用 Unicode 類別並加上 u 修飾符,就能讓正則表達式正確處理各種語系的字元。

問:寫正則時老是出錯,有沒有什麼除錯(Debug)或測試的方法?

答:當然可以!編寫和調試正則表達式時,有幾個實用技巧:

善用線上工具:使用像 regex101 或 Regexr 這類線上正則測試工具。你可以即時輸入正則和測試字串,觀察匹配結果,這些工具通常還會解釋各個模式的含義,方便找出問題。

添加x修飾符:在正則尾部加上 x 可以讓你在模式中使用空白和註解,提升可讀性。例如:

PHP 程式碼
$pattern = '/^
    \d{4}        # 起始的4位數字年份
    -            # 年月之間的連字號
    \d{2}        # 2位數字的月份
    -            # 月日之間的連字號
    \d{2}        # 2位數字的日期
$/x';

如上,使用 x 模式讓我們可以分行撰寫並加註解,有助於檢查複雜正則的每一部分是否正確。

分步測試:若一個完整正則很複雜,不妨將其拆解成較小的片段逐一測試。例如先測試一段字元類別 [ ... ] 是否涵蓋了所需字元,再逐步擴充到整體模式。

使用 var_dump($matches): 在 PHP 中,以 preg_match() 或 preg_match_all() 取得匹配後,輸出 $matches 陣列可以查看正則捕獲了哪些內容,藉此確認你的捕獲群組是否對應到預期的子串。

運用以上方法,你可以更輕鬆地找出正則表達式中的問題並加以修正,逐步提高模式的正確性和效率。

以上就是pattern 正則表達式驗證實戰範例的全部內容。透過這些教學範例,我們學會了如何使用 PHP 的正則表達式來驗證各種常見輸入,以及一些進階技巧來應對複雜的情境。希望這篇文章能夠幫助你在實務中更熟練地運用正則表達式,撰寫出既嚴謹又靈活的輸入驗證邏輯!

CONTACT US

網站設計報價洽詢

請填寫您的資料,我們將儘快與您聯繫! 為必填