Pengaturan error_reporting dan display_errors pada chapter sebelumnya bersifat pasif: PHP hanya memilih antara menampilkan atau menyembunyikan laporannya. Padahal ada kalanya kita ingin bereaksi secara aktif setiap kali warning terjadi — mencatatnya dengan format milik sendiri, mengirim notifikasi ke tim, atau mengubahnya menjadi exception agar bisa ditangkap try-catch. PHP menyediakan set_error_handler() untuk menyisipkan logika kita tepat di antara terjadinya error dan reaksi bawaan PHP. Chapter ini membahas cara memasangnya, semantik nilai kembaliannya, pola klasik konversi ke ErrorException, sampai batas-batasnya.

Memasang Handler Kustom #

Fungsi set_error_handler() menerima sebuah callable lalu menjadikannya pengganti mekanisme pelaporan bawaan PHP. Setiap kali error yang bisa ditangani terjadi — warning, notice, deprecated, atau error yang kita picu sendiri — PHP memanggil callable tersebut dengan empat argumen secara berurutan:

ParameterTipeIsi
$errnointLevel error sebagai nilai integer, misalnya 2 untuk E_WARNING
$errstrstringTeks pesan error, misalnya Undefined variable $stok
$errfilestringNama berkas tempat error terjadi
$errlineintNomor baris tempat error terjadi

Mari kita pasang handler pertama kita:

php
<?php
function catatError(int $errno, string $errstr, string $errfile, int $errline): bool
{
    echo "[TERCATAT] level $errno: $errstr di $errfile baris $errline\n";
    return true;
}

set_error_handler('catatError');

echo $stok;
trigger_error("Stok buku tinggal 3 buah", E_USER_WARNING);
echo "Program tetap berjalan sampai habis\n";

Output yang Dihasilkan:

text
[TERCATAT] level 2: Undefined variable $stok di /home/z/my-project/php-course/content/php/error-handling/error-handlers/.verify_tmp.php baris 10
[TERCATAT] level 512: Stok buku tinggal 3 buah di /home/z/my-project/php-course/content/php/error-handling/error-handlers/.verify_tmp.php baris 11
Program tetap berjalan sampai habis

Mari kita telaah baris-baris kode di atas:

  1. Baris 2-7: function catatError() mendeklarasikan keempat parameter dengan tipe sesuai tabel di atas, mencetak satu baris laporan berformat sendiri, lalu mengembalikan true.
  2. Baris 9: set_error_handler('catatError') memasang handler tersebut — PHP menerima nama function sebagai string, sama seperti callable lainnya.
  3. Baris 11: pembacaan $stok yang tidak terdefinisi memicu warning; alih-alih mencetak format bawaan, PHP memanggil handler kita dengan $errno bernilai 2 (E_WARNING).
  4. Baris 12: trigger_error() memicu error secara sengaja dengan level E_USER_WARNING (nilai 512) — cara yang tepat untuk menguji handler tanpa harus menulis kode yang salah.
  5. Baris 13: baris ini tetap tercetak karena handler tidak menghentikan eksekusi; perannya hanya menggantikan cara PHP melaporkan.

Nilai true yang dikembalikan handler memberi tahu PHP bahwa laporan sudah sepenuhnya kita urus — itulah sebabnya output di atas tidak lagi memuat format bawaan Warning: .... Nama berkas .verify_tmp.php yang muncul pada $errfile adalah berkas sementara alat verifikasi otomatis buku ini; saat Anda menjalankan kode dari berkas sendiri, nilai parameter itu mengikuti nama berkas Anda.

Karena $errno datang sebagai integer, handler yang perlu bereaksi berbeda untuk keluarga level tertentu cukup memakai operasi bitwise:

php
<?php
if ($errno & (E_WARNING | E_USER_WARNING)) {
    // tangani semua warning, baik dari engine maupun yang dipicu trigger_error()
}

Makna Nilai Kembalian true dan false #

Handler wajib mengembalikan nilai yang jelas karena PHP menafsirkannya sebagai keputusan. Mengembalikan true berarti laporan sudah selesai ditangani dan mekanisme bawaan PHP dilewati seluruhnya — pesan tidak ditampilkan dan tidak dicatat ke log bawaan. Mengembalikan false berarti handler hanya ingin “menyisipkan diri” sebelum reaksi bawaan tetap berjalan:

php
<?php
function catatLaluLimpahkan(int $errno, string $errstr): bool
{
    echo "[HANDLER KITA] menerima: $errstr\n";
    return false;
}

set_error_handler('catatLaluLimpahkan');

echo $stok;
echo "Program tetap berjalan\n";

Output yang Dihasilkan:

text
[HANDLER KITA] menerima: Undefined variable $stok

Warning: Undefined variable $stok in /home/z/my-project/php-course/content/php/error-handling/error-handlers/.verify_tmp.php on line 10
Program tetap berjalan

Handler kita mencetak laporannya lebih dulu, lalu warning bawaan PHP tetap tampil setelahnya — keduanya berurutan dalam output karena return false menyerahkan kendali kembali ke mekanisme bawaan. Eksekusi program tetap berlanjut sampai baris terakhir karena warning tidak pernah mematikan program.

restore_error_handler dan Perilaku Tumpukan #

Handler disimpan dalam bentuk tumpukan (stack). Setiap pemanggilan set_error_handler() menambah satu handler baru di posisi paling atas dan menjadikannya yang aktif, sementara restore_error_handler() melepas handler teratas sehingga handler di bawahnya aktif kembali:

php
<?php
function handlerCadangan(int $errno, string $errstr): bool
{
    echo "[CADANGAN] $errstr\n";
    return true;
}

function handlerUtama(int $errno, string $errstr): bool
{
    echo "[UTAMA] $errstr\n";
    return true;
}

set_error_handler('handlerCadangan');
set_error_handler('handlerUtama');

echo $stok;
restore_error_handler();

echo $sisaStok;

Output yang Dihasilkan:

text
[UTAMA] Undefined variable $stok
[CADANGAN] Undefined variable $sisaStok

Pada pemanggilan set_error_handler() kedua, handlerUtama menumpuk di atas handlerCadangan sehingga warning dari echo $stok ditangani handlerUtama. Setelah restore_error_handler() melepas handler teratas, warning kedua dari echo $sisaStok kembali ditangani handlerCadangan. Pola pasang-lepas ini berguna untuk mengubah kebijakan pelaporan hanya pada satu bagian alur program lalu memulihkan kebijakan sebelumnya.

Mencatat Error dari Dalam Handler #

Kombinasi yang paling sering dipakai di aplikasi nyata adalah handler yang mencatat laporan ke berkas log melalui error_log(), lalu mengembalikan true agar layar tetap bersih. Pada contoh berikut kita memakai closure agar nama berkas log bisa dibawa masuk lewat klausa use:

php
<?php
$berkasLog = sys_get_temp_dir() . "/handler-kasir.log";
@unlink($berkasLog);

set_error_handler(
    function (int $errno, string $errstr, string $errfile, int $errline) use ($berkasLog): bool {
        error_log("[$errno] $errstr di $errfile baris $errline\n", 3, $berkasLog);
        return true;
    }
);

echo $stok;
echo "Layar tetap bersih, bukti tersimpan di berkas log:\n";
echo file_get_contents($berkasLog);
unlink($berkasLog);

Output yang Dihasilkan:

text
Layar tetap bersih, bukti tersimpan di berkas log:
[2] Undefined variable $stok di /home/z/my-project/php-course/content/php/error-handling/error-handlers/.verify_tmp.php baris 12

Mari kita telaah baris-baris kode di atas:

  1. Baris 2-3: lokasi berkas log ditentukan di direktori sementara sistem, dan @unlink() menghapus sisa latihan sebelumnya agar isi log selalu mulai dari kosong.
  2. Baris 5-10: closure handler menulis satu baris laporan melalui error_log() dengan tipe 3 — menambahkan pesan ke berkas tujuan — lalu mengembalikan true.
  3. Baris 12: warning dari pembacaan $stok kini tidak meninggalkan apa pun di layar; buktinya justru berada di dalam berkas log yang dibaca kembali pada baris 14.

Pembahasan tipe-tipe tujuan error_log() serta format log yang baik menyusul pada chapter logging-errors . Yang perlu dicatat di sini: penekan @ pada @unlink() bekerja berdampingan dengan handler — error yang ditekan @ tetap diteruskan ke handler kustom, jadi keduanya tidak saling meniadakan.

Pola Klasik: Warning Menjadi ErrorException #

Ada satu keterbatasan mendasar dari warning: ia tidak bisa ditangkap try-catch karena bukan exception — perbedaan dua dunia ini telah kita bedah pada chapter error-vs-exception . Solusi klasiknya adalah handler yang melempar ErrorException setiap kali error terjadi, sehingga error level apa pun tiba-tiba bisa ditangkap layaknya exception:

php
<?php
function ubahJadiException(int $errno, string $errstr, string $errfile, int $errline): bool
{
    throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
}

set_error_handler('ubahJadiException');

try {
    $total = $hargaSatu * $jumlahBeli;
} catch (ErrorException $e) {
    echo "Tertangkap sebagai exception: " . $e->getMessage() . "\n";
}

echo "Alur program kembali terkontrol penuh\n";

Output yang Dihasilkan:

text
Tertangkap sebagai exception: Undefined variable $hargaSatu
Alur program kembali terkontrol penuh

Mari kita telaah baris-baris kode di atas:

  1. Baris 2-5: handler tidak pernah sampai pada return — ia langsung melempar ErrorException dengan pesan asli ($errstr) serta level, berkas, dan baris asal sebagai atribut.
  2. Baris 9: perkalian $hargaSatu * $jumlahBeli memicu warning karena kedua variabel belum terdefinisi; handler mengubahnya menjadi lemparan ErrorException.
  3. Baris 10-12: blok catch kini bisa menangkap kejadian tersebut dan membaca pesannya lewat getMessage() — presisi yang mustahil didapat dari warning biasa.
  4. Baris 15: eksekusi berlanjut normal setelah catch, bukan berhenti di titik warning.

Batasan: Error yang Tidak Bisa Ditangani Handler #

Handler kustom hanya dipanggil untuk error yang bersifat pulih (recoverable): E_WARNING, E_NOTICE, E_DEPRECATED, dan keluarga E_USER_*. PHP sama sekali tidak memanggil handler untuk E_ERROR — error fatal runtime seperti pemanggilan function yang tidak ada — maupun E_PARSE yang terjadi sebelum program sempat berjalan, sehingga handler belum terpasang:

php
<?php
function catatSemua(int $errno, string $errstr): bool
{
    echo "[HANDLER] seharusnya saya dipanggil: $errstr\n";
    return true;
}

set_error_handler('catatSemua');

echo "Sebelum pemanggilan\n";
kirimEmailKePelanggan();
echo "Baris ini tidak pernah tercetak\n";

Output yang Dihasilkan:

text
Sebelum pemanggilan

Fatal error: Uncaught Error: Call to undefined function kirimEmailKePelanggan() in /home/z/my-project/php-course/content/php/error-handling/error-handlers/.verify_tmp.php:11
Stack trace:
#0 {main}
  thrown in /home/z/my-project/php-course/content/php/error-handling/error-handlers/.verify_tmp.php on line 11

Handler sudah terpasang rapi, tetapi pemanggilan kirimEmailKePelanggan() yang tidak ada sama sekali tidak melaluinya. PHP menghentikan program seketika dengan Fatal error: Uncaught Error, menampilkan stack trace, dan mengakhiri eksekusi dengan exit code 255 — baris setelah pemanggilan tidak pernah tercetak. Error fatal semacam ini harus dicegah dengan kode yang benar, bukan ditangani handler.

Kabar baiknya, fatal error seperti di atas sebenarnya dilempar sebagai objek Error yang merupakan Throwable — artinya masih bisa dijaring oleh set_exception_handler(). Handler exception inilah jaring pengaman terakhir PHP, topik chapter berikutnya.

Ringkasan #

set_error_handler() menggantikan mekanisme pelaporan bawaan dengan callable milik kita yang menerima level, pesan, berkas, dan baris kejadian. Nilai kembalian true menelan laporan bawaan sepenuhnya, false meneruskannya, dan handler bertumpuk dengan restore_error_handler() sebagai juru lepas. Pola klasik ErrorException menjembatani dunia error dan exception sehingga warning bisa ditangkap try-catch, sementara handler yang mencatat ke log menyiapkan fondasi bagi pembahasan logging. Error fatal tetap berada di luar jangkauan handler — dan untuk itulah PHP menyediakan exception handler sebagai jaring terakhir.

Referensi #