Fungsi pencarian pada Chapter Searching dalam String bekerja dengan teks literal: kita menyebut persis kata yang dicari, misalnya "PHP" atau "2026". Banyak kebutuhan nyata justru berbicara tentang bentuk, bukan isi persis: mengambil semua tanggal dari sebuah paragraf, memastikan input berbentuk slug yang sah, atau mengubah judul artikel menjadi URL yang rapi. Regular expression (regex) adalah bahasa mini untuk mendeskripsikan bentuk teks tersebut, dan PHP menjalankannya melalui keluarga fungsi preg_* di atas engine PCRE2. Pada Chapter ini kita akan mempelajari anatomi pola, cara menangkap bagian yang cocok, mengganti teks berdasarkan pola, sampai mengenali kegagalan yang tidak disertai pesan error.

Anatomi Pola: Delimiter dan Flag #

Pola regex dalam PHP ditulis sebagai string biasa yang dibungkus delimiter — umumnya garis miring / — dan dapat diakhiri flag. Semua yang berada di antara delimiter adalah polanya, sedangkan huruf setelah delimiter penutup adalah modifier yang mengubah perilaku pencocokan:

php
<?php
var_dump(preg_match("/php/i", "Belajar PHP Dasar"));
var_dump(preg_match("#^/api/v\d+$#", "/api/v2"));

Output yang Dihasilkan:

text
int(1)
int(1)

Pemanggilan pertama memakai flag i sehingga pencocokan tidak peduli besar-kecil huruf: pola php cocok dengan PHP. Pemanggilan kedua memakai delimiter # karena polanya sendiri penuh garis miring — dengan delimiter /, tiap / dalam pola harus ditulis \/ dan pola menjadi sulit dibaca. Tanda ^ dan $ menahan pencocokan agar mencakup seluruh string, \d berarti satu digit, dan + berarti “satu atau lebih”. Tabel berikut merangkum bahan bangun yang paling sering dipakai:

ElemenArtiContoh pemakaian
\dsatu digit 0-9\d{4} tepat empat digit
\whuruf, digit, atau _\w+ satu kata
\sspasi, tab, baris baru\s+ rangkaian whitespace
[...]satu karakter dari kumpulan[a-z0-9]
[^...]satu karakter di luar kumpulan[^a-z]
.satu karakter apa puna.c
+ * ?1+ / 0+ / 0-1 kali\d*
{2,4}berulang 2 sampai 4 kali\d{2,4}
^ $awal dan akhir string^\d+$
(...)capture group(\d{4})

preg_match dan Tangkapan Grup #

preg_match() mencari pola pertama yang cocok, mengembalikan 1 jika ketemu dan 0 jika tidak, lalu mengisi variabel parameter ketiga dengan hasil tangkapan:

php
<?php
$teks = "Rilis versi 3.2 dirilis pada 2026-01-15.";
preg_match("/(\d{4})-(\d{2})-(\d{2})/", $teks, $matches);

var_export($matches);

Output yang Dihasilkan:

text
array (
  0 => '2026-01-15',
  1 => '2026',
  2 => '01',
  3 => '15',
)

Indeks 0 berisi keseluruhan teks yang cocok (2026-01-15), sedangkan indeks 1, 2, dan 3 berisi isi tiap tanda kurung dari kiri ke kanan: tahun, bulan, tanggal. Pola \d{4}-\d{2}-\d{2} menggambarkan bentuk tanggal, dan tanda kurung memungkinkan kita mengambil komponennya satu per satu. Variabel $matches adalah array — pembahasan array yang mendalam baru datang di Section Arrays, jadi untuk sekarang cukup pahami bahwa preg_match() mengisi variabel tersebut dan kita membacanya lewat var_export() atau indeks seperti $matches[1].

Nilai kembalian 1 dan 0 layak diperhatikan lebih dekat:

php
<?php
$tanggal = "2026-01-15";
$sapaan = "Selamat pagi";

var_dump(preg_match("/^\d{4}-\d{2}-\d{2}$/", $tanggal));
var_dump(preg_match("/^\d{4}-\d{2}-\d{2}$/", $sapaan));

Output yang Dihasilkan:

text
int(1)
int(0)

Angka ini akan menjadi kondisi pada struktur if di Section Control Flow: “jika preg_match() mengembalikan 1, input dianggap valid”. Grup juga bisa diberi nama agar kode lebih terbaca:

php
<?php
preg_match("/(?<tahun>\d{4})-(?<bulan>\d{2})-(?<hari>\d{2})/", "2026-01-15", $m);

echo $m["tahun"] . "\n";
echo $m["bulan"] . "\n";
echo $m["hari"] . "\n";

Output yang Dihasilkan:

text
2026
01
15

Sintaks (?<nama>...) menamai grup, sehingga $m["tahun"] lebih jelas maknanya daripada $m[1]. Penamaan juga membuat kode tetap benar ketika Anda menata ulang urutan grup di kemudian hari.

preg_match_all dan Urutan Hasilnya #

preg_match_all() mengumpulkan semua kejadian yang cocok, bukan hanya yang pertama. Mari kita ambil rekap nilai tiga siswa sekaligus:

php
<?php
$rekap = "Budi: 85, Siti: 92, Rio: 78";
preg_match_all("/(\w+): (\d+)/", $rekap, $semua);

var_export($semua);

Output yang Dihasilkan:

text
array (
  0 =>
  array (
    0 => 'Budi: 85',
    1 => 'Siti: 92',
    2 => 'Rio: 78',
  ),
  1 =>
  array (
    0 => 'Budi',
    1 => 'Siti',
    2 => 'Rio',
  ),
  2 =>
  array (
    0 => '85',
    1 => '92',
    2 => '78',
  ),
)

Perhatikan struktur hasilnya: kunci 0 berisi tiga teks lengkap yang cocok, kunci 1 berisi daftar semua nama, dan kunci 2 berisi daftar semua angka. Hasil dikelompokkan per grup — ibarat kolom — bukan per kejadian seperti baris tabel. Pemula sering mengira $semua[1] berisi kejadian kedua; padahal elemen itu adalah daftar seluruh tangkapan grup pertama. Struktur kolom ini justru berguna: satu kunci sudah memuat seluruh nama untuk ditampilkan atau diolah lebih lanjut.

preg_replace dengan Capture Group #

preg_replace() mengganti setiap bagian yang cocok dengan pola menjadi teks pengganti. Kombinasi dengan character class negatif menghasilkan generator slug yang realistis:

php
<?php
$judul = "  Belajar PHP Dasar & Praktis!  ";

$slug = strtolower(preg_replace("/[^a-z0-9]+/i", "-", trim($judul)));
$slug = trim($slug, "-");

echo $slug . "\n";

Output yang Dihasilkan:

text
belajar-php-dasar-praktis

Mari kita bedah dari dalam ke luar. trim($judul) membuang spasi tepi; pola [^a-z0-9]+ mencocokkan rangkaian karakter apa pun yang bukan huruf atau digit — termasuk " & " dan "!" — lalu tiap rangkaian diganti satu tanda hubung; strtolower() meratakan kapital; trim($slug, "-") membuang tanda hubung yang menempel di tepi. Hasilnya belajar-php-dasar-praktis, siap dipakai sebagai URL. Teks pengganti boleh merujuk kembali ke grup yang ditangkap memakai $1, $2, dan seterusnya:

php
<?php
$namaFormal = "Santoso, Budi";

echo preg_replace("/^(\w+), (\w+)$/", "$2 $1", $namaFormal) . "\n";

Output yang Dihasilkan:

text
Budi Santoso

Grup pertama menangkap Santoso dan grup kedua menangkap Budi, lalu teks pengganti "$2 $1" menyusunnya ulang menjadi Budi Santoso. Saat referensi berdempetan dengan karakter lain, tulis bentuk panjangnya seperti ${1}0 agar PHP tidak mengiranya grup kesepuluh.

preg_split: Memecah dengan Pola #

explode() hanya mampu memecah berdasarkan satu pemisah tetap, sebagaimana dibahas pada Chapter Splitting dan Joining . preg_split() menerima pola, sehingga kelas pemisah campuran bisa ditangani sekaligus:

php
<?php
$tag = " php, web; backend";

var_export(preg_split("/[\s,;]+/", $tag));
echo "\n";
var_export(preg_split("/[\s,;]+/", trim($tag)));

Output yang Dihasilkan:

text
array (
  0 => '',
  1 => 'php',
  2 => 'web',
  3 => 'backend',
)
array (
  0 => 'php',
  1 => 'web',
  2 => 'backend',
)

Pola [\s,;]+ mencocokkan rangkaian spasi, koma, atau titik koma berapa pun panjangnya. Karena string $tag diawali spasi, pemanggilan pertama menghasilkan string kosong sebagai elemen pertama — jebakan yang klasik. Memanggil trim() sebelum memecah, seperti pada pemanggilan kedua, membersihkannya.

Modifier u untuk Teks UTF-8 #

Chapter Unicode dan UTF-8 menegaskan bahwa satu karakter bisa terdiri dari beberapa byte. Tanpa flag u, PCRE memeriksa byte demi byte; dengan flag u, pola dan subjek didekode sebagai UTF-8:

php
<?php
var_dump(preg_match("/^\p{L}+$/", "Café"));
var_dump(preg_match("/^\p{L}+$/u", "Café"));

Output yang Dihasilkan:

text
int(0)
int(1)

Tanpa u, huruf é tersusun dari byte c3 a9 dan byte a9 tidak dianggap huruf, sehingga pola gagal. Dengan u, escape \p{L} — kategori huruf Unicode — mengenali é sebagai satu huruf dan pencocokan berhasil. Padanannya untuk digit adalah \p{Nd}.

Kegagalan Senyap dan preg_last_error #

Fungsi preg_* punya satu kebiasaan yang mengejutkan: saat gagal, fungsi tersebut mengembalikan false tanpa mencetak pesan error apa pun. Modifier u memberi contoh yang mudah direproduksi — subjek dengan byte UTF-8 rusak:

php
<?php
$hasil = preg_match("/^\p{L}+$/u", "caf\xC3");

var_dump($hasil);
var_dump(preg_last_error() === PREG_BAD_UTF8_ERROR);

Output yang Dihasilkan:

text
bool(false)
bool(true)

Byte \xC3 adalah setengah dari huruf é yang terpotong, sehingga PCRE2 menolak seluruh subjek dan preg_match() mengembalikan false — nilai yang mudah dikira 0 berarti “tidak ada yang cocok”. preg_last_error() membedah kejadiannya: fungsi tersebut mengembalikan konstanta seperti PREG_NO_ERROR, PREG_BAD_UTF8_ERROR, atau PREG_BACKTRACK_LIMIT_ERROR.

Ringkasan #

Regex mengubah pencarian dari “teks persis ini” menjadi “teks berbentuk begini”: delimiter membungkus pola, flag i dan u mengatur perilaku, preg_match() menangkap grup lewat $matches, preg_match_all() menyusun hasil per kolom grup, preg_replace() mengubah teks dengan referensi $1, dan preg_split() memecah dengan pola. Kegagalan yang senyap diantisipasi dengan preg_last_error(). Dengan pola sebagai alat potong presisi, kita siap merangkai seluruh teknik pembersihan teks menjadi satu alur baku pada Chapter Text Normalization.

Referensi #