Setiap function PHP biasanya berakhir dengan dua cara: mengembalikan nilai, atau selesai tanpa nilai. Ada kategori ketiga yang selama ini tak bisa dinyatakan dalam type system: function yang tidak pernah kembali — karena selalu melempar exception, atau karena menghentikan program lewat exit. PHP 8.1 memberi kategori itu nama resmi: return type never. Janji ini terdengar aneh pada awalnya — function yang menjamin pemanggilnya tidak akan pernah melanjutkan eksekusi — tetapi justru di situlah kekuatannya. Chapter ini membahas never dari sudut evolusi dan menyoroti dividen terbesarnya: membuat static analyzer seperti PHPStan dan Psalm memahami alur program jauh lebih akurat. Perbandingan mendalamnya dengan void ada pada chapter never dan void di Section Tipe Data dan Type System .

Function yang Berjanji Tidak Kembali #

Dua wajah never yang paling umum: helper yang selalu melempar, dan pembungkus exit. Mari mulai dari yang pertama:

php
<?php
function gagal(string $pesan): never
{
    throw new RuntimeException($pesan);
}

function ambilKunci(array $data, string $kunci): string
{
    if (!array_key_exists($kunci, $data)) {
        gagal("Kunci '$kunci' tidak ditemukan");
    }

    return $data[$kunci];
}

$data = ['nama' => 'Budi', 'kota' => 'Bandung'];

echo ambilKunci($data, 'nama'), "\n";

try {
    echo ambilKunci($data, 'umur'), "\n";
} catch (RuntimeException $e) {
    echo 'Terjadi galat: ', $e->getMessage(), "\n";
}

Output yang Dihasilkan:

text
Budi
Terjadi galat: Kunci 'umur' tidak ditemukan

Function gagal() dideklarasikan never karena satu-satunya jalan keluarnya adalah throw. Pemanggilan pertama ambilKunci() menemukan kunci 'nama' dan mengembalikan Budi. Pemanggilan kedua tidak menemukan kunci 'umur', memasuki cabang gagal(), dan exception yang dilempar menjalar sampai blok catch di level atas. Perhatikan struktur ambilKunci(): setelah cabang gagal(), tidak ada return di dalam if — kode langsung melompat ke return $data[$kunci] di bawahnya. Tanpa never, pembaca (dan static analyzer) harus menebak apakah jalur itu aman; dengan never, kontraknya tegas — begitu gagal() dipanggil, tidak ada jalan kembali.

Versi exit: Menghentikan Program #

Wajah kedua never adalah pembungkus exit, lazim di skrip CLI dan handler request web:

php
<?php
function hentikanProgram(string $alasan): never
{
    echo "Menghentikan program: $alasan\n";
    exit;
}

echo "Program dimulai\n";
hentikanProgram('kunci API tidak ada');
echo "Baris ini tidak pernah tercetak\n";

Output yang Dihasilkan:

text
Program dimulai
Menghentikan program: kunci API tidak ada

Output membuktikan janjinya: baris terakhir — echo "Baris ini tidak pernah tercetak\n" — memang tidak pernah tercetak, karena exit menghentikan seluruh proses sebelum sampai ke sana. Inilah bedanya mendasar dengan function biasa: pemanggilan hentikanProgram() bukan sekadar “selesai”, melainkan titik henti seluruh program. Pada praktik CLI, exit juga menerima kode status — exit(0) berarti sukses, exit(1) berarti galat — yang dibaca skrip pemanggil atau pipeline shell.

Janji yang Diingkari #

Return type never adalah janji yang ditegakkan runtime. Bila function berdeklarasi never justru berusaha kembali secara normal, interpreter menolaknya:

php
<?php
function selamanya(): never
{
    echo "Bersiap berhenti...\n";
    // tidak ada `exit` maupun `throw`
}

selamanya();

Output yang Dihasilkan:

text
Bersiap berhenti...

Fatal error: Uncaught TypeError: selamanya(): never-returning function must not implicitly return in /home/z/my-project/php-course/content/php/modern-php/never-return-type/.verify_tmp.php:6
Stack trace:
#0 /home/z/my-project/php-course/content/php/modern-php/never-return-type/.verify_tmp.php(8): selamanya()
#1 {main}
  thrown in /home/z/my-project/php-course/content/php/modern-php/never-return-type/.verify_tmp.php on line 6

Eksekusi mencetak Bersiap berhenti..., lalu berhenti dengan Fatal error: Uncaught TypeError: selamanya(): never-returning function must not implicitly return. Fungsi jatuh ke akhir badannya tanpa exit maupun throw — itu persis yang dilarang deklarasi never. Varian yang lebih terang-terangan, pernyataan return; di dalam function never, bahkan ditolak saat kompilasi dengan pesan A never-returning function must not return — program tidak akan berjalan sama sekali.

never sebagai Tipe Paling Bawah #

Di teori type system, never adalah bottom type: subtype dari semua tipe. Konsekuensi praktisnya, pemanggilan function never boleh ditempatkan di posisi yang menuntut nilai tipe apa pun — karena pemanggilan tersebut dipastikan tidak pernah menghasilkan nilai:

php
<?php
function gagal(string $pesan): never
{
    throw new RuntimeException($pesan);
}

function ambilNama(array $data): string
{
    return $data['nama'] ?? gagal("kunci 'nama' tidak tersedia");
}

try {
    echo ambilNama(['nama' => 'Siti']), "\n";
    echo ambilNama([]), "\n";
} catch (RuntimeException $e) {
    echo 'Terjadi galat: ', $e->getMessage(), "\n";
}

Output yang Dihasilkan:

text
Siti
Terjadi galat: kunci 'nama' tidak tersedia

Function ambilNama() ber-return-type string, tetapi sisi kanan ?? berisi pemanggilan gagal() yang bertipe never — sah, karena cabang itu hanya dievaluasi saat nilai kiri null, dan saat itu terjadi, exception sudah terbang sebelum ada “nilai” yang perlu dikembalikan. Bandingkan posisinya dengan void, yang tampak mirip tetapi berbeda makna:

Aspekvoidnever
ArtiKembali tanpa nilaiTidak kembali sama sekali
Eksekusi pemanggilBerlanjut ke baris berikutnyaTidak pernah berlanjut
Diakhiri olehreturn; atau akhir badan functionthrow atau exit
Dipakai dalam ekspresiTidak bolehBoleh di posisi nilai apa pun

Dividen Terbesar: Static Analysis #

Runtime hanyalah separuh cerita never; separuh lainnya hidup di static analysis. Perhatikan function validasi berikut:

php
<?php
function gagal(string $pesan): never
{
    throw new RuntimeException($pesan);
}

function validasiUsia(?int $usia): int
{
    if ($usia === null || $usia < 0) {
        gagal('usia wajib diisi dan tidak boleh negatif');
    }

    return $usia;
}

try {
    echo validasiUsia(25), "\n";
    echo validasiUsia(null), "\n";
} catch (RuntimeException $e) {
    echo 'Terjadi galat: ', $e->getMessage(), "\n";
}

Output yang Dihasilkan:

text
25
Terjadi galat: usia wajib diisi dan tidak boleh negatif

Bagi PHPStan dan Psalm, deklarasi never pada gagal() mengubah cara membaca validasiUsia(): jalur yang melewati gagal() pasti tidak sampai ke baris return $usia, sehingga baris tersebut hanya tercapai bila $usia sudah pasti int positif. Tanpa never, analyzer menuduh return $usia bisa mengembalikan null — false positive yang memaksa kita menulis pemeriksaan ulang atau penegasan tipe. Dengan never, alur analisis menjadi presisi.

Dua manfaat lain mengikuti pola yang sama. Pertama, kode yang berada setelah pemanggilan function never pasti tak terjangkau — PHPStan menandainya sebagai dead code, menangkap baris “cadangan” yang tertinggal setelah redirect() atau exit. Kedua, never menjadi teman pemeriksaan exhaustiveness: helper seperti function tidakMungkin(mixed $nilai): never yang dipasang di arm default membuat match yang lupa menangani satu enum langsung terdeteksi analyzer, bukan menunggu UnhandledMatchError di production.

Ringkasan #

Return type never memberi nama pada kategori function yang selama ini tak terdokumentasi: yang selalu throw, yang selalu exit. Runtime menegakkan janjinya dengan TypeError bila function mencoba kembali normal, teori type system menempatkannya sebagai bottom type yang sah di posisi nilai mana pun, dan static analyzer mengubah janji itu menjadi analisis alur yang lebih akurat — lebih sedikit false positive, lebih cepat menemukan dead code. Dengan ini rangkaian fitur modern per-chapter di section ini lengkap; peta fitur per versi PHP dan strategi kompatibilitas lintas versi menunggu di chapter penutup section.

Referensi #