PHP memiliki dua mekanisme pelaporan kegagalan yang hidup berdampingan: error mesin yang berupa pesan diagnostik seperti Warning dan Notice, serta exception yang berupa objek yang bisa dilempar dan ditangkap. Selama section ini kita bergulat dengan keduanya secara bergantian, tetapi belum pernah membandingkannya secara berdampingan. Pada chapter ini kita rapikan perbedaan keduanya, menetapkan kapan masing-masing mekanisme adalah pilihan yang tepat, dan melihat cara menjembatani keduanya dengan set_error_handler().

Dua Mekanisme Pelaporan Kegagalan #

Error mesin adalah warisan sejarah PHP sebelum era exception: ketika sesuatu tidak beres, mesin PHP atau function bawaan menerbitkan pesan dengan level tertentu (E_WARNING, E_NOTICE, E_DEPRECATED, dan seterusnya), lalu eksekusi berlanjut atau berhenti sesuai tingkat keparahannya. Mekanisme ini bersifat prosedural dan tidak berbasis objek; peringkat level beserta cara mengonfigurasinya sudah kita bahas tuntas pada chapter error-levels. Exception, di sisi lain, adalah objek yang mengimplementasikan interface Throwable: objek tersebut membawa pesan, kode, dan jejak call stack, serta dapat ditangkap dengan try-catch. Tabel berikut merangkum perbedaan keduanya:

AspekError mesinException
PelaporMesin PHP atau function bawaanKode aplikasi melalui throw
WujudPesan diagnostik non-objek dengan level E_*Objek Throwable (cabang Error atau Exception)
Cara menanganiKonfigurasi error_reporting, error handler khususBlok try-catch-finally
Dapat ditangkap try-catchTidak untuk Warning/Notice; sebagian besar fatal berubah jadi objek Error sejak PHP 7Ya, melalui tipe yang cocok
Jejak call stackTidak dibawa oleh pesanTersimpan otomatis, bisa diambil via getTrace()
ContohWarning: Undefined variable $xthrow new RuntimeException(...)

Sejak PHP 7, kegagalan fatal yang dulu menghentikan program tanpa ampun (seperti memanggil function pada null) dirombak menjadi objek cabang Error yang bisa ditangkap. Warisan lama yang tetap tidak bisa ditangkap try-catch adalah pesan non-fatal seperti Warning dan Notice; untuk itulah kita perlu error handler yang akan kita intip di bagian akhir chapter.

Exception untuk Kegagalan yang Diantisipasi #

Kegagalan yang masuk akal terjadi dalam operasi normal aplikasi adalah wilayah Exception. Pengguna mengisi form dengan salah, saldo tidak cukup, file konfigurasi hilang: semuanya adalah keadaan bisnis yang sudah diperkirakan sejak desain, dan pemanggil wajib punya rencana tanggapan. Contoh berikut memvalidasi input pengguna dan melempar exception dengan tipe yang sesuai untuk tiap jenis kesalahan:

php
<?php
function validasiUsia($input): int
{
    $usia = filter_var($input, FILTER_VALIDATE_INT);

    if ($usia === false) {
        throw new InvalidArgumentException("Usia harus berupa angka bulat.");
    }
    if ($usia < 17) {
        throw new DomainException("Usia minimal 17 tahun untuk mendaftar.");
    }

    return $usia;
}

foreach (["25", "tujuh belas"] as $input) {
    try {
        echo "Input '$input' diterima, usia: " . validasiUsia($input) . "\n";
    } catch (InvalidArgumentException | DomainException $e) {
        echo "Input '$input' ditolak: " . $e->getMessage() . "\n";
    }
}

Output yang Dihasilkan:

text
Input '25' diterima, usia: 25
Input 'tujuh belas' ditolak: Usia harus berupa angka bulat.

Mari kita bedah contoh di atas:

  1. Baris 3 (filter_var(..., FILTER_VALIDATE_INT)): function filter mengembalikan angka jika input valid, atau false jika tidak. Titik inilah batas antara “data dari dunia luar” dan “data yang sudah bersih”.
  2. Baris 7 dan 10 (dua throw): dua jenis kesalahan sengaja dibedakan tipenya. InvalidArgumentException untuk input yang bukan angka, dan DomainException untuk angka yang melanggar aturan usia minimal. Pemanggil boleh menanggapi keduanya secara berbeda.
  3. Baris 18 (multi-catch): pada contoh ini keduanya ditangani sama, sehingga multi-catch InvalidArgumentException | DomainException cukup. Karena keduanya berada di cabang Exception, tidak ada Error yang ikut tertangkap.

Perhatikan pola pentingnya: kegagalan yang diantisipasi tidak menghentikan program secara keseluruhan. Input pertama diterima, input kedua ditolak dengan pesan yang jelas, dan loop tetap selesai dengan rapi.

Error untuk Kode yang Rusak #

Cabang Error menandakan sesuatu yang berbeda: kode yang rusak, bukan keadaan bisnis yang diantisipasi. Ketika TypeError muncul karena string masuk ke parameter int, perbaikannya adalah memperbaiki kode atau menambah validasi di hulu, bukan menangkap error tersebut di setiap titik pemanggilan. Meski begitu, ada satu pola sah bernama defensive programming: menangkap Error di titik batas sistem untuk menjaga satu data rusak tidak menjatuhkan seluruh proses:

php
<?php
declare(strict_types=1);

function formatHarga(int $harga): string
{
    return "Rp" . number_format($harga, 0, ",", ".");
}

function renderKartuProduk(array $produk): string
{
    // Nilai "harga" berasal dari sumber eksternal dan tidak divalidasi
    // sebelum sampai ke sini — inilah kondisi yang harus dijaga.
    return formatHarga($produk["harga"]);
}

try {
    echo renderKartuProduk(["harga" => 75000]) . "\n";
    echo renderKartuProduk(["harga" => "gratis"]) . "\n";
} catch (TypeError $e) {
    $pesan = strstr($e->getMessage(), ", called in", true) ?? $e->getMessage();
    echo "Data rusak terdeteksi: $pesan\n";
}

Output yang Dihasilkan:

text
Rp75.000
Data rusak terdeteksi: formatHarga(): Argument #1 ($harga) must be of type int, string given

Pemanggilan pertama menghasilkan Rp75.000 secara normal. Pemanggilan kedua melempar TypeError dari dalam formatHarga(), dan tangkapan defensif di pemanggil memutuskan bahwa satu kartu produk rusak cukup dilaporkan tanpa menjatuhkan seluruh halaman. Dua hal perlu diingat dari pola ini: penangkapan Error harus selektif pada tipenya (di sini hanya TypeError), dan penangkapan tersebut bukan alasan untuk menunda perbaikan akar masalah, yaitu validasi data hulu yang hilang.

Mengonversi Error menjadi Exception #

Dua dunia ini bisa dijembatani. Function set_error_handler() memasang callback yang dipanggil setiap kali mesin PHP menerbitkan error non-fatal; di dalam callback tersebut kita bebas melempar ErrorException, sehingga Warning yang biasanya hanya tercetak berubah menjadi exception yang bisa ditangkap try-catch:

php
<?php
set_error_handler(function (int $nomor, string $pesan): bool {
    throw new ErrorException($pesan, 0, $nomor);
});

try {
    echo strlen($judulHalaman) . "\n";
} catch (ErrorException $e) {
    echo "Warning berubah menjadi exception: " . $e->getMessage() . "\n";
} finally {
    restore_error_handler();
}

echo "Eksekusi program tetap berlanjut sampai selesai.\n";

Output yang Dihasilkan:

text
Warning berubah menjadi exception: Undefined variable $judulHalaman
Eksekusi program tetap berlanjut sampai selesai.

Mari kita telaah alurnya:

  1. Baris 2 (set_error_handler(...)): callback menerima nomor level error dan pesannya, lalu membungkus keduanya ke dalam ErrorException dan melemparnya. Mulai baris ini, semua Warning dan Notice pada kode di bawahnya berhenti sekadar tercetak.
  2. Baris 7 (strlen($judulHalaman)): variabel $judulHalaman belum pernah diberi nilai, sehingga mesin PHP menerbitkan Warning: Undefined variable. Alih-alih tercetak, warning tersebut berubah menjadi ErrorException yang langsung ditangkap blok catch.
  3. Baris 12 (restore_error_handler() di finally): handler dilepas kembali agar pemanggil berikutnya tidak terpengaruh kebijakan konversi ini. Menyatukan pemulihan di blok finally menjamin handler selalu dilepas meskipun terjadi kegagalan lain.

Banyak framework modern memasang konversi seperti ini secara global di titik awal aplikasi agar seluruh kegagalan bisa ditangani dengan satu pola try-catch. Ada batas pentingnya: error level E_ERROR yang benar-benar fatal tetap tidak melewati error handler, sehingga tidak bisa dikonversi. Konfigurasi handler secara penuh, termasuk tanda tangan callback yang lengkap, akan kita bahas pada chapter error-handlers.

Tabel Keputusan #

Setelah mengenal karakter kedua mekanisme, kita bisa menyusun keputusan ringkas. Tabel berikut memetakan situasi yang paling sering dijumpai ke mekanisme yang tepat:

SituasiMekanisme yang tepatAlasan
Validasi input dari penggunathrow Exception (keluarga Logic atau custom)Kegagalan normal alur bisnis; pemanggil wajib menanggapi
Kegagalan sumber daya eksternal (database, API, berkas)throw Exception (keluarga Runtime)Wajar terjadi saat runtime; layak dipulihkan dengan retry atau fallback
Aturan domain dilanggar (saldo, kuota, usia)Custom exception bertipe spesifikPemanggil butuh respons khusus per jenis pelanggaran
Tipe argumen salah pada function internalBiarkan TypeError munculTanda bug; perbaiki kode atau validasi di hulu
Nilai di luar batas fungsi internalBiarkan ValueError munculBug atau data belum tersaring di sumbernya
Titik batas wajib tahan banting (job queue, handler HTTP)catch selektif Error + Throwable di batasSatu data rusak tidak boleh menjatuhkan seluruh proses
Warning/Notice mesin yang ingin dikelola seriusKonversi via set_error_handler()Menyatukan semua kegagalan ke pola try-catch

Ringkasan #

Error mesin adalah pesan diagnostik prosedural untuk kondisi mesin dan function bawaan, sedangkan exception adalah objek Throwable untuk kegagalan yang dirancang dan diantisipasi oleh kode aplikasi. Aturan praktisnya: kegagalan yang diantisipasi dilempar sebagai Exception, kode yang rusak dibiarkan mengeluarkan Error lalu diperbaiki, dan ErrorException menjembatani Warning ke dunia try-catch. Dengan kerangka keputusan ini, kita siap naik ke lapisan berikutnya: memasang handler global yang mengelola kegagalan apa pun yang lolos dari seluruh try-catch di aplikasi.

Referensi #