Data relasional di aplikasi nyata jarang berbentuk lurus: pesanan mungkin punya pelanggan, pelanggan mungkin punya alamat, dan di setiap simpul ada kemungkinan null. Sebelum PHP 8.0, membaca satu nilai di ujung rantai relasi menuntut pemeriksaan null bertingkat yang membengkak sepanjang kedalaman rantainya. Nullsafe operator ?-> yang hadir pada PHP 8.0 memangkas seluruh ritual tersebut menjadi satu ekspresi pendek. Chapter ini menelaah fitur tersebut dari sudut evolusi — pola lama apa yang digantikannya dan bagaimana perilaku short-circuit-nya bekerja. Untuk pembahasan operator secara penuh, termasuk posisinya di antara operator lain dan interaksinya dengan null coalescing, rujuk chapter nullsafe operator di Section Operator dan Expression .

Luka Lama: Pemeriksaan Null Bertingkat #

Pola lama mengakses property di ujung rantai mengikuti satu aturan tegas: sebelum menulis ->, kita wajib memastikan ruas kirinya bukan null, karena mengakses property pada null memicu fatal error. Perhatikan dua function berikut — keduanya menyelesaikan tugas yang persis sama:

php
<?php
class Pelanggan
{
    public function __construct(public string $nama) {}
}

class Pesanan
{
    public function __construct(public ?Pelanggan $pelanggan) {}
}

// Pola lama PHP 7 dan sebelumnya: pemeriksaan bertingkat
function namaPelangganLama(?Pesanan $pesanan): ?string
{
    if ($pesanan !== null && $pesanan->pelanggan !== null) {
        return $pesanan->pelanggan->nama;
    }

    return null;
}

// Pola baru PHP 8.0: satu baris nullsafe
function namaPelangganBaru(?Pesanan $pesanan): ?string
{
    return $pesanan?->pelanggan?->nama;
}

var_dump(namaPelangganLama(null));
var_dump(namaPelangganBaru(null));
var_dump(namaPelangganBaru(new Pesanan(new Pelanggan('Siti'))));

Output yang Dihasilkan:

text
NULL
NULL
string(4) "Siti"

Ketiga pemanggilan menghasilkan output yang identik untuk kedua versi: NULL, NULL, dan string(4) "Siti". Bedanya ada pada bentuk dan ketahanan terhadap perubahan. Function lama menumpuk kondisi !== null sebanyak kedalaman rantainya — dua tingkat berarti dua kondisi, tiga tingkat berarti tiga, dan setiap penambahan relasi baru menuntut penulisan ulang blok if. Function baru cukup menuliskan ?-> di setiap simpul yang mungkin null: bila ruas kiri ternyata null, seluruh ekspresi langsung bernilai null tanpa error.

Rantai Panjang dan Perilaku Short-Circuit #

Pada rantai yang lebih dalam, keuntungan nullsafe makin terasa — cukup tanda ? di setiap panah, dan rantai sepanjang apa pun aman dievaluasi:

php
<?php
class Alamat
{
    public function __construct(public string $kota) {}
}

class Pelanggan
{
    public function __construct(public ?Alamat $alamat = null) {}
}

class Toko
{
    public function __construct(public ?Pelanggan $pembeli = null) {}
}

$tokoKosong = new Toko();
$tokoTerisi = new Toko(new Pelanggan(new Alamat('Bandung')));

var_dump($tokoKosong->pembeli?->alamat?->kota);
var_dump($tokoTerisi->pembeli?->alamat?->kota);

Output yang Dihasilkan:

text
NULL
string(7) "Bandung"

Baris pertama menghasilkan NULL karena $tokoKosong->pembeli bernilai null — dua panah sisanya tidak pernah dieksekusi. Baris kedua menelusuri tiga tingkat hingga 'Bandung'. Mekanisme ini bernama short-circuit: begitu satu simpul rantai terbukti null, evaluasi berhenti di titik itu dan seluruh ekspresi dinilai null. Mari kita buktikan sisi “berhenti”-nya dengan method yang mencetak jejak saat dipanggil:

php
<?php
class Alamat
{
    public function __construct(public string $kota) {}
}

class Pelanggan
{
    public function __construct(public ?Alamat $alamat = null) {}

    public function alamatUtama(): ?Alamat
    {
        echo "  -> metode alamatUtama() dieksekusi\n";
        return $this->alamat;
    }
}

$pelanggan = null;
echo "Pemanggilan 1 — object sejak awal null:\n";
var_dump($pelanggan?->alamatUtama()?->kota);

$ada = new Pelanggan();
echo "Pemanggilan 2 — object ada, alamat null:\n";
var_dump($ada->alamatUtama()?->kota);

Output yang Dihasilkan:

text
Pemanggilan 1 — object sejak awal null:
NULL
Pemanggilan 2 — object ada, alamat null:
  -> metode alamatUtama() dieksekusi
NULL

Pada pemanggilan pertama, $pelanggan bernilai null sehingga method alamatUtama() tidak pernah tersentuh — tidak ada baris jejak pada output. Pada pemanggilan kedua, object $ada ada sehingga method dieksekusi (baris jejak tercetak), lalu method tersebut mengembalikan null dan rantai berhenti sebelum membaca ?->kota. Implikasinya penting: nullsafe bukan sekadar pengaman error, tetapi juga jaminan bahwa efek samping — query database, penulisan log, apa pun isi method — tidak terjadi ketika objectnya sendiri tidak ada.

Kombinasi dengan Null Coalescing #

Nullsafe menghasilkan null saat rantai terputus, dan null jarang menjadi nilai akhir yang ingin ditampilkan pengguna. Dua fitur ini memang dirancang saling melengkapi: nullsafe menjaga akses aman, null coalescing ?? menyediakan nilai cadangan. Pola keduanya menjadi teman sehari-hari di lapisan presentasi:

php
<?php
class Pengguna
{
    public function __construct(public string $nama) {}
}

class Sesi
{
    public function __construct(public ?Pengguna $user = null) {}
}

$sesiTamu = new Sesi();
$sesiLogin = new Sesi(new Pengguna('Budi'));

echo 'Halo, ', $sesiTamu->user?->nama ?? 'Tamu', "!\n";
echo 'Halo, ', $sesiLogin->user?->nama ?? 'Tamu', "!\n";

Output yang Dihasilkan:

text
Halo, Tamu!
Halo, Budi!

Pada baris pertama, $sesiTamu->user bernilai null, ekspresi $sesiTamu->user?->nama menghasilkan null, lalu ?? 'Tamu' menggantinya menjadi Tamu. Baris kedua menelusuri rantai hingga Budi sehingga nilai cadangan tidak terpakai. Urutan keduanya tidak tertukar: ?-> bekerja di level object dan property, sedangkan ?? bekerja di level nilai akhir ekspresi.

Batasan: Nullsafe Bukan untuk Penulisan #

Semua kekuatan ?-> berlaku pada konteks pembacaan. Saat operator ini dipakai sebagai tujuan penugasan, interpreter menolaknya:

php
<?php
class Konfigurasi
{
    public string $bahasa = 'id';
}

$konfig = null;
$konfig?->bahasa = 'en';

Output yang Dihasilkan:

text

Fatal error: Can't use nullsafe operator in write context in /home/z/my-project/php-course/content/php/modern-php/nullsafe-operator/.verify_tmp.php on line 8

Eksekusi berhenti dengan Fatal error: Can't use nullsafe operator in write context. Alasannya masuk akal: bila $konfig bernilai null, penugasan $konfig?->bahasa = 'en' tidak punya object tujuan — PHP tidak mau menebak apakah harus membuat object baru, mengabaikan penugasan, atau melempar error, sehingga pilihan desainnya adalah melarang bentuk ini sama sekali. Pola yang benar tetap eksplisit: periksa null dulu, atau bangun object lengkap sebelum mengisi property-nya.

Ringkasan #

Nullsafe operator memangkas pemeriksaan null bertingkat menjadi satu rantai ?-> pendek: eksekusi berhenti otomatis di simpul null pertama, efek samping method pada simpul berikutnya tidak pernah terjadi, dan kombinasi dengan ?? memberi nilai cadangan siap pakai. Batasannya jelas: hanya konteks baca, dan hanya null — bukan false — yang diperiksa. Bedah operator ini berdampingan dengan operator lain ada di chapter nullsafe operator . Berikutnya, kita beralih dari menjaga akses property ke menjaga kontrak tipe: union types yang membawa keamanan runtime ke deklarasi yang dulu hanya hidup di docblock.

Referensi #