Seleksi Kondisi

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:#dcfce7

switch — 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:

  1. Menggunakan perbandingan ketat ===, bukan longgar ==
  2. Merupakan ekspresi — menghasilkan nilai yang bisa langsung di-assign
  3. Tidak ada fall-through — setiap arm hanya menjalankan satu ekspresi
  4. Harus exhaustive — jika tidak ada arm yang cocok dan tidak ada default, melempar UnhandledMatchError
<?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
SituasiPilihan Terbaik
Kondisi kompleks dengan berbagai variabelif/elseif/else
Satu variabel dibandingkan banyak nilai, PHP 8+match
Satu variabel, butuh fall-through disengajaswitch
Dua kemungkinan, ekspresi sederhanaTernary ?:
Nilai default untuk null/undefinedNull coalescing ??
Banyak kondisi sederhana di templateSintaks alternatif if:...endif
Sekumpulan nilai yang tipe-safematch + 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/else untuk 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 else yang tidak perlu setelah return, throw, atau exit — kode yang tersisa sudah implisit berada di cabang lain.
  • switch menggunakan == (longgar) — perhatikan ketika membandingkan nilai yang mungkin berbeda tipe. Selalu sertakan break di setiap case kecuali fall-through memang disengaja dan didokumentasikan.
  • match (PHP 8.0+) lebih aman dari switch: menggunakan ===, tidak ada fall-through, merupakan ekspresi (menghasilkan nilai), dan melempar UnhandledMatchError jika tidak ada arm yang cocok.
  • match(true) bisa digunakan sebagai alternatif if/elseif yang lebih ekspresif untuk kondisi-kondisi yang berbentuk ekspresi boolean.
  • match + enum adalah 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 karena endif dan endswitch menunjukkan konteks penutup dengan jelas.
  • Selalu tangani default di switch dan match untuk nilai yang datang dari luar (user input, database, API) — nilai yang tidak terduga seharusnya menghasilkan error, bukan diam-diam diabaikan.

← Sebelumnya: Operator   Berikutnya: Perulangan →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact