Data dalam program sering kali “mungkin ada, mungkin tidak”: parameter URL yang tidak selalu dikirim pengguna, kunci konfigurasi yang belum diisi, atau kolom database yang bernilai kosong. Membaca data seperti itu secara langsung memicu peringatan Warning: Undefined variable atau Warning: Undefined array key pada PHP 8. Null coalescing operator (??) menyelesaikan masalah tersebut dalam satu ekspresi ringkas: gunakan nilai yang ada, atau jatuhkan ke nilai cadangan. Pada Chapter ini kita akan mempelajari cara kerja ??, bentuk assignment-nya ??=, serta pola penggunaan yang umum di aplikasi nyata.

Masalah Nilai yang Mungkin Tidak Ada #

Sebelum ada ??, cara baku menangani nilai yang mungkin tidak ada adalah menggabungkan fungsi isset() dengan operator ternary (kondisi ? nilaiJikaBenar : nilaiJikaSalah — operator ini akan kita bedah tuntas pada Chapter Ternary Operator ). Pola tersebut berfungsi, tetapi membuat kita menulis nama variabel atau kunci array dua kali:

php
<?php
$setelan = ["tema" => "gelap"];

$bahasa = isset($setelan["bahasa"]) ? $setelan["bahasa"] : "id";

echo "Bahasa: $bahasa\n";

Output yang Dihasilkan:

text
Bahasa: id

Fungsi isset($setelan["bahasa"]) memeriksa apakah kunci "bahasa" ada dan nilainya bukan null. Karena kunci tersebut tidak ada, ekspresi ternary menghasilkan nilai cadangan "id". Pola ini benar, tetapi boros — kita menyebut $setelan["bahasa"] dua kali dan pembaca harus memindai seluruh baris untuk menemukan nilai cadangannya.

Operator ?? untuk Nilai Cadangan #

Operator ?? (dibaca: “null coalescing”) memadatkan pola di atas menjadi satu ekspresi. Operand kiri adalah nilai utama; operand kanan adalah nilai cadangan yang dipakai hanya jika operand kiri tidak ada atau bernilai null:

php
<?php
$setelan = ["tema" => "gelap"];

$tema = $setelan["tema"] ?? "terang";
$bahasa = $setelan["bahasa"] ?? "id";

echo "Tema: $tema\n";
echo "Bahasa: $bahasa\n";

Output yang Dihasilkan:

text
Tema: gelap
Bahasa: id

Mari kita telaah baris-baris pentingnya:

  1. Baris 4 ($tema = ...): kunci "tema" ada dan nilainya "gelap" (bukan null), sehingga $tema diisi "gelap"; nilai cadangan "terang" diabaikan.
  2. Baris 5 ($bahasa = ...): kunci "bahasa" tidak ada di array $setelan, sehingga $bahasa diisi nilai cadangan "id" — tanpa peringatan apa pun.

Perilaku “tanpa peringatan” ini setara dengan isset(): operator ?? memeriksa keberadaan terlebih dahulu sebelum membaca nilai, sehingga variabel yang belum pernah didefinisikan pun aman dipakai:

php
<?php
$namaPengguna = $tidakPernahDidefinisikan ?? "Tamu";

echo "Halo, $namaPengguna\n";

Output yang Dihasilkan:

text
Halo, Tamu

Perlu digarisbawahi: variabel $tidakPernahDidefinisikan memang tidak ada, namun baris tersebut tidak mencetak Warning: Undefined variable. Inilah alasan utama ?? menjadi cara idiomatik PHP modern untuk menangani data opsional.

Hanya null yang Digantikan #

Operator ?? mengganti nilai hanya dalam dua keadaan: nilai tidak ada, atau nilainya null. Nilai lain — sekalipun dianggap false oleh PHP — tetap dipakai apa adanya. Perhatikan contoh berikut:

php
<?php
$catatan = null;
$stok = 0;
$kodeKupon = "0";

echo ($catatan ?? "belum ada catatan") . "\n";
echo ($stok ?? 100) . "\n";
echo ($kodeKupon ?? "tanpa kupon") . "\n";

Output yang Dihasilkan:

text
belum ada catatan
0
0

Nilai null pada $catatan digantikan nilai cadangan. Sebaliknya, $stok bernilai 0 dan $kodeKupon bernilai "0" — keduanya adalah nilai falsy (akan dibahas tuntas pada Chapter Truthiness dan Short-Circuit ) — namun keduanya bukan null, sehingga keduanya lolos tanpa diganti. Perilaku ini disengaja: angka nol dan string "0" adalah data valid yang tidak boleh hilang diam-diam.

Merangkai Beberapa Nilai Cadangan #

Operand ?? boleh dirangkai menjadi rantai: PHP mengevaluasi dari kiri ke kanan dan mengambil nilai pertama yang ada dan bukan null. Nilai di sebelah kanannya tidak dievaluasi sama sekali:

php
<?php
$variabelLingkungan = null;
$berkasSetelan = null;

$sumberData = $berkasSetelan ?? $variabelLingkungan ?? "nilai bawaan";

echo "Sumber data: $sumberData\n";

Output yang Dihasilkan:

text
Sumber data: nilai bawaan

Sifat malas (lazy) rantai ?? bisa dibuktikan dengan sebuah fungsi. Contoh berikut mensimulasikan pembacaan data mahal: selama cache berisi data, fungsi pembaca tidak pernah dipanggil:

php
<?php
function bacaDariServer(): ?string {
    echo "(menghubungi server...)\n";
    return "data dari server";
}

$cache = "data dari cache";
$data = $cache ?? bacaDariServer();
echo $data . "\n";

$cache = null;
$data = $cache ?? bacaDariServer();
echo $data . "\n";

Output yang Dihasilkan:

text
data dari cache
(menghubungi server...)
data dari server

Pada penugasan pertama, $cache berisi string sehingga bacaDariServer() tidak pernah dieksekusi — tidak ada baris “(menghubungi server…)” sebelum “data dari cache”. Setelah $cache diubah menjadi null, giliran fungsi tersebut dijalankan. (Fungsi akan dibahas tuntas pada Section Fungsi; untuk sekarang cukup pahami bahwa bacaDariServer() adalah kode yang baru dieksekusi saat dipanggil.)

Operator Assignment ??= #

Sering kali kita ingin aturannya terbalik: variabel tetap mempertahankan nilai lamanya, dan nilai bawaan hanya diisi bila variabel tersebut kosong. PHP 7.4 menyediakan ??= untuk itu:

php
<?php
$setelan = ["timeout" => 30];

$setelan["timeout"] ??= 60;
$setelan["retries"] ??= 3;

print_r($setelan);

$catatan = null;
$catatan ??= "Belum ada catatan";
echo $catatan . "\n";

Output yang Dihasilkan:

text
Array
(
    [timeout] => 30
    [retries] => 3
)
Belum ada catatan

Kunci "timeout" sudah bernilai 30, sehingga ??= 60 tidak mengubahnya. Kunci "retries" belum ada, sehingga dibuat dan diisi 3 — ??= pada elemen array yang belum ada tidak memicu peringatan, sama seperti ??. Variabel $catatan yang semula null kemudian terisi string cadangan. Ekspresi $x ??= $y juga mengembalikan nilai akhirnya, sehingga bisa langsung dipakai dalam ekspresi lain.

Pola Umum dalam Praktik #

Penggunaan paling sering dari ?? adalah membaca input dari superglobal seperti $_GET tanpa harus memeriksa keberadaannya. Contoh berikut berjalan di CLI, tempat $_GET berupa array kosong, sehingga kedua parameter jatuh ke nilai bawaan:

php
<?php
$halaman = (int) ($_GET["halaman"] ?? 1);
$aksi = $_GET["aksi"] ?? "daftar";

echo "Aksi: $aksi, halaman: $halaman\n";

Output yang Dihasilkan:

text
Aksi: daftar, halaman: 1

Pola yang sama berlaku untuk konfigurasi aplikasi: mulai dari nilai bawaan, lalu biarkan berkas setelan atau variabel lingkungan menimpa sebagian saja. Berkas contoh.php pada bagian bawah halaman merangkum pola-pola tersebut dalam satu program utuh yang bisa Anda jalankan langsung.

Ringkasan #

Operator ?? mengambil nilai pertama yang ada dan bukan null dari rangkaian operand, tanpa pernah memicu peringatan untuk variabel atau kunci array yang tidak ada. Bentuk ??= mengisi nilai bawaan hanya bila target kosong. Berbeda dengan ?:, operator ini tidak menyentuh nilai falsy seperti 0 atau "" — ia hanya peduli null. Selanjutnya kita akan bertemu sepupu dekatnya: nullsafe operator ?-> yang menerapkan ide serupa pada akses method dan properti objek.

Referensi #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

contoh.php

Unduh berkas
php
<?php
// contoh.php — rangkuman Null Coalescing Operator
// Jalankan dengan: php contoh.php

// 1. Nilai cadangan untuk kunci array yang mungkin tidak ada
$setelanPengguna = [
    "tema" => "gelap",
    "notifikasi" => null,
];

$tema = $setelanPengguna["tema"] ?? "terang";
$notifikasi = $setelanPengguna["notifikasi"] ?? "aktif";
$bahasa = $setelanPengguna["bahasa"] ?? "id";

echo "Tema          : $tema\n";
echo "Notifikasi    : $notifikasi\n";
echo "Bahasa        : $bahasa\n";

// 2. Hanya null yang diganti; 0 dan "0" tetap dipakai
$stok = 0;
$kodeKupon = "0";
echo "Stok          : " . ($stok ?? 100) . "\n";
echo "Kode kupon    : " . ($kodeKupon ?? "tanpa kupon") . "\n";

// 3. Rantai nilai cadangan: ambil yang pertama tersedia
$variabelLingkungan = null;
$berkasSetelan = null;
$sumber = $berkasSetelan ?? $variabelLingkungan ?? "nilai bawaan";
echo "Sumber data   : $sumber\n";

// 4. ??= mengisi hanya bila target kosong
$setelanPengguna["timeout"] ??= 60;
$setelanPengguna["retries"] ??= 3;
echo "Timeout       : {$setelanPengguna['timeout']} detik\n";
echo "Retries       : {$setelanPengguna['retries']}\n";

// 5. Input request dengan nilai bawaan
$halaman = (int) ($_GET["halaman"] ?? 1);
echo "Halaman       : $halaman\n";