Menu

PHP preg_match(): biểu thức chính quy kèm ví dụ

preg_match($pattern, $subject, $matches) kiểm tra một chuỗi với biểu thức chính quy, trả về 1 nếu khớp và 0 nếu không. Tìm hiểu nhóm bắt, nhóm có tên, preg_match_all, neo, mẫu kiểm tra dữ liệu và modifier u cho văn bản UTF-8 như tiếng Việt hay tiếng Nhật.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

preg_match($pattern, $subject, $matches) kiểm tra một chuỗi có khớp với biểu thức chính quy (regular expression) hay không. Hàm trả về 1 khi khớp và 0 khi không, đồng thời điền những gì tìm được vào $matches: preg_match('/\d+/', 'Order 4521 shipped', $m) trả về 1 và gán $m[0] bằng "4521".

Mẫu là một chuỗi có ký tự phân cách bao quanh, thường là /.../, theo sau là các modifier như i. Hãy viết mẫu trong dấu nháy đơn: trong dấu nháy kép, PHP sẽ thay $name và các chuỗi escape như \x41 hay \101 trước, nên engine regex có thể nhận một mẫu khác với mẫu bạn đã gõ.

Nhóm bắt

Dấu ngoặc tròn trong mẫu bắt lấy phần chuỗi mà chúng khớp. $m[0] là toàn bộ phần khớp, $m[1] là nhóm thứ nhất, $m[2] là nhóm thứ hai, đếm theo dấu ngoặc mở từ trái sang.

Nhóm có tên

(?<name>...) đặt tên cho một nhóm. Giá trị được lưu dưới cả tên lẫn số thứ tự của nhóm, nên code đọc kết quả sẽ không hỏng khi ai đó thêm một nhóm ở phía trước trong mẫu.

Tìm mọi lần khớp với preg_match_all

preg_match_all() tiếp tục tìm sau lần khớp đầu tiên và trả về số lần khớp. Mặc định $m[0] chứa mọi phần khớp đầy đủ và $m[1] chứa mọi giá trị của nhóm thứ nhất. Với PREG_SET_ORDER, bạn nhận một mảng cho mỗi lần khớp, dễ duyệt bằng vòng lặp hơn.

Kiểm tra dữ liệu nhập: neo, số điện thoại, mã bưu chính

Để kiểm tra dữ liệu, mẫu phải khớp với toàn bộ chuỗi, nên hãy bắt đầu bằng ^ và kết thúc bằng $. Có một cái bẫy: $ còn khớp ngay trước dấu xuống dòng cuối cùng, nên '/^\d+$/' chấp nhận "123\n". Thêm modifier D, hoặc dùng \z, chỉ khớp ở đúng điểm cuối chuỗi.

Với địa chỉ email, một regex hoặc sẽ từ chối địa chỉ hợp lệ, hoặc sẽ chấp nhận địa chỉ không hợp lệ. Hãy dùng filter_var($email, FILTER_VALIDATE_EMAIL) thay thế (xem filter_var()).

Modifier u cho UTF-8 và tiếng Nhật

Không có modifier u, mẫu làm việc trên từng byte. Một ký tự tiếng Nhật chiếm ba byte trong UTF-8 (chữ có dấu tiếng Việt cũng chiếm nhiều byte), nên . chỉ khớp một phần ba ký tự và độ dài tính ra bị sai. Có u, mẫu làm việc trên ký tự và bạn dùng được các lớp thuộc tính Unicode.

u còn kiểm tra chuỗi đầu vào: nếu chuỗi không phải UTF-8 hợp lệ, preg_match() trả về false thay vì so khớp.

Escape dữ liệu người dùng bằng preg_quote

Các ký tự như ., +, ?, ( và / có nghĩa đặc biệt trong mẫu. Nếu một phần của mẫu đến từ biến, chẳng hạn từ khóa tìm kiếm, hãy cho nó đi qua preg_quote() với ký tự phân cách làm đối số thứ hai, nếu không 1+1 sẽ khớp với 11 và một dấu / chưa escape sẽ làm hỏng mẫu.

Một mẫu không hợp lệ không ném ngoại lệ. PHP in ra cảnh báo và preg_match() trả về false:

Warning: preg_match(): No ending delimiter '/' found in /path/to/index.php on line 3

Vì vậy hãy kiểm tra === false (hoặc preg_last_error_msg()) khi mẫu được tạo lúc chạy. Để kiểm tra một chuỗi con đơn giản, không cần mẫu nào, str_contains() nhanh và rõ ràng hơn. Để thay đổi đoạn văn bản đã khớp, hãy dùng preg_replace().

Câu hỏi thường gặp

preg_match trả về gì trong PHP?

1 nếu mẫu khớp, 0 nếu không khớp, và false nếu bản thân mẫu không hợp lệ. Vì 0 và false đều là giá trị falsy, hãy so sánh bằng === 1 khi bạn cần phân biệt mẫu bị lỗi với trường hợp không khớp.

Làm sao lấy đoạn văn bản khớp từ preg_match?

Truyền thêm đối số thứ ba: preg_match('/(\d+)/', 'Order 4521', $m). Khi đó $m[0] là toàn bộ phần khớp và $m[1] là nhóm bắt đầu tiên, ở đây cả hai đều là "4521". Với nhóm có tên (?<id>\d+), giá trị còn nằm trong $m['id'].

preg_match và preg_match_all khác nhau thế nào?

preg_match() dừng ở lần khớp đầu tiên. preg_match_all() tìm mọi lần khớp, trả về số lần tìm được và điền tất cả vào $matches: preg_match_all('/\d+/', 'a1 b22 c333', $m) trả về 3 và $m[0] là ["1", "22", "333"].

Làm sao dùng preg_match với tiếng Việt, tiếng Nhật hay văn bản UTF-8 khác?

Thêm modifier u: preg_match('/^.{3}$/u', '日本語') trả về 1. Không có u, PHP coi chuỗi là các byte, nên . chỉ khớp một byte của một ký tự 3 byte. Có u, bạn còn dùng được các lớp Unicode như \p{Han}, \p{Hiragana} và \p{Katakana}.

Làm sao kiểm tra một chuỗi chỉ chứa chữ số trong PHP?

preg_match('/^\d+$/D', $s) hoặc, không cần regex, ctype_digit($s). Modifier D (hoặc \z thay cho $) rất quan trọng: $ thường còn chấp nhận một dấu xuống dòng ở cuối, nên "123\n" cũng sẽ lọt qua.

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu