Seleksi Kondisi #
Seleksi kondisi adalah mekanisme yang membuat program bisa mengambil keputusan — menjalankan blok kode berbeda tergantung kondisi yang berlaku saat runtime. PHP menyediakan beberapa mekanisme untuk ini: if/elseif/else untuk kondisi yang kompleks dan tidak linear, switch untuk membandingkan satu ekspresi terhadap banyak nilai, dan match (PHP 8.0+) yang merupakan versi switch yang lebih aman dengan perbandingan ketat. Yang membedakan developer PHP yang berpengalaman dari yang baru belajar bukan sekadar menguasai sintaksnya — tapi tahu kapan menggunakan masing-masing, bagaimana menghindari jebakan fall-through di switch, dan bagaimana menulis kondisi yang bersih dengan pola early return dan guard clause.
if — Kondisi Dasar
#
if adalah bentuk seleksi kondisi paling mendasar. Blok kode di dalamnya hanya dijalankan jika ekspresi kondisi bernilai true.
<?php
$stok = 15;
if ($stok > 0) {
echo "Produk tersedia";
}
// Kondisi bisa berupa ekspresi apapun yang menghasilkan boolean
$user = cariUser(42);
if ($user !== null) {
echo "Halo, {$user->nama}!";
}
// Untuk blok satu baris, kurung kurawal opsional — tapi tetap gunakan
// ANTI-PATTERN: tanpa kurung kurawal mudah menyebabkan bug saat menambah baris
if ($stok > 0)
echo "tersedia";
echo "harga: ..."; // ini SELALU dijalankan, bukan bagian dari if!
// BENAR: selalu gunakan kurung kurawal
if ($stok > 0) {
echo "tersedia";
echo "harga: ..."; // ini benar-benar bagian dari if
}
if...else — Dua Cabang
#
else menyediakan blok alternatif yang dijalankan ketika kondisi if bernilai false.
<?php
$umur = 17;
if ($umur >= 18) {
echo "Akses diberikan";
} else {
echo "Akses ditolak — harus berusia minimal 18 tahun";
}
Pola Early Return — Menghindari else yang Tidak Perlu
#
Salah satu tanda kode yang bersih adalah menghindari else yang tidak perlu melalui pola early return. Jika if diakhiri return, throw, atau exit, blok else tidak diperlukan karena eksekusi tidak akan mencapai kode setelahnya:
<?php
// ANTI-PATTERN: else yang tidak perlu setelah return
function hitungDiskon(float $harga, string $kode): float
{
if ($kode === 'SALE10') {
return $harga * 0.10;
} else { // else ini tidak diperlukan
if ($kode === 'SALE20') {
return $harga * 0.20;
} else { // ini juga tidak diperlukan
return 0;
}
}
}
// BENAR: early return — setiap kondisi langsung return, tidak ada else
function hitungDiskonV2(float $harga, string $kode): float
{
if ($kode === 'SALE10') {
return $harga * 0.10;
}
if ($kode === 'SALE20') {
return $harga * 0.20;
}
return 0; // default — tidak ada diskon
}
if...elseif...else — Banyak Cabang
#
Untuk memeriksa lebih dari dua kondisi secara berurutan, gunakan elseif. PHP memeriksa kondisi dari atas ke bawah dan menjalankan blok pertama yang kondisinya true, lalu melewati sisa blok.
<?php
$nilai = 78;
if ($nilai >= 90) {
$grade = 'A';
} elseif ($nilai >= 80) {
$grade = 'B';
} elseif ($nilai >= 70) {
$grade = 'C';
} elseif ($nilai >= 60) {
$grade = 'D';
} else {
$grade = 'E';
}
echo "Grade: $grade"; // Grade: C
Guard Clause — Validasi di Awal Fungsi #
Pola guard clause menempatkan semua validasi dan kondisi penolakan di awal fungsi, sehingga logika utama tetap datar dan mudah dibaca. Ini kebalikan dari “happy path terakhir” yang bersarang dalam:
<?php
// ANTI-PATTERN: validasi yang bersarang dalam
function prosesOrder(array $order): string
{
if (isset($order['user_id'])) {
if ($order['user_id'] > 0) {
if (!empty($order['items'])) {
if (count($order['items']) <= 50) {
// logika utama baru ada di sini, sudah dalam 4 level!
return "Order berhasil diproses";
} else {
return "Terlalu banyak item";
}
} else {
return "Tidak ada item";
}
} else {
return "User ID tidak valid";
}
} else {
return "User ID tidak ada";
}
}
// BENAR: guard clause — validasi di atas, happy path di bawah, datar
function prosesOrderV2(array $order): string
{
if (!isset($order['user_id'])) {
return "User ID tidak ada";
}
if ($order['user_id'] <= 0) {
return "User ID tidak valid";
}
if (empty($order['items'])) {
return "Tidak ada item";
}
if (count($order['items']) > 50) {
return "Terlalu banyak item";
}
// Logika utama — bersih, datar, mudah dibaca
return "Order berhasil diproses";
}
flowchart TD
A[Mulai prosesOrder] --> B{user_id ada?}
B -- Tidak --> Z1[return: User ID tidak ada]
B -- Ya --> C{user_id > 0?}
C -- Tidak --> Z2[return: User ID tidak valid]
C -- Ya --> D{items tidak kosong?}
D -- Tidak --> Z3[return: Tidak ada item]
D -- Ya --> E{items <= 50?}
E -- Tidak --> Z4[return: Terlalu banyak item]
E -- Ya --> F[Proses order...]
F --> Z5[return: Berhasil]
style Z1 fill:#fee2e2
style Z2 fill:#fee2e2
style Z3 fill:#fee2e2
style Z4 fill:#fee2e2
style Z5 fill:#dcfce7switch — Satu Nilai, Banyak Kemungkinan
#
switch membandingkan satu ekspresi terhadap banyak nilai. Lebih ringkas dari rangkaian if/elseif ketika semua kondisi membandingkan variabel yang sama.
<?php
$metodePembayaran = "transfer";
switch ($metodePembayaran) {
case "transfer":
echo "Silakan transfer ke rekening BCA 1234567890";
break;
case "kartu_kredit":
echo "Masukkan detail kartu kredit kamu";
break;
case "dompet_digital":
case "gopay":
case "ovo":
echo "Pilih aplikasi dompet digital kamu";
break; // satu break untuk beberapa case (fall-through disengaja)
default:
echo "Metode pembayaran tidak dikenali";
}
Jebakan Fall-Through di switch
#
Perilaku paling berbahaya di switch adalah fall-through — eksekusi berlanjut ke case berikutnya jika break terlupa. PHP tidak memberi warning untuk ini:
<?php
$status = "aktif";
// ANTI-PATTERN: lupa break — fall-through yang tidak disengaja
switch ($status) {
case "aktif":
echo "Pengguna aktif\n";
// lupa break! eksekusi berlanjut ke case berikutnya
case "pending":
echo "Menunggu verifikasi\n"; // ini ikut tereksekusi!
break;
case "nonaktif":
echo "Pengguna nonaktif\n";
break;
}
// Output:
// Pengguna aktif
// Menunggu verifikasi ← tidak diharapkan!
// BENAR: setiap case memiliki break eksplisit
switch ($status) {
case "aktif":
echo "Pengguna aktif\n";
break; // jangan lupa ini
case "pending":
echo "Menunggu verifikasi\n";
break;
case "nonaktif":
echo "Pengguna nonaktif\n";
break;
default:
echo "Status tidak dikenal\n";
}
switch Menggunakan == (Longgar)
#
switch menggunakan perbandingan longgar ==, bukan ===. Ini bisa menyebabkan bug halus:
<?php
$nilai = 0;
switch ($nilai) {
case false: // 0 == false → true!
echo "false";
break;
case null: // tidak pernah dicapai karena case sebelumnya match
echo "null";
break;
case 0:
echo "nol";
break;
}
// Output: "false" — mungkin mengejutkan!
// Ini adalah salah satu alasan mengapa match lebih aman untuk nilai yang
// bisa bermacam tipe — match menggunakan ===
match — Switch yang Lebih Aman (PHP 8.0+)
#
match adalah ekspresi yang diperkenalkan di PHP 8.0 untuk menggantikan banyak use case switch. Tiga perbedaan utama yang membuatnya lebih aman:
- Menggunakan perbandingan ketat
===, bukan longgar== - Merupakan ekspresi — menghasilkan nilai yang bisa langsung di-assign
- Tidak ada fall-through — setiap arm hanya menjalankan satu ekspresi
- Harus exhaustive — jika tidak ada arm yang cocok dan tidak ada
default, melemparUnhandledMatchError
<?php
$statusKode = 404;
// match sebagai ekspresi — langsung assign ke variabel
$pesan = match ($statusKode) {
200, 201 => "Sukses",
301, 302 => "Redirect",
400 => "Bad Request",
401 => "Unauthorized",
403 => "Forbidden",
404 => "Not Found",
500 => "Internal Server Error",
default => "Unknown Status",
};
echo $pesan; // "Not Found"
match vs switch — Perbandingan Langsung
#
<?php
// Dengan switch
$tipe = "1"; // string "1", bukan integer 1
switch ($tipe) {
case 1: // "1" == 1 → true (konversi tipe!)
echo "integer";
break;
case "1":
echo "string"; // tidak pernah dicapai!
break;
}
// Output: "integer" — mungkin salah!
// Dengan match
$hasil = match ($tipe) {
1 => "integer", // "1" !== 1 → tidak cocok
"1" => "string", // "1" === "1" → cocok!
default => "lainnya",
};
echo $hasil; // "string" — benar!
match Harus Exhaustive
#
Jika tidak ada arm yang cocok dan tidak ada default, PHP melempar UnhandledMatchError:
<?php
$warna = "ungu";
// ANTI-PATTERN: match tanpa default, warna yang tidak tercakup akan error
try {
$kode = match ($warna) {
"merah" => "#FF0000",
"hijau" => "#00FF00",
"biru" => "#0000FF",
// tidak ada default — "ungu" akan melempar UnhandledMatchError
};
} catch (\UnhandledMatchError $e) {
echo "Warna tidak dikenal: $warna";
}
// BENAR: selalu sertakan default untuk nilai yang tidak terduga
$kode = match ($warna) {
"merah" => "#FF0000",
"hijau" => "#00FF00",
"biru" => "#0000FF",
default => throw new \InvalidArgumentException("Warna tidak dikenal: $warna"),
};
match dengan Kondisi Kompleks
#
match(true) memungkinkan penggunaan ekspresi boolean sebagai kondisi — berguna sebagai alternatif if/elseif yang lebih ekspresif:
<?php
$suhu = 35; // derajat Celsius
// Dengan if/elseif biasa
if ($suhu >= 40) {
$kategori = "Sangat Panas";
} elseif ($suhu >= 30) {
$kategori = "Panas";
} elseif ($suhu >= 20) {
$kategori = "Hangat";
} elseif ($suhu >= 10) {
$kategori = "Sejuk";
} else {
$kategori = "Dingin";
}
// Dengan match(true) — lebih ringkas tapi tetap jelas
$kategori = match (true) {
$suhu >= 40 => "Sangat Panas",
$suhu >= 30 => "Panas",
$suhu >= 20 => "Hangat",
$suhu >= 10 => "Sejuk",
default => "Dingin",
};
echo $kategori; // "Panas"
match Bersama Enum (PHP 8.1+)
#
match bekerja dengan sangat baik bersama enum — keduanya menggunakan === dan compiler bisa memvalidasi exhaustiveness:
<?php
enum OrderStatus
{
case Pending;
case Processing;
case Shipped;
case Delivered;
case Cancelled;
}
function labelStatus(OrderStatus $status): string
{
return match ($status) {
OrderStatus::Pending => "Menunggu Pembayaran",
OrderStatus::Processing => "Sedang Diproses",
OrderStatus::Shipped => "Dalam Pengiriman",
OrderStatus::Delivered => "Terkirim",
OrderStatus::Cancelled => "Dibatalkan",
// Tidak perlu default karena semua case enum sudah tercakup
// PHPStan/Psalm bisa memverifikasi ini secara statik
};
}
echo labelStatus(OrderStatus::Shipped); // "Dalam Pengiriman"
Sintaks Alternatif untuk Template #
PHP menyediakan sintaks alternatif untuk if dan switch yang lebih bersih saat dipakai di dalam template HTML — menggantikan { dan } dengan : dan endif/endswitch:
<!-- Template PHP — sintaks alternatif lebih bersih di HTML -->
<?php if ($user->isAdmin()): ?>
<div class="admin-panel">
<a href="/admin">Panel Admin</a>
</div>
<?php elseif ($user->isEditor()): ?>
<div class="editor-panel">
<a href="/editor">Panel Editor</a>
</div>
<?php else: ?>
<div class="user-panel">
<a href="/dashboard">Dashboard</a>
</div>
<?php endif; ?>
<!-- Sintaks alternatif switch -->
<?php switch ($tema): ?>
<?php case 'gelap': ?>
<link rel="stylesheet" href="/css/dark.css">
<?php break; ?>
<?php case 'terang': ?>
<link rel="stylesheet" href="/css/light.css">
<?php break; ?>
<?php default: ?>
<link rel="stylesheet" href="/css/default.css">
<?php endswitch; ?>
Sintaks alternatif ini sangat berguna di file view/template karena kurung kurawal penutup } sulit diidentifikasi kepemilikannya saat bercampur dengan banyak HTML, sementara endif dan endswitch jelas menunjukkan konteksnya.
Memilih Mekanisme yang Tepat #
Dengan tiga mekanisme utama yang tersedia, panduan berikut membantu memilih yang tepat untuk setiap situasi:
flowchart TD
A{Membandingkan\napa?} --> B{Satu variabel\nvs banyak nilai?}
B -- Ya --> C{PHP 8.0+\ntersedia?}
C -- Ya --> D{Butuh type\nsafety ketat?}
D -- Ya --> E[Gunakan match\nPerbandingan ===\nTidak ada fall-through]
D -- Tidak --> F{Ada fall-through\nyang disengaja?}
F -- Ya --> G[Gunakan switch\nDokumentasikan fall-through]
F -- Tidak --> E
C -- Tidak --> G
B -- Tidak --> H{Kondisi\nkompleks/berbeda?}
H -- Ya --> I[Gunakan if/elseif/else\nPola guard clause]
H -- Tidak --> J{Hanya dua\ncabang?}
J -- Ya --> K{Ekspresi\nsederhana?}
K -- Ya --> L[Gunakan ternary ?\nAtau null coalescing ??]
K -- Tidak --> I
J -- Tidak --> I| Situasi | Pilihan Terbaik |
|---|---|
| Kondisi kompleks dengan berbagai variabel | if/elseif/else |
| Satu variabel dibandingkan banyak nilai, PHP 8+ | match |
| Satu variabel, butuh fall-through disengaja | switch |
| Dua kemungkinan, ekspresi sederhana | Ternary ?: |
| Nilai default untuk null/undefined | Null coalescing ?? |
| Banyak kondisi sederhana di template | Sintaks alternatif if:...endif |
| Sekumpulan nilai yang tipe-safe | match + enum |
Anti-Pattern yang Sering Ditemui #
<?php
// ✗ Anti-pattern 1: kondisi boolean yang redundan
$aktif = true;
if ($aktif === true) { // ✗ perbandingan ke true tidak diperlukan
echo "aktif";
}
if ($aktif) { // ✓ lebih bersih — $aktif sudah boolean
echo "aktif";
}
// ✗ Anti-pattern 2: negasi ganda yang membingungkan
if (!$user->tidakAktif()) { // ✗ tidak (!tidakAktif) sulit dibaca
echo "user aktif";
}
if ($user->aktif()) { // ✓ nama method yang positif lebih jelas
echo "user aktif";
}
// ✗ Anti-pattern 3: if bersarang dalam yang bisa diratakan
function cekAkses(User $user, string $resource): bool
{
if ($user->isLoggedIn()) {
if ($user->hasPermission($resource)) {
if (!$user->isBanned()) {
return true;
}
}
}
return false;
}
// ✓ Versi yang diratakan dengan guard clause
function cekAksesV2(User $user, string $resource): bool
{
if (!$user->isLoggedIn()) return false;
if (!$user->hasPermission($resource)) return false;
if ($user->isBanned()) return false;
return true;
}
// ✗ Anti-pattern 4: switch tanpa default untuk nilai eksternal
function prosesStatus(string $status): void
{
switch ($status) {
case "aktif":
aktifkan();
break;
case "nonaktif":
nonaktifkan();
break;
// tidak ada default — nilai tidak terduga diam-diam diabaikan!
}
}
// ✓ Selalu tangani kasus yang tidak terduga
function prosesStatusV2(string $status): void
{
switch ($status) {
case "aktif":
aktifkan();
break;
case "nonaktif":
nonaktifkan();
break;
default:
throw new \InvalidArgumentException("Status tidak dikenal: $status");
}
}
// ✗ Anti-pattern 5: assignment di dalam kondisi (typo umum)
$data = cariData();
if ($data = null) { // ✗ ini MENUGASKAN null ke $data, selalu false!
echo "data kosong";
}
if ($data === null) { // ✓ ini MEMBANDINGKAN
echo "data kosong";
}
// Untuk menghindari typo ini, gunakan "Yoda condition" — tempatkan literal di kiri
if (null === $data) { // ✓ jika kamu tidak sengaja tulis = , parse error
echo "data kosong";
}
Ringkasan #
if/elseif/elseuntuk kondisi yang melibatkan ekspresi berbeda atau logika yang kompleks. Gunakan pola guard clause — validasi di atas, happy path di bawah — untuk menghindari nesting yang dalam.- Hindari
elseyang tidak perlu setelahreturn,throw, atauexit— kode yang tersisa sudah implisit berada di cabang lain.switchmenggunakan==(longgar) — perhatikan ketika membandingkan nilai yang mungkin berbeda tipe. Selalu sertakanbreakdi setiap case kecuali fall-through memang disengaja dan didokumentasikan.match(PHP 8.0+) lebih aman dariswitch: menggunakan===, tidak ada fall-through, merupakan ekspresi (menghasilkan nilai), dan melemparUnhandledMatchErrorjika tidak ada arm yang cocok.match(true)bisa digunakan sebagai alternatifif/elseifyang lebih ekspresif untuk kondisi-kondisi yang berbentuk ekspresi boolean.match+enumadalah kombinasi terkuat untuk memodelkan state machine atau domain logic yang tipe-safe — PHPStan dan Psalm bisa memverifikasi exhaustiveness secara statik.- Sintaks alternatif (
if:...endif,switch:...endswitch) lebih bersih untuk template HTML karenaendifdanendswitchmenunjukkan konteks penutup dengan jelas.- Selalu tangani
defaultdiswitchdanmatchuntuk nilai yang datang dari luar (user input, database, API) — nilai yang tidak terduga seharusnya menghasilkan error, bukan diam-diam diabaikan.