Sử dụng regex với preg_match, preg_replace, preg_split trong PHP

PHP Tutorial | by Học PHP

Regex là một ngôn ngữ nhỏ gọn, mạnh mẽ dùng để mô tả các mẫu tìm kiếm trong chuỗi văn bản. Nó cho phép bạn định nghĩa các quy tắc phức tạp để khớp, trích xuất, thay thế hoặc tách chuỗi dựa trên các mẫu linh hoạt, vượt xa khả năng của các phép so sánh chuỗi đơn giản.

PHP hỗ trợ đầy đủ Regex thông qua nhóm hàm preg_*, dựa trên thư viện PCRE (Perl Compatible Regular Expressions), nổi tiếng về hiệu suất và tính năng phong phú. Bài viết này sẽ giới thiệu bạn đến thế giới của Regex, tập trung vào ba hàm cốt lõi: preg_match() để kiểm tra sự khớp mẫu, preg_replace() để thay thế các phần khớp, và preg_split() để tách chuỗi dựa trên mẫu. Chúng ta sẽ tìm hình các khái niệm Regex cơ bản và đi sâu vào cách sử dụng từng hàm với các ví dụ thực tế, giúp bạn nắm vững công cụ mạnh mẽ này để xử lý chuỗi một cách hiệu quả và linh hoạt hơn trong các dự án PHP của mình.

Regex là gì?

Regex (viết tắt của Regular Expressions, hay còn gọi là Biểu thức chính quy) là một chuỗi các ký tự đặc biệt tạo thành một mẫu hình (pattern). Mẫu hình này được sử dụng để tìm kiếm, khớp, hoặc thao tác các chuỗi văn bản theo các quy tắc rất linh hoạt và mạnh mẽ.

Hãy tưởng tượng bạn có một cuốn sách lớn và muốn tìm tất cả các số điện thoại, hoặc tất cả các địa chỉ email, hoặc tất cả các từ bắt đầu bằng chữ "P" và kết thúc bằng "P" và có ít nhất 5 ký tự. Việc tìm kiếm thông thường bằng Ctrl+F sẽ rất khó khăn. Regex chính là công cụ giúp bạn định nghĩa chính xác những "mẫu" mà bạn muốn tìm trong khối văn bản đó.

Nó giống như một ngôn ngữ nhỏ dùng để mô tả các bộ quy tắc cho việc xử lý chuỗi. Các ký tự trong Regex có thể là:

  • Ký tự thông thường (literals): Khớp chính xác với chính nó (ví dụ: a, 1, hello).

  • Ký tự đặc biệt (metacharacters): Có ý nghĩa riêng và cho phép bạn tạo ra các mẫu phức tạp hơn (ví dụ: . khớp với bất kỳ ký tự nào, * khớp 0 hoặc nhiều lần).

Tại sao dùng Regex trong PHP?

Trong PHP, Regex trở thành một công cụ không thể thiếu khi bạn cần thực hiện các tác vụ xử lý chuỗi vượt ra ngoài khả năng của các hàm chuỗi cơ bản (str_replace, strpos, v.v.). Bạn nên dùng Regex khi:

Tìm kiếm mẫu phức tạp: Bạn cần kiểm tra xem một chuỗi có khớp với một cấu trúc cụ thể nào đó không (ví dụ: một địa chỉ IP hợp lệ, một số thẻ HTML, một ngày tháng theo định dạng DD/MM/YYYY).

  • Ví dụ: Kiểm tra xem mật khẩu có chứa ít nhất một chữ hoa, một chữ thường, một số và một ký tự đặc biệt hay không.

Trích xuất thông tin: Bạn muốn "bóc tách" các phần cụ thể từ một chuỗi dài mà các phần đó tuân theo một mẫu nhất định.

  • Ví dụ: Lấy tất cả các liên kết URL từ một đoạn văn bản HTML, hoặc trích xuất tên người dùng và tên miền từ một địa chỉ email.

Thay thế theo mẫu: Thay thế các chuỗi con không chỉ dựa trên giá trị chính xác mà còn dựa trên mẫu khớp.

  • Ví dụ: Thay thế tất cả các khoảng trắng liên tiếp bằng một khoảng trắng duy nhất, hoặc định dạng lại số điện thoại từ nhiều kiểu khác nhau về một kiểu thống nhất.

Tách chuỗi linh hoạt: Tách một chuỗi thành nhiều phần dựa trên một hoặc nhiều dấu phân cách phức tạp.

  • Ví dụ: Tách một câu thành các từ, nhưng bỏ qua các dấu câu, hoặc tách một đoạn văn thành các câu dựa trên dấu chấm, dấu hỏi, dấu chấm than.

Regex mang lại sự linh hoạthiệu quả vượt trội trong các tình huống này, giúp code của bạn gọn gàng và mạnh mẽ hơn rất nhiều.

Các hàm preg_* trong PHP: Sức mạnh PCRE

PHP cung cấp một bộ các hàm xử lý biểu thức chính quy với tiền tố preg_. Các hàm này sử dụng thư viện PCRE (Perl Compatible Regular Expressions), đây là một trong những thư viện Regex mạnh mẽ và được sử dụng rộng rãi nhất trên thế giới. PCRE nổi tiếng vì cú pháp phong phú và hiệu suất tốt, tương thích gần như hoàn toàn với cú pháp Regex của Perl.

Dưới đây là một ví dụ cơ bản minh họa cách Regex và các hàm preg_* hoạt động. Chúng ta sẽ sử dụng một trong những hàm phổ biến nhất: preg_match().

Ví dụ Code Cơ Bản: Kiểm tra định dạng Email đơn giản

<!DOCTYPE html>
<html lang="vi">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Ví dụ cơ bản về Regex và preg_match</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; background-color: #f4f7f6; color: #333; }
        .container { background-color: #ffffff; padding: 25px; border-radius: 10px; box-shadow: 0 4px 12px rgba(0,0,0,0.08); max-width: 700px; margin: 30px auto; border-left: 5px solid #28a745; }
        h1 { color: #28a745; text-align: center; margin-bottom: 25px; }
        h2 { color: #555; border-bottom: 1px solid #eee; padding-bottom: 10px; margin-top: 25px; }
        p { margin-bottom: 10px; line-height: 1.6; }
        strong { color: #007bff; }
        pre { background-color: #e9ecef; padding: 15px; border-radius: 8px; overflow-x: auto; margin-top: 15px; font-size: 0.95em; color: #333; border: 1px solid #dee2e6; }
        .success { color: #28a745; font-weight: bold; }
        .fail { color: #dc3545; font-weight: bold; }
        .info { background-color: #e0f7fa; border-left: 5px solid #00bcd4; padding: 15px; border-radius: 5px; margin-bottom: 20px; }
        code { background-color: #f0f0f0; padding: 2px 4px; border-radius: 3px; font-family: 'Courier New', Courier, monospace; }
    </style>
</head>
<body>

    <div class="container">
        <h1>Regex và `preg_match()`: Kiểm tra Email</h1>

        <div class="info">
            <p><strong>Khái niệm cơ bản:</strong></p>
            <ul>
                <li><strong>Regex (Biểu thức chính quy):</strong> Một chuỗi các ký tự tạo thành một mẫu để tìm kiếm, khớp hoặc thao tác chuỗi.</li>
                <li><strong>`preg_match()`:</strong> Hàm PHP dùng để kiểm tra xem một chuỗi có khớp với một mẫu Regex hay không.</li>
                <li><strong>PCRE:</strong> Các hàm <code>preg_*</code> trong PHP sử dụng cú pháp Regex tương thích với Perl.</li>
            </ul>
        </div>

        <h2>Ví dụ: Kiểm tra định dạng Email</h2>
        <p>Chúng ta sẽ sử dụng Regex để kiểm tra xem một chuỗi có phải là địa chỉ email hợp lệ theo một định dạng đơn giản hay không.</p>

        <h3>Mẫu Regex được sử dụng: <code>/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/</code></h3>
        <p>Giải thích mẫu Regex trên:</p>
        <ul>
            <li><code>^</code>: Khớp với **bắt đầu** của chuỗi.</li>
            <li><code>[a-zA-Z0-9._%+-]+</code>: Khớp **một hoặc nhiều** ký tự (chữ cái, số, dấu chấm, gạch dưới, phần trăm, cộng, hoặc trừ) cho phần tên người dùng.</li>
            <li><code>@</code>: Khớp chính xác ký tự **"@"**.</li>
            <li><code>[a-zA-Z0-9.-]+</code>: Khớp **một hoặc nhiều** ký tự (chữ cái, số, dấu chấm, hoặc dấu gạch ngang) cho phần tên miền.</li>
            <li><code>\.</code>: Khớp chính xác ký tự **"."** (dấu chấm cần được thoát `\` vì nó là ký tự đặc biệt trong Regex).</li>
            <li><code>[a-zA-Z]{2,}</code>: Khớp **hai hoặc nhiều hơn** ký tự chữ cái cho phần đuôi tên miền (ví dụ: `.com`, `.vn`, `.org`).</li>
            <li><code>$</code>: Khớp với **kết thúc** của chuỗi.</li>
            <li><code>/</code>: Dấu phân cách (delimiter) bắt buộc cho Regex trong PHP.</li>
        </ul>

        <?php
        // Định nghĩa mẫu Regex cho email
        $pattern = "/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/";

        // Các chuỗi email để kiểm tra
        $email1 = "[email protected]";
        $email2 = "[email protected]";
        $email3 = "invalid-email";
        $email4 = "[email protected]"; // Lỗi tên miền bắt đầu bằng dấu chấm
        $email5 = "another_test@domain"; // Thiếu phần đuôi .com

        echo "<h3>Kết quả kiểm tra:</h3>";

        echo "<p>Kiểm tra email: <strong><code>" . htmlspecialchars($email1) . "</code></strong></p>";
        if (preg_match($pattern, $email1)) {
            echo "<p class='success'>&rarr; **Hợp lệ!**</p>";
        } else {
            echo "<p class='fail'>&rarr; Không hợp lệ.</p>";
        }

        echo "<p>Kiểm tra email: <strong><code>" . htmlspecialchars($email2) . "</code></strong></p>";
        if (preg_match($pattern, $email2)) {
            echo "<p class='success'>&rarr; **Hợp lệ!**</p>";
        } else {
            echo "<p class='fail'>&rarr; Không hợp lệ.</p>";
        }

        echo "<p>Kiểm tra email: <strong><code>" . htmlspecialchars($email3) . "</code></strong></p>";
        if (preg_match($pattern, $email3)) {
            echo "<p class='success'>&rarr; **Hợp lệ!**</p>";
        } else {
            echo "<p class='fail'>&rarr; Không hợp lệ.</p>";
        }

        echo "<p>Kiểm tra email: <strong><code>" . htmlspecialchars($email4) . "</code></strong></p>";
        if (preg_match($pattern, $email4)) {
            echo "<p class='success'>&rarr; **Hợp lệ!**</p>";
        } else {
            echo "<p class='fail'>&rarr; Không hợp lệ.</p>";
        }
        
        echo "<p>Kiểm tra email: <strong><code>" . htmlspecialchars($email5) . "</code></strong></p>";
        if (preg_match($pattern, $email5)) {
            echo "<p class='success'>&rarr; **Hợp lệ!**</p>";
        } else {
            echo "<p class='fail'>&rarr; Không hợp lệ.</p>";
        }
        ?>
    </div>

</body>
</html>

Trong ví dụ trên, chúng ta thấy cách preg_match() sử dụng một mẫu Regex để xác định xem một chuỗi có tuân theo định dạng email mong muốn hay không. Các hàm preg_* khác như preg_replace()preg_split() cũng sẽ sử dụng các mẫu Regex tương tự để thực hiện các thao tác thay thế và tách chuỗi một cách linh hoạt.

Khái niệm cơ bản về Regex (Mẫu - Patterns) trong PHP

Để sử dụng Regex hiệu quả, bạn cần hiểu các thành phần cơ bản tạo nên một mẫu (pattern). Regex được xây dựng từ sự kết hợp của các ký tự thông thường và các ký tự đặc biệt có ý nghĩa riêng.

Ký tự chữ và số (Literals)

Đây là những ký tự đơn giản, khớp chính xác với chính nó trong chuỗi.

Ví dụ: Mẫu cat sẽ khớp với chuỗi "cat".

  • 123 sẽ khớp với chuỗi "123".

  • hello world sẽ khớp với "hello world".

Ký tự đặc biệt (Metacharacters)

Các ký tự này có ý nghĩa đặc biệt trong Regex, cho phép bạn định nghĩa các mẫu phức tạp hơn.

. (Dấu chấm): Khớp với bất kỳ ký tự nào (trừ ký tự xuống dòng \n).

  • Ví dụ: Mẫu /a.c/ sẽ khớp với "abc", "axc", "a&c", nhưng không khớp "ac" (thiếu ký tự ở giữa).

* (Dấu sao): Khớp không (0) hoặc nhiều lần của ký tự hoặc nhóm đứng ngay trước nó.

  • Ví dụ: Mẫu /ab*c/ sẽ khớp với "ac" (b không có lần nào), "abc", "abbc", "abbbc".

+ (Dấu cộng): Khớp một (1) hoặc nhiều lần của ký tự hoặc nhóm đứng ngay trước nó.

  • Ví dụ: Mẫu /ab+c/ sẽ khớp với "abc", "abbc", "abbbbc", nhưng không khớp "ac" (vì cần ít nhất một 'b').

? (Dấu hỏi chấm): Khớp không (0) hoặc một (1) lần của ký tự hoặc nhóm đứng ngay trước nó.

  • Ví dụ: Mẫu /colou?r/ sẽ khớp với "color" và "colour".

[] (Dấu ngoặc vuông - Character Set): Khớp với bất kỳ ký tự đơn lẻ nào nằm trong dấu ngoặc vuông.

Ví dụ:

  • Mẫu /[aeiou]/ sẽ khớp với bất kỳ nguyên âm nào.

  • Mẫu /[0-9]/ sẽ khớp với bất kỳ chữ số nào.

  • Mẫu /[a-zA-Z]/ sẽ khớp với bất kỳ chữ cái nào (hoa hoặc thường).

  • Bạn có thể dùng ^ ngay sau dấu mở ngoặc để phủ định (khớp bất kỳ ký tự nào không nằm trong nhóm). Ví dụ: [^0-9] sẽ khớp với bất kỳ ký tự nào không phải là chữ số.

() (Dấu ngoặc tròn - Grouping/Capturing):

Nhóm các biểu thức: Coi một chuỗi biểu thức như một đơn vị duy nhất để áp dụng các lượng từ (*, +, ?, {}).

  • Ví dụ: Mẫu /(ab)+/ sẽ khớp với "ab", "abab", "ababab". Nếu không có ngoặc, /ab+/ chỉ khớp "abbb".

Bắt nhóm (Capturing Group): Các phần của chuỗi khớp trong nhóm này có thể được trích xuất riêng biệt.

  • Ví dụ: Mẫu /(\d{4})-(\d{2})-(\d{2})/ có thể dùng để bắt riêng năm, tháng, ngày.

| (Dấu sổ đứng - OR): Hoạt động như một toán tử "HOẶC", khớp với một trong các mẫu được phân tách.

  • Ví dụ: Mẫu /cat|dog/ sẽ khớp với "cat" hoặc "dog".

^ (Dấu mũ - Anchor for Start): Khớp với bắt đầu của chuỗi.

  • Ví dụ: Mẫu /^Hello/ chỉ khớp nếu chuỗi bắt đầu bằng "Hello".

$ (Dấu đô la - Anchor for End): Khớp với kết thúc của chuỗi.

  • Ví dụ: Mẫu /World$/ chỉ khớp nếu chuỗi kết thúc bằng "World".

\ (Dấu gạch chéo ngược - Escape Character): Dùng để thoát (escape) các ký tự đặc biệt, biến chúng thành ký tự thông thường. Hoặc dùng để tạo các lớp ký tự đặc biệt (xem phần dưới).

Ví dụ:

  • Nếu bạn muốn khớp với ký tự dấu chấm . thật sự, bạn phải viết /\./.

  • Nếu muốn khớp với dấu \, bạn phải viết \\.

Lượng từ (Quantifiers)

Các ký tự này chỉ định số lần mà ký tự, nhóm hoặc lớp ký tự đứng trước nó phải xuất hiện.

{n}: Khớp chính xác n lần.

  • Ví dụ: Mẫu /\d{3}/ sẽ khớp với "123", "456" (khớp 3 chữ số).

{n,}: Khớp ít nhất n lần.

  • Ví dụ: Mẫu /\d{3,}/ sẽ khớp với "123", "1234", "12345" (khớp 3 chữ số trở lên).

{n,m}: Khớp từ n đến m lần.

  • Ví dụ: Mẫu /\w{5,8}/ sẽ khớp với một từ có độ dài từ 5 đến 8 ký tự chữ, số hoặc gạch dưới.

Lớp ký tự (Character Classes)

Đây là các ký tự đặc biệt viết tắt cho các nhóm ký tự phổ biến, thường được dùng với \.

\d: Khớp với một chữ số (tương đương [0-9]).

  • Ví dụ: Mẫu /\d+/ sẽ khớp với "123", "4567" (một hoặc nhiều chữ số).

\w: Khớp với một ký tự chữ, số hoặc dấu gạch dưới (tương đương [a-zA-Z0-9_]).

  • Ví dụ: Mẫu /\w{5}/ sẽ khớp với "hello", "word1" (chuỗi 5 ký tự chữ/số/_).

\s: Khớp với một ký tự khoảng trắng (space, tab \t, xuống dòng \n, carriage return \r, form feed \f, vertical tab \v).

  • Ví dụ: Mẫu /\s+/ sẽ khớp với một hoặc nhiều khoảng trắng.

Các lớp phủ định:

  • \D: Khớp với bất kỳ ký tự nào không phải là chữ số.

  • \W: Khớp với bất kỳ ký tự nào không phải là chữ, số hoặc dấu gạch dưới.

  • \S: Khớp với bất kỳ ký tự nào không phải là khoảng trắng.

Delimiters (Dấu phân cách)

Trong PHP, mẫu Regex phải được đặt trong các dấu phân cách. Các dấu phân cách phổ biến nhất là / (dấu gạch chéo), # (dấu thăng), hoặc ~ (dấu ngã). Bạn phải chọn một ký tự không xuất hiện trong chính mẫu Regex của bạn.

Ví dụ:

  • /pattern/

  • #pattern#

  • ~pattern~

Modifiers (Bộ sửa đổi - Flags)

Các bộ sửa đổi được thêm vào ngay sau dấu phân cách kết thúc của mẫu Regex để thay đổi hành vi tìm kiếm.

  • i (Case-Insensitive): Khiến việc so khớp không phân biệt chữ hoa/thường.

    • Ví dụ: Mẫu /apple/i sẽ khớp với "apple", "Apple", "APPLE", "ApPlE".

  • g (Global - Không áp dụng trực tiếp trong preg_match()):

    • Trong nhiều ngôn ngữ khác, g modifier có nghĩa là tìm kiếm tất cả các lần xuất hiện của mẫu trong chuỗi.

    • Trong PHP, preg_match() mặc định chỉ tìm lần khớp đầu tiên. Để tìm tất cả các lần khớp, bạn dùng preg_match_all().

    • Tuy nhiên, trong preg_replace(), việc thay thế tất cả các lần khớp là hành vi mặc định, không cần g.

  • m (Multiline): Khiến ^$ khớp với bắt đầu và kết thúc của mỗi dòng (tức là sau/trước ký tự xuống dòng \n), thay vì chỉ khớp với bắt đầu và kết thúc của toàn bộ chuỗi.

    • Ví dụ: Mẫu /^Start/m sẽ khớp với "Start" nếu nó xuất hiện ở đầu một dòng, ngay cả khi nó không phải là đầu của toàn bộ chuỗi.

Ví Dụ Code Tổng Hợp Các Khái Niệm Cơ Bản

Bài viết liên quan