7-3: Regular Expression
Objectives
- Dasar-dasar regex
- preg_match dan preg_replace
- Pattern matching
- Mengenal capture groups
- Menghindari regex yang berbahaya (ReDoS)
Apa Itu Regular Expression?
Regular expression (regex) adalah pola (pattern) untuk mencari teks dengan aturan tertentu. Alih-alih menulis banyak strpos dan substr untuk memeriksa format, satu regex bisa menangani semuanya sekaligus.
Contoh sederhana: memvalidasi apakah sebuah string adalah email. Dengan function string biasa:
<?php
$email = "user@example.com";
// Cara manual: harus ada @, ada titik setelah @, dst.
if (
str_contains($email, "@") &&
str_contains($email, ".") &&
strpos($email, "@") > 0 &&
strpos($email, ".") > strpos($email, "@")
) {
echo "Kira-kira email valid";
}
// Dengan regex: satu pola, sekali cek
if (preg_match("/^[\w.+-]+@[\w-]+\.[a-zA-Z]{2,}$/", $email)) {
echo "Email valid";
}
?>
┌─────────────────────────────────────────────────────┐
│ PATTERN REGEX │
│ │
│ / ^ [a-z]+ @ [a-z]+ \. [a-z]{2,} $ / │
│ │ │ │ │ │ │ │ │
│ awal huruf @ huruf . huruf akhir │
│ │
│ "andi@mail.com" -> cocok ✅ │
│ "andi@@mail" -> tidak cocok ❌ │
└─────────────────────────────────────────────────────┘
Syntax Dasar Regex
Di PHP, pola regex ditulis di antara dua delimiter — biasanya garis miring /.../ — diikuti modifier opsional seperti i (case-insensitive).
<?php
$pattern = "/hello/i"; // Case-insensitive
$text = "Hello World";
if (preg_match($pattern, $text)) {
echo "Found!";
}
// Metacharacters
// . - Any character
// \d - Digit (0-9)
// \w - Word character (a-z, A-Z, 0-9, _)
// \s - Whitespace
// Quantifiers
// * - 0 or more
// + - 1 or more
// ? - 0 or 1
// {n} - Exactly n
// {n,} - n or more
// {n,m} - Between n and m
?>
Karakter Khusus (Metacharacters)
| Pola | Arti | Cocok dengan |
|---|---|---|
. | Karakter apa pun | a, 5, _, spasi (bukan newline) |
\d | Digit | 0 sampai 9 |
\w | Word character | a-z, A-Z, 0-9, _ |
\s | Whitespace | spasi, tab, newline |
^ | Awal string | posisi awal |
$ | Akhir string | posisi akhir |
[abc] | Salah satu karakter | a, b, atau c |
[a-z] | Rentang | huruf kecil a sampai z |
[^abc] | Negasi | bukan a, b, c |
\b | Word boundary | batas kata |
Quantifiers (Jumlah Kemunculan)
| Quantifier | Arti | Contoh |
|---|---|---|
* | 0 atau lebih | a* cocok "", "a", "aaa" |
+ | 1 atau lebih | a+ cocok "a", "aaa" (bukan "") |
? | 0 atau 1 | a? cocok "", "a" |
{n} | Tepat n kali | \d{4} cocok "1234" |
{n,} | Minimal n kali | \d{2,} cocok "12", "12345" |
{n,m} | Antara n dan m | \d{2,4} cocok "12", "1234" |
<?php
$pola = "/\d{4}-\d{4}-\d{4}/"; // 4 digit - 4 digit - 4 digit
$teks = "Hubungi 0812-3456-7890 ya!";
if (preg_match($pola, $teks)) {
echo "Nomor ketemu"; // Output: Nomor ketemu
}
?>
^ dan $ sangat penting untuk validasi penuh. Tanpa keduanya, preg_match("/\d{4}/", "abc12345") tetap true karena "1234" ada di tengah string. Dengan ^...$, seluruh string harus cocok.
preg_match()
<?php
$email = "user@example.com";
// Validate email pattern
$pattern = "/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/";
if (preg_match($pattern, $email)) {
echo "Valid email!";
}
// Get matches
$text = "Call 0812-3456-7890";
$pattern = "/\d{4}-\d{4}-\d{4}/";
preg_match($pattern, $text, $matches);
print_r($matches); // [0812-3456-7890]
// All matches
preg_match_all($pattern, $text, $all);
print_r($all[0]);
?>
Perbedaan preg_match vs preg_match_all:
| Fungsi | Perilaku | Kapan |
|---|---|---|
preg_match | Berhenti di kecocokan pertama | Cek validasi |
preg_match_all | Kumpulkan semua kecocokan | Ekstraksi data |
Capture Groups (Kurung)
Bagian pola yang dikurung (...) menjadi capture group — bisa diambil terpisah dari hasil:
<?php
$tanggal = "Lahir: 12-08-2026";
// Tangkap hari, bulan, tahun secara terpisah
$pola = "/(\d{2})-(\d{2})-(\d{4})/";
preg_match($pola, $tanggal, $m);
echo $m[0]; // 12-08-2026 (match penuh)
echo $m[1]; // 12 (hari)
echo $m[2]; // 08 (bulan)
echo $m[3]; // 2026 (tahun)
// Named groups (PHP 7.2+): beri nama, lebih jelas
$pola = "/(?<hari>\d{2})-(?<bulan>\d{2})-(?<tahun>\d{4})/";
preg_match($pola, $tanggal, $m2);
echo $m2["hari"]; // 12
echo $m2["tahun"]; // 2026
?>
Gunakan named groups (?<nama>...) untuk pola yang kompleks — hasilnya jauh lebih mudah dibaca daripada $m[1], $m[2], $m[3].
preg_match_all untuk Ekstraksi
<?php
$artikel = "Kontak: a@mail.com atau b@mail.com atau c@mail.com";
$pola = "/[\w.+-]+@[\w-]+\.[a-zA-Z]{2,}/";
preg_match_all($pola, $artikel, $email_terambil);
print_r($email_terambil[0]);
// Output:
// Array
// (
// [0] => a@mail.com
// [1] => b@mail.com
// [2] => c@mail.com
// )
echo "Ditemukan " . count($email_terambil[0]) . " email";
// Output: Ditemukan 3 email
?>
preg_replace()
<?php
$text = "Hello World";
// Replace
$new = preg_replace("/World/", "PHP", $text);
echo $new; // Hello PHP
// Replace with regex
$phone = "0812-3456-7890";
$clean = preg_replace("/\D/", "", $phone); // Hapus non-digit
echo $clean; // 081234567890
// Callback replace
$numbers = [1, 2, 3, 4, 5];
$result = preg_replace_callback("/\d/", function($match) {
return $match[0] * 2;
}, $numbers);
print_r($result);
// Output: [2, 4, 6, 8, 10]
?>
Backreference di Penggantian
$1, $2, dst. merujuk ke capture group dari pola — memungkinkan "menulis ulang" format:
<?php
$tanggal_amerika = "2026-08-12";
// Ubah format YYYY-MM-DD menjadi DD/MM/YYYY
$pola = "/(\d{4})-(\d{2})-(\d{2})/";
$hasil = preg_replace($pola, "$3/$2/$1", $tanggal_amerika);
echo $hasil; // 12/08/2026
// Contoh lain: sensor kata kasar (case-insensitive)
$komentar = "Kamu BODOH banget!";
$bersih = preg_replace("/bodoh/i", "***", $komentar);
echo $bersih; // Kamu *** banget!
?>
Dalam string double quotes, $1 bisa salah diinterpretasi sebagai variabel PHP. Tulis pola pengganti dalam single quotes ('$3/$2/$1') agar aman.
preg_split — Memecah String dengan Pola
Saudara explode, tapi dengan regex:
<?php
// Pecah berdasarkan satu atau lebih spasi/koma
$teks = "apel, pisang jeruk, mangga";
$buah = preg_split("/[\s,]+/", $teks);
print_r($buah);
// Output: ["apel", "pisang", "jeruk", "mangga"]
// Dengan explode biasa, spasi ganda jadi elemen kosong:
$buah2 = explode(" ", $teks);
print_r($buah2);
// Output: ["apel,", "pisang", "", "jeruk,", "", "", "mangga"]
?>
Common Patterns
<?php
// Phone number (Indonesian)
$phone = "/^08[1-9]\d{7,10}$/";
// URL
$url = "/https?:\/\/[\w\-.]+(\/[\w\-.?\/%&=]*)?/";
// Username (alphanumeric, 3-16 chars)
$username = "/^[a-zA-Z0-9_]{3,16}$/";
// Password (min 8 chars, 1 uppercase, 1 lowercase, 1 number)
$password = "/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/";
// Contoh penggunaan
$tests = [
"081234567890" => $phone,
"081234" => $phone, // terlalu pendek
"andi_123" => $username,
"a!" => $username, // terlalu pendek & simbol
];
foreach ($tests as $nilai => $pola) {
echo "$nilai => " . (preg_match($pola, $nilai) ? "valid" : "tidak valid") . "\n";
}
?>
Penjelasan pola password ^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$:
| Bagian | Arti |
|---|---|
(?=.*[a-z]) | Lookahead: di suatu tempat ada huruf kecil |
(?=.*[A-Z]) | Lookahead: ada huruf besar |
(?=.*\d) | Lookahead: ada digit |
.{8,} | Total minimal 8 karakter apa pun |
^...$ | Harus mencakup seluruh string |
preg_quote — Meng-escape Otomatis
Saat user mengetik kata kunci pencarian yang kamu jadikan regex, karakter khusus di inputnya bisa merusak pola atau malah jadi "regex injection". Gunakan preg_quote:
<?php
$kata_kunci = "harga (promo)?"; // input user, penuh karakter regex
// ❌ Berbahaya: ( ) ? punya arti khusus di regex
// $pola = "/" . $kata_kunci . "/i";
// ✅ Aman: karakter khusus di-escape
$pola = "/" . preg_quote($kata_kunci, "/") . "/i";
echo $pola; // /harga\ \(promo\)\?/i
if (preg_match($pola, "Promo hari ini: harga (promo)? serbu!")) {
echo "Ketemu!"; // Output: Ketemu!
}
?>
Selalu preg_quote input user yang disisipkan ke pola regex — kalau tidak, karakter seperti (, ), [, * bisa mengubah arti pola dan menimbulkan bug atau bahkan serangan ReDoS.
Common Mistakes
Mistake 1: Lupa anchor ^ dan $ saat validasi
<?php
$kode = "AB12";
// ❌ Tanpa anchor: "XAB12Y" juga lolos!
if (preg_match("/[A-Z]{2}\d{2}/", "XAB12Y")) {
echo "lolos"; // ❌ padahal format salah
}
// ✅ Dengan anchor: seluruh string harus cocok
if (preg_match("/^[A-Z]{2}\d{2}$/", "XAB12Y")) {
echo "lolos";
} else {
echo "ditolak"; // Output: ditolak
}
?>
Mistake 2: Tidak mengecek hasil preg_*
<?php
// preg_match mengembalikan:
// 1 = cocok
// 0 = tidak cocok
// false = ERROR (pola tidak valid)
$pola = "/[a-z/"; // kurung tidak ditutup!
$hasil = preg_match($pola, "abc");
var_dump($hasil); // Output: bool(false) — error, bukan "tidak cocok"!
// Bandingkan dengan cermat kalau perlu:
if ($hasil === false) {
echo "Pola regex error: " . preg_last_error_msg();
}
?>
Jika preg_match tiba-tiba selalu false padahal polanya benar, cek error dengan preg_last_error() / preg_last_error_msg(). Pola yang tidak valid menghasilkan false, bukan 0.
Mistake 3: Regex katastropik (ReDoS)
<?php
// ❌ BERBAHAYA: nested quantifier (a+)* bisa meledak
// pada input panjang yang tidak cocok
// $pola = "/(a+)+$/";
// ✅ Hindari pola dengan quantifier bersarang seperti
// (a+)+, (a*)*, (a|a)+ pada input yang tidak terkendali.
// Jika harus, tambahkan batasan: (a{1,100})+
?>
ReDoS (Regular expression Denial of Service): pola seperti (a+)+ atau (a|a)* bisa membuat CPU bekerja eksponensial pada input panjang yang tidak cocok. Jangan pernah meng-compile regex dari input user, dan hindari nested quantifier.
Mistake 4: Lupa delimiter saat pola mengandung /
<?php
// ❌ Error: delimiter "/" bertabrakan dengan slash di pola
// $pola = "/https?:\/\/.../" -> perlu escape atau ganti delimiter
// ✅ Ganti delimiter dengan # atau ~
$pola = "#https?://[\w.\-/]+#";
if (preg_match($pola, "https://marikoding.com")) {
echo "URL valid"; // Output: URL valid
}
?>
Pro Tips
- Mulai dari pola sederhana lalu tambah kompleksitas bertahap — jangan tulis pola raksasa sekali jadi.
- Gunakan delimiter selain
/(#,~) saat pola berisi banyak slash (URL, path). - Test pola di regex101.com atau sejenisnya sebelum dipakai di kode.
- Named groups
(?<nama>...)untuk pola kompleks. preg_quotesemua input user yang masuk ke pola.- Regex itu senjata terakhir — untuk kasus sederhana,
str_contains/str_replacelebih cepat dan lebih mudah dibaca.
Exercise
- Validasi format tanggal DD/MM/YYYY
- Extract semua email dari text
- Replace semua SPACES menjadi dash
- Tulis pola yang cocok dengan username: huruf/angka/underscore, 3-16 karakter, diawali huruf (bukan angka).
- Gunakan
preg_replacedengan backreference untuk mengubah"John Doe"menjadi"Doe, John". - Validasi password dengan
preg_matchdan tampilkan pesan error spesifik: terlalu pendek, kurang huruf besar, kurang angka.
Summary
| Konsep | Poin Kunci |
|---|---|
| Delimiter | Pola diapit /.../ atau #...#, modifier i = case-insensitive |
| Metachar | . \d \w \s ^ $ [ ] |
| Quantifier | * + ? {n} {n,} {n,m} |
preg_match | Cek kecocokan pertama (validasi) |
preg_match_all | Ambil semua kecocokan (ekstraksi) |
preg_replace | Ganti dengan pola + backreference $1 |
preg_split | Pecah string dengan pola |
| Capture group | (...) dan (?<nama>...) |
| Keamanan | preg_quote input user, hindari nested quantifier |
Klik tombol ini setelah menyelesaikan materi
Quiz Bab 7
Uji pemahamanmu tentang String Manipulation