Operator Nullsafe dan Null Coalescing

Sintaks modern ?->, ??, dan ??= untuk penanganan null.

Daftar isi

Topik ini membahas tiga operator yang dirancang untuk penanganan nilai null, yaitu operator null coalescing ??, operator nullsafe ?->, dan bentuk penugasannya ??=. Pembahasan mencakup semantik masing-masing operator, cara memadukannya pada rantai akses objek, dan contoh penerapannya pada data perpustakaan. Setelah membacanya, rangkaian pemeriksaan isset() dan cabang if yang berulang untuk menangani null dapat diringkas menjadi ekspresi pendek yang perilakunya tetap jelas.

Operator null coalescing#

Operator ?? mengembalikan operand kiri apabila operand itu tersedia dan bukan null, dan mengembalikan operand kanan pada kasus sebaliknya. Pemeriksaannya mengikuti semantik isset(), sehingga variabel yang belum ada maupun kunci array yang belum terisi tidak memicu peringatan apa pun. Operator ini pada dasarnya meringkas pola isset($x) ? $x : $cadangan menjadi satu ekspresi.

php null-coalescing.php
<?php

declare(strict_types=1);

$pengaturan = [
    'tema' => 'gelap',
    'batas' => null,
];

$tema = $pengaturan['tema'] ?? 'terang';
$batas = $pengaturan['batas'] ?? 30;
$bahasa = $pengaturan['bahasa'] ?? 'id';

echo "Tema: $tema", PHP_EOL;
echo "Batas: $batas", PHP_EOL;
echo "Bahasa: $bahasa", PHP_EOL;

$kunci = null;
$sandi = $kunci ?? 'nilai cadangan';
echo "Sandi: $sandi", PHP_EOL;
text Keluaran
Tema: gelap
Batas: 30
Bahasa: id
Sandi: nilai cadangan

Tiga kasus pada contoh di atas memperlihatkan tiga perilaku yang berbeda. Kunci tema berisi string, sehingga nilainya langsung dipakai. Kunci batas ada tetapi berisi null, sehingga nilai cadangannya dipakai. Kunci bahasa sama sekali tidak ada, dan pemeriksaan bergaya isset() membuat kasus ini juga jatuh ke nilai cadangan tanpa pemberitahuan. Operand kanan ?? hanya dievaluasi apabila operand kirinya jatuh ke null, sehingga nilai cadangan yang mahal harganya, misalnya pemanggilan fungsi, tidak dijalankan bila tidak diperlukan.

Operator nullsafe#

Operator ?-> mengakses anggota objek dengan satu kemungkinan tambahan, yaitu menghasilkan null tanpa error apabila ekspresi di sebelah kirinya null. Pada akses biasa dengan ->, pembacaan anggota dari null melempar Error dan menghentikan program. Operator ini berlaku untuk akses properti maupun pemanggilan metode, dan penghentiannya menjangkau seluruh sisa rantai, sehingga kegagalan pada tautan pertama tidak menyisakan evaluasi pada tautan berikutnya.

php nullsafe.php
<?php

declare(strict_types=1);

class Peminjam
{
    public function __construct(
        public readonly string $nama,
        public readonly ?Kartu $kartu,
    ) {
    }
}

class Kartu
{
    public function __construct(
        public readonly string $nomor,
        public readonly ?MasaBerlaku $masaBerlaku,
    ) {
    }
}

class MasaBerlaku
{
    public function __construct(
        public readonly int $tahun,
    ) {
    }

    public function tahunBerakhir(): int
    {
        return $this->tahun;
    }
}

$dewi = new Peminjam('Dewi', new Kartu('PJM-042', new MasaBerlaku(2027)));
$budi = new Peminjam('Budi', null);

echo 'Kartu Dewi berlaku sampai: ', $dewi->kartu?->masaBerlaku?->tahunBerakhir(), PHP_EOL;
echo 'Kartu Budi berlaku sampai: ', $budi->kartu?->masaBerlaku?->tahunBerakhir() ?? 'tanpa kartu', PHP_EOL;
text Keluaran
Kartu Dewi berlaku sampai: 2027
Kartu Budi berlaku sampai: tanpa kartu

Rantai $budi->kartu?->masaBerlaku?->tahunBerakhir() berhenti pada tautan pertama karena $budi->kartu bernilai null, dan seluruh ekspresi menghasilkan null tanpa menyentuh masaBerlaku maupun metodenya. Hasil null itu kemudian diberi nilai cadangan melalui ?? sehingga kalimat yang ditampilkan tetap lengkap. Pola perpaduan ?-> dan ?? inilah bentuk paling lazim dari kedua operator tersebut, yaitu jalur akses yang aman diikuti nilai pengganti.

?-> tidak dapat dipakai pada sisi penugasan. Ekspresi seperti $kartu?->nomor = 'baru' ditolak pada saat kompilasi dengan pesan Can't use nullsafe operator in write context, karena penulisan ke objek yang mungkin tidak ada tidak bermakna. Rantai nullsafe selalu berperan sebagai pembaca, sedangkan penulisan tetap memerlukan pemeriksaan eksplisit terlebih dahulu.

Operator penugasan ??=#

Operator ??= menugaskan operand kanan ke operand kiri hanya apabila operand kiri bernilai null atau belum ada. Bentuk ini adalah singkatan dari $a = $a ?? $b dan berguna untuk inisialisasi tertunda, yaitu mengisi nilai bawaan pada entri yang belum terisi tanpa menimpa yang sudah ada. Karena pemeriksaannya bergaya isset(), kunci array yang belum terbentuk pun langsung dibuat oleh penugasan ini.

php null-assignment.php
<?php

declare(strict_types=1);

$hitungan = [];

foreach (['fiksi', 'fiksi', 'sains', 'fiksi', 'sains'] as $rak) {
    $hitungan[$rak] ??= 0;
    $hitungan[$rak] += 1;
}

print_r($hitungan);
text Keluaran
Array
(
    [fiksi] => 3
    [sains] => 2
)

Pada perulangan di atas, kunci yang belum ada diinisialisasi dengan 0 lebih dahulu, lalu dinaikkan satu per satu. Tanpa ??=, pola yang sama membutuhkan pemanggilan isset() di dalam cabang if. Bentuk penugasan ini juga berlaku untuk properti dan elemen array bertingkat, sebagaimana ?? pada umumnya.

Bentuk ini juga menjadi pola memoization sederhana, yaitu menyimpan hasil perhitungan agar pemanggilan berikutnya tidak mengulanginya. Hasil yang mahal disimpan pada kunci cache, dan setiap pemanggilan menemukan kunci tersebut sudah terisi sehingga perhitungan dilewati. Karena pemicunya adalah null semata, hasil sah seperti string kosong maupun nol tetap dipertahankan sebagai isi cache, dan nilai cadangan hanya dihitung untuk kunci yang memang belum ada.

Kesalahan umum#

Kesalahan pertama adalah menyamakan null dengan nilai yang dianggap kosong. Operator ?? hanya memeriksa null, sehingga string kosong dan angka nol lolos sebagai nilai sah sebagaimana contoh berikut.

php null-falsy.php
<?php

declare(strict_types=1);

$teks = '';
$angka = 0;

var_dump($teks ?? 'nilai cadangan');
var_dump($angka ?? 'nilai cadangan');
var_dump(isset($teks), isset($tidakada));
text Keluaran
string(0) ""
int(0)
bool(true)
bool(false)

String kosong dan nol pada keluaran di atas menegaskan bahwa nilai yang falsy (dianggap salah dalam konteks boolean) tetap bukan null bagi operator ini. Apabila nilai kosong seperti string kosong juga perlu diganti, pemeriksaan eksplisit dengan === '' atau fungsi yang sesuai dituliskan terlebih dahulu.

Kesalahan kedua adalah mengira ?-> menjaga seluruh ekspresi tempat ia berada. Operator itu hanya menangani rantai di sebelah kanannya, dan hasil null dari rantai yang terputus tetap harus ditangani, misalnya dengan ?? atau pemeriksaan eksplisit sebelum dipakai dalam perhitungan. Ekspresi $kartu?->masaBerlaku()->tahun + 1 pada peminjam tanpa kartu tetap berusaha menjumlahkan null, dan perhitungan semacam itu berakhir dengan Error.

Kesalahan ketiga adalah memakai ??= untuk logika yang tidak boleh menimpa nilai 0 atau ''. Karena pemicu penugasannya adalah null semata, nilai yang sudah terisi dengan 0 tidak akan diganti, dan itu memang perilaku yang diharapkan. Bila kebijakannya berbeda, misalnya memperlakukan string kosong sebagai kosong, penugasan ini bukan alat yang tepat.

Ringkasan#

  • ?? mengembalikan operand kiri bila tersedia dan bukan null, dengan pemeriksaan bergaya isset() yang tidak memicu peringatan.
  • ?-> mengakses anggota objek dengan aman dan menghentikan seluruh sisa rantai bila kirinya null.
  • ??= menugaskan nilai baru hanya bila operand kirinya null atau belum ada, sehingga cocok untuk inisialisasi bawaan.
  • Ketiganya tidak memperlakukan string kosong dan nol sebagai null, dan hasil null dari rantai nullsafe tetap perlu ditangani.

Referensi#