Hướng dẫn dùng lookahead trong PHP

Khi chúng ta chạy một đoạn code PHP, có rất nhiều thứ xảy ra sâu bên dưới mà ta không nhìn thấy. Một cách khái quát, bộ thông dịch PHP trải qua 4 giai đoạn khi nó thực thi 1 đoạn code:

  • Lexing

  • Parsing

  • BIên dịch (compilation)

  • Thông dịch (intepretation)

Bài viết này sẽ tập trung vào các giai đoạn này, biểu diễn các output trong mỗi giai đoạn để hiểu bản chất. Cần chú ý rằng một số extension được cài đặt mặc định cùng PHP ( tokenizer, OPcache, …), trong khi đó nhiều extension bắt buộc phải cài đặt và khởi động 1 cách thủ công ( php-ast và VLD ).

Giai đoạn 1: LEXING

Lexing (tokenizing) là quá trình chuyển một đoạn string (mã nguồnPHP) thành 1 chuỗi các token. Một token đơn giản là một định danh gắn với một giá trị. PHP sử dụng re2c để sinh ra các lexer của nó từ file khai báo zend_language_scanner.

An assertion is a test on the characters following or preceding the current matching point that does not actually consume any characters. The simple assertions coded as \b, \B, \A, \Z, \z, ^ and $ are described in escape sequences. More complicated assertions are coded as subpatterns. There are two kinds: those that look ahead of the current position in the subject string, and those that look behind it.

An assertion subpattern is matched in the normal way, except that it does not cause the current matching position to be changed. Lookahead assertions start with (?= for positive assertions and (?! for negative assertions. For example, \w+(?=;) matches a word followed by a semicolon, but does not include the semicolon in the match, and foo(?!bar) matches any occurrence of "foo" that is not followed by "bar". Note that the apparently similar pattern (?!foo)bar does not find an occurrence of "bar" that is preceded by something other than "foo"; it finds any occurrence of "bar" whatsoever, because the assertion (?!foo) is always true when the next three characters are "bar". A lookbehind assertion is needed to achieve this effect.

Lookbehind assertions start with (?<= for positive assertions and (?(? does find an occurrence of "bar" that is not preceded by "foo". The contents of a lookbehind assertion are restricted such that all the strings it matches must have a fixed length. However, if there are several alternatives, they do not all have to have the same fixed length. Thus (?<=bullock|donkey) is permitted, but (? causes an error at compile time. Branches that match different length strings are permitted only at the top level of a lookbehind assertion. This is an extension compared with Perl 5.005, which requires all branches to match the same length of string. An assertion such as (?<=ab(c|de)) is not permitted, because its single top-level branch can match two different lengths, but it is acceptable if rewritten to use two top-level branches: (?<=abc|abde) The implementation of lookbehind assertions is, for each alternative, to temporarily move the current position back by the fixed width and then try to match. If there are insufficient characters before the current position, the match is deemed to fail. Lookbehinds in conjunction with once-only subpatterns can be particularly useful for matching at the ends of strings; an example is given at the end of the section on once-only subpatterns.

Biểu thức chính quy (Regular Expression)  thường được gọi là Regex hoặc RegExp. Là thuật toán khớp mẫu mạnh mẽ có thể được thực hiện trong một biểu thức. Nó giúp bạn trong tiết kiệm được rất nhiều thời gian khi xây dựng các trang web động.

Hầu hết các ngôn ngữ lập trình đều hỗ trợ Regex như Javascript, PHP, Java… Thậm chí RegExp còn rất phổ biến trong các ứng dụng, công cụ khác nhau như rewrite URL, tìm kiếm và thay thế trong các IDE…

>>> Xem ngay SÁCH LẬP TRÌNH PHP TỪ CĂN BẢN ĐẾN NÂNG CAO

Biểu thức chính quy là gì?

Biểu thức chính quy là một nhóm các ký tự, ký hiệu nó được sử dụng để tìm kiếm văn bản (text).

Một biểu thức chính quy là một mẫu nó tương đồng quy luật với một chuỗi từ trái qua phải. Biểu thức chính quy tên tiếng anh là Regular Expression  thường gọi tắt là regex hoặc regexp

Trong lập trình nó được dùng với các hàm xử lý chuỗi, xử lý văn bản với các tác vụ cụ thể như: tìm và thay thế chuỗi, kiểm tra tính hợp lệ của dữ liệu, trích xuất chuỗi con từ một chuỗi … 

Tại sao lại sử dụng biểu thức chính quy?

  • Biểu thức chính quy đơn giản hóa việc xác định các mẫu trong chuỗi dữ liệu bằng cách gọi một hàm duy nhất. Điều này giúp chúng ta tiết kiệm thời gian lập trình.
  • Khi xác thực đầu input của người dùng nhập vào như username, password, email, tên miền, số điện thoại, địa chỉ IP…
  • Highlight từ khóa trong kết quả tìm kiếm.
  • Khi tạo mẫu HTML tùy chỉnh. Biểu thức chính quy thông thường có thể được sử dụng để xác định các tag và thay thế chúng bằng dữ liệu thực tế.
  • Đặc biệt, sử dụng biểu thức chính quy tăng performance của chương trình rất nhiều lần.

Khai báo biểu thức chính quy

Để khai báo một chuỗi Regular Expression ta chỉ cần khai báo bắt đầu bằng ký tự / và kết thúc cũng là ký tự /

Cú pháp: $pattern = ‘/các ký tự của BTCQ- metacharacters/flags’;

Tham khảo ví dụ sau đây: 

// Partern kiểm tra trong subject có tồn tại chuỗi abc không
$pattern = '/abc/';
$subject = 'abc';
if (preg_match($pattern, $subject)){
    echo 'Chuỗi regex so khớp';
}

Trong ví dụ trên, $pattern = ‘/abc/’ có ý nghĩa là tìm trong chuỗi $subject có chứa chuỗi ‘abc’ hay không. Vì chúng ta truyền vào $subject là ‘abc’ cho nên kết quả thu được sẽ là thông báo có chứa chuỗi. Hàm preg_match sẽ được giới thiệu ở sau.

Metacharactes

Các ký tự dùng trong biểu thức chính quy được gọi là metacharacters. Sau đây là một số ký tự cơ bản thường hay sử dụng trong biểu thức chính quy: 

MetacharacterMô tảVí dụ^Bắt đầu của chuỗi /^a/ sẽ khớp với các chuỗi bắt đầu bằng chữ a, như: a, ab, aabc…$Kết thúc của chuỗi /a$/ sẽ khớp với chuỗi kết thúc là ký tự, như a, ba, cha, …*Ký tự / cụm ký tự đứng trước lặp lại 0 đến nhiều lần. /^a*$/ sẽ khớp với chuỗi chứa 0, 1 hay nhiều chữ a, như: chuỗi rỗng, a, aa, aaaa,…+ Yêu cầu các ký tự đứng trước xuất hiện ít nhất 1 lần /goo+gle/ sẽ khớp với google, gooogle?Tùy chọn có hay không cho mẫu phía trước/^a?$/ sẽ khớp với chuỗi rỗng (“”) hoặc chuỗi chứa chữ a (“a”){n,m}Lặp tối thiểu là n lần, tối đa là m lần/^a{1,3}$/ sẽ khớp với a, aa, aaa\Sử dụng để ký tự sau nó là nguyên thủy, biểu diễn ký tự đặc biệt 
(T|t)he => The fat cat sat on the mat.
(T|t)he(?=\sfat) => The fat cat sat on the mat.
1 /google+\.com/ sẽ coi dấu dấu là dấu chấm theo nghĩa đen, ví dụ: google.com|Biểu diễn thay thế, phép toán 
(T|t)he => The fat cat sat on the mat.
(T|t)he(?=\sfat) => The fat cat sat on the mat.
2x|y sẽ khớp x hoặc y[]Tập hợp ký tự. Phù hợp nếu có bất kỳ ký tự nào trong dấu [] /[abc]/ sẽ khớp với a,b,c[^]Tập hợp ký tự phủ định. Phù hợp nếu không có ký tự nào trong []/[^xyz]/ sẽ khớp nếu loại trừ các ký tự xyz(xyz)Biểu diễn một nhóm ký tự/^(xyz)$/ sẽ khớp với xyz.Khớp với bất kỳ ký tự đơn nào trừ dòng mới/^a.$/ khớp với an, am,…–Khoảng liên tiếp các giá trịx-z tức các giá trị x,y,za-z Khớp với các ký tự thường từ a-z /a-z/ sẽ khớp với các ký tự thường từ a-zA-Z Khớp với các ký tự Hoa từ A-Z /A-Z/ sẽ khớp với các ký tự chữa hoa A-Z0-9 Khớp bất kỳ số nào trong khoảng từ 0 đến 9 /0-9/ sẽ khớp với các ký số

Ký hiệu tắt cho tập hợp

Viết tắtDiễn tả\wChữ,sô, và _, tương đương với: 
(T|t)he => The fat cat sat on the mat.
(T|t)he(?=\sfat) => The fat cat sat on the mat.
3\WNgoài bảng chữ cái, tương đương với: 
(T|t)he => The fat cat sat on the mat.
(T|t)he(?=\sfat) => The fat cat sat on the mat.
4\dCác số: 
(T|t)he => The fat cat sat on the mat.
(T|t)he(?=\sfat) => The fat cat sat on the mat.
5\DKhông phải số: 
(T|t)he => The fat cat sat on the mat.
(T|t)he(?=\sfat) => The fat cat sat on the mat.
6\sLà ký tự trắng, tương đương với: 
(T|t)he => The fat cat sat on the mat.
(T|t)he(?=\sfat) => The fat cat sat on the mat.
7\SKhông phải ký tự trắng: 
(T|t)he => The fat cat sat on the mat.
(T|t)he(?=\sfat) => The fat cat sat on the mat.
8

Các cờ – flags

CờDiễn tảiThiết lập không phân biệt chữ hoa chữ thườnggTìm kiếm toàn chuỗi.mSo khớp trên từng dòng đối với văn bản đa dòng và có sử dụng cặp “^$”


Các cờ này được đưa vào mẫu theo dạng 

(T|t)he => The fat cat sat on the mat.
(T|t)he(?=\sfat) => The fat cat sat on the mat.
9

(T|t)he(?!\sfat) => The fat cat sat on the mat.
0 => The fat cat sat on the mat.

(T|t)he(?!\sfat) => The fat cat sat on the mat.
1 => The fat cat sat on the mat.

(T|t)he(?!\sfat) => The fat cat sat on the mat.
2 => The fat cat sat on the mat.

Biểu thức ?= lookahead

Lookahead 

(T|t)he(?!\sfat) => The fat cat sat on the mat.
3 cho thêm vào để lọc kết quả.

Ký hiệu 

(T|t)he(?!\sfat) => The fat cat sat on the mat.
3. Phần đầu của biểu thức phải được tiếp nối bởi biểu thức 
(T|t)he(?!\sfat) => The fat cat sat on the mat.
5.
Ví dụ 
(T|t)he(?!\sfat) => The fat cat sat on the mat.
6 thì 
(T|t)he(?!\sfat) => The fat cat sat on the mat.
5 là 
(T|t)he(?!\sfat) => The fat cat sat on the mat.
8 – nghĩa là 
(T|t)he(?!\sfat) => The fat cat sat on the mat.
9 hoặc 
(?<=(T|t)he\s)(fat|mat) => The fat cat sat on the mat.
0 theo sau là 
(?<=(T|t)he\s)(fat|mat) => The fat cat sat on the mat.
1 vậy tìm được 2 kết quả. Nhưng do có biểu thức lookahead, điều này thì kết quả phù hợp là chỉ lấy khi theo sau nó là chuỗi 
(?<=(T|t)he\s)(fat|mat) => The fat cat sat on the mat.
2

(T|t)he => The fat cat sat on the mat.
(T|t)he(?=\sfat) => The fat cat sat on the mat.

Biểu thức ?! phủ định lookahead

Ký hiệu là 

(?<=(T|t)he\s)(fat|mat) => The fat cat sat on the mat.
3, nghĩa là lấy kết quả mà đi sau nó không có chuỗi 
(T|t)he(?!\sfat) => The fat cat sat on the mat.
5

(T|t)he(?!\sfat) => The fat cat sat on the mat.

Biểu thức (?<=) lookbehind

Sử dụng để lấy các phù hợp mà đi trước là một mẫu cũ thể. 

(?<=(T|t)he\s)(fat|mat) => The fat cat sat on the mat.
5 có nghĩa lấy tất cả các từ 
(?<=(T|t)he\s)(fat|mat) => The fat cat sat on the mat.
2 hoặc 
(?<=(T|t)he\s)(fat|mat) => The fat cat sat on the mat.
7 sau các từ 
(?<=(T|t)he\s)(fat|mat) => The fat cat sat on the mat.
8 hoặc 
(?<=(T|t)he\s)(fat|mat) => The fat cat sat on the mat.
9