Di antara rangkaian fitur PHP 8.0, match lahir dari keluhan yang paling tua: switch pewaris warisan bahasa C yang rawan bug fallthrough — satu break yang terlupakan dan program langsung salah jalan. Fitur ini juga membalik cara pandang: match bukan struktur kontrol, melainkan ekspresi yang menghasilkan nilai, sehingga bisa diisikan ke variabel, dikembalikan dari function, bahkan disisipkan ke argument. Pada chapter ini kita membahas match dari sudut evolusi: masalah lama apa yang diselesaikannya dan bagaimana kode switch lama ditulis ulang. Pembahasan sintaks lengkapnya — aturan arm, penggunaan lanjutan, dan pola match bersarang — sudah kita bedah tuntas pada chapter match expression di Section Control Flow
.
Bug Fallthrough pada switch #
Sebelum PHP 8.0, satu-satunya alat bawaan untuk percabangan berbasis nilai adalah switch. Desainnya mewarisi C: eksekusi berpindah ke case yang cocok, lalu terus jatuh menembus (fallthrough) ke case berikutnya kecuali dihentikan break. Perhatikan kode lama berikut, lalu perhatikan baik-baik komentar di tengahnya:
<?php
$kode = 'A';
$kategori = '';
switch ($kode) {
case 'A':
$kategori = 'Pelanggan VIP';
// `break` yang terlupakan
case 'B':
$kategori = 'Pelanggan reguler';
break;
default:
$kategori = 'Tamu';
}
echo "Kategori: $kategori\n";Output yang Dihasilkan:
Kategori: Pelanggan regulerInput-nya 'A', niatnya jelas Pelanggan VIP, tetapi output justru Pelanggan reguler. Inilah bug fallthrough: tanpa break, interpreter mengeksekusi case 'A', menetapkan 'Pelanggan VIP', lalu tanpa protes lanjut mengeksekusi isi case 'B' dan menimpa nilainya. Yang membuat bug semacam ini awet bertahan adalah sifatnya yang senyap — tidak ada error, tidak ada warning, program tetap berjalan, hanya hasilnya yang salah. Selama dua puluh tahun lebih, satu-satunya pertahanan kita adalah disiplin menulis break di setiap case.
Menulis Ulang dengan match #
PHP 8.0 memotong akar masalahnya: pada match, setiap arm (lengan kondisi) berdiri sendiri dan eksekusi tidak pernah menembus ke arm berikutnya. Kode yang sama kini ditulis begini:
<?php
$kode = 'A';
$kategori = match ($kode) {
'A' => 'Pelanggan VIP',
'B' => 'Pelanggan reguler',
default => 'Tamu',
};
echo "Kategori: $kategori\n";Output yang Dihasilkan:
Kategori: Pelanggan VIPTidak ada break untuk dilupakan karena tidak dibutuhkan — pemisah antar arm adalah koma, dan tepat satu arm yang dieksekusi. Perhatikan juga arah penulisannya berbalik: match menghasilkan nilai yang langsung diisikan ke $kategori, bukan memerintahkan interpreter mengubah variabel di dalam blok. Ini pergeseran dari command (perintah) ke expression (ekspresi) — konsekuensinya kita bahas sebentar lagi.
Perbandingan Longgar versus Ketat #
Perbedaan kedua yang sama pentingnya: switch membandingkan dengan operator longgar ==, sedangkan match membandingkan dengan operator ketat ===. Dua baris kode berikut menerima input yang sama dan menghasilkan kesimpulan berbeda:
<?php
$nilai = '1';
switch ($nilai) {
case 1:
$hasilSwitch = 'dianggap sama dengan 1';
break;
default:
$hasilSwitch = 'dianggap berbeda';
}
$hasilMatch = match ($nilai) {
1 => 'dianggap sama dengan 1',
default => 'dianggap berbeda',
};
echo "switch: $hasilSwitch\n";
echo "match : $hasilMatch\n";Output yang Dihasilkan:
switch: dianggap sama dengan 1
match : dianggap berbedaVariabel $nilai berisi string '1'. Operator == menganggap '1' dan 1 setara (ingat pembahasan type juggling), sehingga switch masuk ke case 1. Operator === menolaknya karena tipe berbeda, sehingga match jatuh ke default. Perilaku ketat ini membuat match cocok untuk nilai domain seperti kode status dan enum, tempat string '0' dan integer 0 memang tidak boleh tertukar.
Match sebagai Ekspresi Bernilai #
Karena match adalah ekspresi, ia dapat dipakai di posisi yang mustahil bagi switch: pengisian variabel, nilai kembalian return, argument pemanggilan, hingga dalam string interpolation. Dua pola berikut paling sering terpakai sehari-hari. Pertama, satu arm menerima beberapa kondisi sekaligus:
<?php
$ukuran = 'L';
$ket = match ($ukuran) {
'S', 'M' => 'ukuran kecil atau sedang',
'L', 'XL' => 'ukuran besar',
default => 'ukuran tidak dikenal',
};
echo "$ukuran -> $ket\n";Output yang Dihasilkan:
L -> ukuran besarArm 'S', 'M' cocok bila $ukuran menyamai salah satu dari keduanya. Kedua, idiom match (true) menggantikan tangga if/elseif untuk rentang nilai:
<?php
$nilaiUjian = 87;
$predikat = match (true) {
$nilaiUjian >= 90 => 'A',
$nilaiUjian >= 80 => 'B',
$nilaiUjian >= 70 => 'C',
default => 'remedial',
};
echo "Nilai $nilaiUjian mendapat predikat $predikat\n";Output yang Dihasilkan:
Nilai 87 mendapat predikat BInterpreter mengevaluasi kondisi tiap arm dari atas ke bawah, lalu mengembalikan nilai dari arm pertama yang menghasilkan true. Terakhir, mari kita buktikan bahwa hanya arm yang cocok yang dieksekusi — arm lainnya tidak pernah tersentuh:
<?php
function besar(string $s): string
{
echo " (fungsi besar() dipanggil)\n";
return strtoupper($s);
}
echo "Memanggil match...\n";
$hasil = match (2) {
1 => besar('satu'),
2 => besar('dua'),
3 => besar('tiga'),
};
echo "Hasil: $hasil\n";Output yang Dihasilkan:
Memanggil match...
(fungsi besar() dipanggil)
Hasil: DUAOutput membuktikan besar() hanya dipanggil satu kali — untuk arm 2 — padahal ketiga arm sama-sama menyebut function tersebut. Bandingkan dengan switch, yang mengeksekusi semua case yang dilewatinya saat terjadi fallthrough. Berikut rangkuman perbedaan keduanya:
| Aspek | switch | match |
|---|---|---|
| Fallthrough | Terjadi tanpa break | Tidak ada; satu arm per evaluasi |
| Perbandingan | Longgar (==) | Ketat (===) |
| Hasil | Tidak menghasilkan nilai | Ekspresi bernilai |
| Kondisi arm | Nilai konstan atau ekspresi | Ekspresi apa pun, bisa beberapa per arm |
| Kegagalan cocok | Diam turun ke default | Melempar UnhandledMatchError tanpa default |
| Isi case | Blok statement | Satu ekspresi per arm |
Ketika Tidak Ada Arm yang Cocok #
Jika tidak ada arm yang cocok dan arm default tidak ada, match menolak melanjutkan:
<?php
$format = 'zip';
echo match ($format) {
'rar' => 'mendukung arsip rar',
'tar' => 'mendukung arsip tar',
};Output yang Dihasilkan:
Fatal error: Uncaught UnhandledMatchError: Unhandled match case 'zip' in /home/z/my-project/php-course/content/php/modern-php/match-expressions/.verify_tmp.php:4
Stack trace:
#0 {main}
thrown in /home/z/my-project/php-course/content/php/modern-php/match-expressions/.verify_tmp.php on line 4Eksekusi berhenti dengan Fatal error: Uncaught UnhandledMatchError: Unhandled match case 'zip' — sebuah Error kelas baru yang sekaligus menjadi kabar baik. Kegagalan yang dulu senyap (nilai tak dikenal lolos ke default atau switch tidak mencocokkan apa pun) kini menjadi error yang keras dan mudah dilacak. Dalam praktiknya, gunakan default hanya bila nilai tak dikenal memang wajar; biarkan UnhandledMatchError meledak bila nilai tak dikenal berarti ada bug di hulu data.
Ringkasan #
match menuntaskan dua luka switch sekaligus: fallthrough yang lahir dari break terlupakan, dan perbandingan longgar yang menukar '1' dengan 1. Sebagai ekspresi bernilai, match juga membuat percabangan nilai dapat diisikan ke variabel dan dikembalikan langsung dari function, sementara UnhandledMatchError menjaga nilai tak dikenal agar tidak lolos senyap. Kedalaman penuh aturan sintaksnya ada pada chapter match expression
. Fitur PHP 8.0 berikutnya menyerang masalah yang tak kalah seringnya: rantai pemeriksaan null sebelum mengakses property — mari kita sambut nullsafe operator.