Data teks dari dunia luar hampir tidak pernah datang dalam keadaan bersih: ada spasi ganda hasil salin-tempel, kapital yang acak, karakter tak terlihat di awal berkas CSV buatan Excel, bahkan dua penyandian berbeda untuk huruf beraksen yang tampak sama. Menyimpan dan membandingkan data mentah seperti itu berujung pada duplikasi terselubung serta pencarian yang gagal menemukan data yang sebenarnya ada. Normalisasi teks adalah proses mengubah input apa pun ke satu bentuk baku (canonical) sebelum data tersebut disimpan atau dibandingkan. Pada Chapter ini kita akan merangkai alat-alat yang telah kita kumpulkan — fungsi mb_*, preg_replace(), trim() — menjadi pipeline normalisasi yang utuh.

Menyeragamkan Huruf Besar-Kecil #

Chapter Unicode dan UTF-8 telah memperlihatkan bagaimana fungsi kapital level byte merusak teks non-ASCII. Kasusnya kerap muncul pada data nyata seperti nama kota:

php
<?php
$kota = "CAFÉ";

echo strtolower($kota) . "\n";
echo mb_strtolower($kota, "UTF-8") . "\n";

Output yang Dihasilkan:

text
cafÉ
café

strtolower() menurunkan byte C, A, F dengan baik, tetapi huruf É tersusun dari byte c3 89 yang tidak dikenali sebagai huruf besar ASCII sehingga dibiarkan — hasilnya cafÉ. mb_strtolower() mendekode string sebagai UTF-8 terlebih dahulu sehingga É ikut diturunkan menjadi é. Kebiasaan pengguna mengetik email dengan kapital campuran membuat penyamaan ini berguna untuk data login:

php
<?php
$email = "[email protected]";

var_dump(mb_strtolower($email, "UTF-8") === "[email protected]");

Output yang Dihasilkan:

text
bool(true)

Untuk kapitalisasi judul — nama orang, nama jalan, judul artikel — mb_convert_case() dengan mode MB_CASE_TITLE menaikkan huruf awal setiap kata sekaligus merapikan sisanya:

php
<?php
echo mb_convert_case("jalan suDIRman", MB_CASE_TITLE, "UTF-8") . "\n";

Output yang Dihasilkan:

text
Jalan Sudirman

Mode lain yang tersedia adalah MB_CASE_UPPER, MB_CASE_LOWER, dan MB_CASE_FOLD untuk perbandingan yang mengabaikan kapital secara ketat. Sebagai aturan praktis, simpan data dalam satu bentuk konsisten — biasanya huruf kecil untuk identifier seperti email dan username — lalu bentuk tampilan tertentu dibangun saat mencetak.

Merapikan Whitespace #

Spasi ganda, tab, dan baris baru biasanya tidak dibatasi pengguna saat mengisi formulir. Pola /\s+/ meruntuhkan seluruh jenis dan jumlah whitespace menjadi satu spasi:

php
<?php
$alamat = "  Jl. Merdeka \n\t No. 17 \n Bandung  ";

$satuSpasi = preg_replace('/\s+/', ' ', $alamat);

echo "[$satuSpasi]\n";
echo "[" . trim($satuSpasi) . "]\n";

Output yang Dihasilkan:

text
[ Jl. Merdeka No. 17 Bandung ]
[Jl. Merdeka No. 17 Bandung]

Langkah pertama mengganti tiap rangkaian whitespace — spasi, tab, baris baru — dengan satu spasi, tetapi menyisakan satu spasi di kedua tepi string. Langkah kedua, trim(), memangkas sisa tepi tersebut. Urutannya penting: memangkas sebelum meruntuhkan tidak akan menyentuh spasi ganda yang berada di tengah, dan hasil akhirnya tetap kotor.

Menghapus BOM dari Berkas #

Byte Order Mark (BOM) adalah tiga byte EF BB BF yang dituliskan beberapa editor Windows dan Microsoft Excel di awal berkas UTF-8. Pada UTF-8 penanda tersebut tidak diperlukan, dan efeknya nyata: kolom pertama CSV seolah-olah mengandung karakter tak terlihat yang membuat perbandingan gagal:

php
<?php
$barisCsv = "\xEF\xBB\xBFBudi,[email protected]";
$bersih = preg_replace('/^\x{FEFF}/u', '', $barisCsv);

$namaKotor = explode(",", $barisCsv)[0];
$namaBersih = explode(",", $bersih)[0];

echo "[$namaKotor] vs [$namaBersih]\n";
echo bin2hex($namaKotor) . "\n";
echo bin2hex($namaBersih) . "\n";

Output yang Dihasilkan:

text
[Budi] vs [Budi]
efbbbf42756469
42756469

Baris pertama kelihatannya mencetak dua kata Budi yang sama persis — itulah inti masalahnya, karena BOM memang tidak terlihat. Baris heksadesimal membongkarnya: nama yang belum dibersihkan diawali byte ef bb bf, sedangkan yang bersih langsung dimulai huruf B (42). Pola '/^\x{FEFF}/u' menargetkan code point U+FEFF di awal string; dengan modifier u, penulisan \x{FEFF} dibaca sebagai code point, bukan byte tunggal.

NFC dan NFD: Satu Huruf, Dua Bentuk #

Huruf beraksen punya dua cara penulisan di Unicode: satu code point utuh é (U+00E9), atau gabungan huruf e dengan tanda aksen mengambang (U+0301). Dua bentuk tersebut tampak identik di layar tetapi berbeda sebagai data:

php
<?php
$nfc = "Jos\u{00E9}";
$nfd = "Jose\u{0301}";

echo $nfc . " / " . $nfd . "\n";
var_dump($nfc === $nfd);
var_dump(mb_strlen($nfc), mb_strlen($nfd));

Output yang Dihasilkan:

text
José / José
bool(false)
int(4)
int(5)

Bentuk pertama (NFC, precomposed) terdiri dari empat code point, sedangkan bentuk kedua (NFD, decomposed) terdiri dari lima. Karena byte-nya berbeda, perbandingan === mengembalikan false meski mata pengguna melihat dua José yang sama. Teks bentuk NFD biasa muncul dari nama berkas di macOS atau hasil ketikan metode input tertentu. Konversi antar bentuk ini disebut normalisasi Unicode, dan fungsinya adalah normalizer_normalize() dari ekstensi intl:

php
<?php
// Membutuhkan ekstensi intl; tidak tersedia di lingkungan kursus ini
$nfd = "Jose\u{0301}";
$kanonik = normalizer_normalize($nfd, NORMALIZER_FORM_C);

echo bin2hex($kanonik);

Konstanta NORMALIZER_FORM_C meminta bentuk NFC. Setelah konversi tersebut, byte $kanonik menjadi identik dengan Jos\u{00E9} sehingga perbandingan === antar keduanya berhasil. Mayoritas sistem Linux dan Windows menghasilkan teks NFC secara default, jadi bentuk ini dijadikan target baku.

Menyeragamkan Sebelum Membandingkan #

Seluruh teknik di atas menemukan gunanya saat digabungkan. Bandingkan input formulir yang kotor dengan data tersimpan yang bersih:

php
<?php
$inputForm = "  BUDI   Santoso ";
$inputDb = "budi santoso";

$kanonik = mb_strtolower(trim(preg_replace('/\s+/', ' ', $inputForm)), "UTF-8");

var_dump($kanonik === $inputDb);

Output yang Dihasilkan:

text
bool(true)

Mari kita baca pipeline dari dalam ke luar: preg_replace('/\s+/', ' ', $inputForm) meruntuhkan spasi ganda menjadi satu, trim() memangkas tepi, dan mb_strtolower() meratakan kapital. Tanpa pipeline ini, " BUDI Santoso " dan "budi santoso" adalah dua data yang berbeda padahal merujuk orang yang sama. Terapkan normalisasi di batas sistem — begitu data masuk, sebelum disimpan atau dibandingkan — lalu bungkus rangkaian ini sebagai satu fungsi normalisasiNama() setelah kita menguasai Section Fungsi. Kasus pemakaian nyatanya meluas: mencegah duplikat kontak, mencocokkan kredensial login, sampai membangun fitur pencarian yang toleran terhadap kekacauan pengetikan.

Ringkasan #

Normalisasi mengubah teks dari berbagai keadaan kotor menuju satu bentuk baku: mb_strtolower() dan mb_convert_case() menyamakan kapital, pipeline preg_replace('/\s+/', ' ', ...) plus trim() merapikan whitespace, pola '/^\x{FEFF}/u' membuang BOM, dan normalisasi Unicode menyatukan bentuk NFC serta NFD. Data yang baku membuat perbandingan menjadi andal dan duplikasi mudah dideteksi. Namun semua pipeline ini berjalan lurus dari atas ke bawah — program yang sesungguhnya perlu mengambil keputusan di tengah jalan. Saatnya memasuki Section Control Flow, dimulai dari struktur if-else.

Referensi #