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:
| Aspek | Error mesin | Exception |
|---|---|---|
| Pelapor | Mesin PHP atau function bawaan | Kode aplikasi melalui throw |
| Wujud | Pesan diagnostik non-objek dengan level E_* | Objek Throwable (cabang Error atau Exception) |
| Cara menangani | Konfigurasi error_reporting, error handler khusus | Blok try-catch-finally |
Dapat ditangkap try-catch | Tidak untuk Warning/Notice; sebagian besar fatal berubah jadi objek Error sejak PHP 7 | Ya, melalui tipe yang cocok |
| Jejak call stack | Tidak dibawa oleh pesan | Tersimpan otomatis, bisa diambil via getTrace() |
| Contoh | Warning: Undefined variable $x | throw 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
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:
Input '25' diterima, usia: 25
Input 'tujuh belas' ditolak: Usia harus berupa angka bulat.Mari kita bedah contoh di atas:
- Baris 3 (
filter_var(..., FILTER_VALIDATE_INT)): function filter mengembalikan angka jika input valid, ataufalsejika tidak. Titik inilah batas antara “data dari dunia luar” dan “data yang sudah bersih”. - Baris 7 dan 10 (dua
throw): dua jenis kesalahan sengaja dibedakan tipenya.InvalidArgumentExceptionuntuk input yang bukan angka, danDomainExceptionuntuk angka yang melanggar aturan usia minimal. Pemanggil boleh menanggapi keduanya secara berbeda. - Baris 18 (multi-catch): pada contoh ini keduanya ditangani sama, sehingga multi-catch
InvalidArgumentException | DomainExceptioncukup. Karena keduanya berada di cabangException, tidak adaErroryang 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
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:
Rp75.000
Data rusak terdeteksi: formatHarga(): Argument #1 ($harga) must be of type int, string givenPemanggilan 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
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:
Warning berubah menjadi exception: Undefined variable $judulHalaman
Eksekusi program tetap berlanjut sampai selesai.Mari kita telaah alurnya:
- Baris 2 (
set_error_handler(...)): callback menerima nomor level error dan pesannya, lalu membungkus keduanya ke dalamErrorExceptiondan melemparnya. Mulai baris ini, semuaWarningdanNoticepada kode di bawahnya berhenti sekadar tercetak. - Baris 7 (
strlen($judulHalaman)): variabel$judulHalamanbelum pernah diberi nilai, sehingga mesin PHP menerbitkanWarning: Undefined variable. Alih-alih tercetak, warning tersebut berubah menjadiErrorExceptionyang langsung ditangkap blokcatch. - Baris 12 (
restore_error_handler()difinally): handler dilepas kembali agar pemanggil berikutnya tidak terpengaruh kebijakan konversi ini. Menyatukan pemulihan di blokfinallymenjamin 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:
| Situasi | Mekanisme yang tepat | Alasan |
|---|---|---|
| Validasi input dari pengguna | throw 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 spesifik | Pemanggil butuh respons khusus per jenis pelanggaran |
| Tipe argumen salah pada function internal | Biarkan TypeError muncul | Tanda bug; perbaiki kode atau validasi di hulu |
| Nilai di luar batas fungsi internal | Biarkan ValueError muncul | Bug atau data belum tersaring di sumbernya |
| Titik batas wajib tahan banting (job queue, handler HTTP) | catch selektif Error + Throwable di batas | Satu data rusak tidak boleh menjatuhkan seluruh proses |
Warning/Notice mesin yang ingin dikelola serius | Konversi 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.