Seluruh materi section ini bermuara pada satu pertanyaan praktis: apa yang terjadi ketika aplikasi gagal di produksi, di depan pengunjung nyata? Kegagalan tidak bisa dihapus — koneksi database terputus, berkas rusak, layanan pihak ketiga lambat — tetapi reaksi aplikasi terhadap kegagalan bisa kita rancang. Tujuannya ganda: pengunjung tetap nyaman dengan pesan yang jelas tanpa istilah teknis, sementara developer tetap menerima bukti lengkap untuk memperbaiki akar masalah. Chapter penutup ini merangkai semua alat yang telah kita pelajari — konfigurasi php.ini, error handler, exception handler, dan logging — menjadi satu strategi produksi yang utuh.
Prinsip: Pisahkan Pengalaman Pengunjung dari Kebutuhan Developer #
Perilaku bawaan PHP menampilkan pesan error mentah langsung ke output, dan hal itu sangat membantu saat pengembangan. Di produksi, kebiasaan yang sama justru berbahaya karena pesan mentah membocorkan hal-hal yang membantu penyerang memahami aplikasi Anda:
- Path absolut di server, misalnya
/var/www/aplikasi/src/pesanan.php, membeberkan struktur direktori. - Stack trace menyingkap nama function, class, dan alur internal kode.
- Pesan kegagalan database bisa memuat potongan query beserta nama tabel.
- Nomor versi PHP dan library yang terpasang mempermudah pencarian celah keamanan yang diketahui.
Karena itu prinsip dasarnya sederhana: pesan detail mengalir ke log untuk developer, sementara layar pengunjung hanya menerima pesan umum yang menenangkan. Kedua arus ini tidak boleh tertukar — menampilkan detail kepada pengunjung sama berbahayanya dengan mencatat detail yang salah untuk developer.
Konfigurasi Dasar Produksi #
Titik awal strategi adalah konfigurasi yang telah kita bahas pada chapter error-reporting
, kali ini dalam bentuk finalnya untuk php.ini produksi:
display_errors = Off
display_startup_errors = Off
log_errors = On
error_log = /var/log/aplikasi/php-error.logEmpat baris ini memindahkan seluruh laporan PHP dari layar ke berkas log: pesan error, warning, sampai kegagalan saat PHP dinyalakan semuanya tercatat, tidak ada yang tampil ke pengunjung. Pastikan direktori /var/log/aplikasi/ dapat ditulis oleh pengguna yang menjalankan PHP dan tidak dapat dibaca dari web. Dengan konfigurasi ini, error_reporting boleh tetap E_ALL — semakin lengkap catatan untuk developer, semakin cepat akar masalah ditemukan.
Mendeteksi Lingkungan Runtime #
Aplikasi yang sama biasanya berjalan di dua dunia: melalui web server untuk melayani pengunjung, dan melalui CLI untuk skrip cron atau perintah administratif. Kebijakan error yang tepat untuk keduanya berbeda — di CLI, pesan yang tampil di terminal justru membantu. Konstanta PHP_SAPI dan function php_sapi_name() memberi tahu kita sedang berjalan di mode mana:
<?php
echo "SAPI saat ini: " . php_sapi_name() . "\n";
$appEnv = getenv("APP_ENV") ?: "production";
echo "Lingkungan aplikasi: $appEnv\n";
if (PHP_SAPI === "cli") {
echo "Mode CLI: pesan error boleh tampil langsung di terminal\n";
} else {
echo "Mode web: pesan error harus disembunyikan dari pengunjung\n";
}Output yang Dihasilkan:
SAPI saat ini: cli
Lingkungan aplikasi: production
Mode CLI: pesan error boleh tampil langsung di terminalPada lingkungan latihan kita, php_sapi_name() mengembalikan cli karena kode dijalankan dari terminal. Aplikasi web dengan PHP-FPM umumnya mengembalikan fpm-fcgi, sedangkan web server bawaan php -S mengembalikan cli-server. Baris getenv("APP_ENV") membaca variabel lingkungan yang lazim dipakai untuk menandai tahap aplikasi — nilainya diambil dari production ketika variabel tersebut tidak diatur, jadi kebijakan aman berlaku secara bawaan. Menjalankan skrip yang sama dengan APP_ENV=development akan membalik kebijakannya tanpa mengubah satu baris kode pun.
Halaman Error yang Ramah Pengunjung #
Setelah pesan mentah disembunyikan, pengunjung yang menabrak kegagalan tetap perlu jawaban. Halaman error yang baik memuat tiga unsur: permintaan maaf yang jujur, penjelasan tanpa istilah teknis, dan kode referensi yang menghubungkan laporan pengunjung dengan baris log:
<?php
function buatHalamanError(int $kodeHttp, string $pesanRamah, string $kodeReferensi): string
{
http_response_code($kodeHttp);
$html = "<!DOCTYPE html>\n"
. "<html lang=\"id\">\n"
. "<head><meta charset=\"utf-8\"><title>Terjadi Kendala</title></head>\n"
. "<body>\n"
. " <h1>Maaf, terjadi kendala</h1>\n"
. " <p>$pesanRamah</p>\n"
. " <p>Kode referensi: $kodeReferensi</p>\n"
. "</body>\n"
. "</html>\n";
return $html;
}
echo buatHalamanError(
503,
"Sistem kami sedang dalam pemeliharaan. Silakan coba lagi dalam 5 menit.",
"ERR-2026-0115"
);Output yang Dihasilkan:
<!DOCTYPE html>
<html lang="id">
<head><meta charset="utf-8"><title>Terjadi Kendala</title></head>
<body>
<h1>Maaf, terjadi kendala</h1>
<p>Sistem kami sedang dalam pemeliharaan. Silakan coba lagi dalam 5 menit.</p>
<p>Kode referensi: ERR-2026-0115</p>
</body>
</html>Mari kita telaah bagian-bagian utamanya:
- Baris 4:
http_response_code($kodeHttp)mengirim status HTTP yang benar —503memberi tahu browser dan mesin pencari bahwa kendala ada di pihak server, bukan pada permintaan pengunjung. - Baris 6-14: halaman dirakit sebagai string HTML sederhana dengan pesan yang manusiawi dan tanpa satu pun detail teknis.
- Baris 19-22: pemanggil cukup memilih kode status, pesan, dan kode referensi — function ini bisa dipakai ulang untuk
500(kegagalan umum) maupun503(pemeliharaan).
Kode status HTTP sendiri mengikuti kesepakatan yang baku: awalan 5xx berarti server sedang bermasalah, sedangkan 4xx berarti permintaan pengunjung yang salah — halaman 404 untuk URL yang tidak ditemukan adalah contoh 4xx yang paling akrab. Pada framework modern, halaman semacam ini biasanya sudah tersedia sebagai berkas template terpisah yang dapat dikustomisasi, tetapi prinsip isinya tetap sama.
Jaring Pengaman Terakhir #
Sekarang kita rangkai semuanya: exception handler dari chapter exception-handlers
sebagai penangkap terakhir, error_log() dari chapter logging-errors
sebagai pencatat bukti, dan pesan ramah sebagai penutup untuk pengunjung:
<?php
$berkasLog = sys_get_temp_dir() . "/produksi-kasir.log";
@unlink($berkasLog);
set_exception_handler(function (Throwable $e) use ($berkasLog): void {
// aplikasi nyata: $kodeReferensi = strtoupper(bin2hex(random_bytes(4)));
$kodeReferensi = "ERR-2026-0115-8F3A";
error_log(
sprintf(
"[%s] %s: %s di %s baris %d\n",
$kodeReferensi,
get_class($e),
$e->getMessage(),
$e->getFile(),
$e->getLine()
),
3,
$berkasLog
);
echo "Maaf, terjadi kendali. Kode referensi: $kodeReferensi\n";
});
throw new RuntimeException("Koneksi database timeout setelah 5 detik");Output yang Dihasilkan:
Maaf, terjadi kendali. Kode referensi: ERR-2026-0115-8F3AAlur kerjanya berjalan seperti ini: exception RuntimeException terlepas tanpa catch, sehingga handler terakhir mengambil kendali. Handler menulis satu baris log berisi kode referensi, kelas exception, pesan, berkas, dan baris kejadian — bentuknya persis seperti [ERR-2026-0115-8F3A] RuntimeException: Koneksi database timeout ... di ... — lalu menampilkan pesan ramah kepada pengunjung. Ketika seorang pengunjung menelepon dukungan dan menyebut kode ERR-2026-0115-8F3A, tim cukup mencari kode itu di log untuk menemukan kejadian persisnya.
Pada aplikasi web sesungguhnya, dua penyesuaian diperlukan. Pertama, kode referensi dibuat acak untuk setiap kejadian — misalnya strtoupper(bin2hex(random_bytes(4))) — seperti tercantum pada komentar; contoh di atas memakai nilai tetap agar hasil latihannya konsisten. Kedua, echo diganti dengan http_response_code(500) diikuti pembacaan template halaman error yang kita rancang di atas. Pemasangannya juga dipindah ke berkas masuk aplikasi (front controller) agar meliputi seluruh request sejak awal.
Monitoring dan Peringatan Dini #
Log yang lengkap tetap sia-sia bila tidak ada yang membacanya sampai pengunjung komplain. Aplikasi produksi yang matang menambahkan lapisan monitoring yang membaca log dan memberi sinyal secara otomatis:
| Sinyal yang dipantau | Contoh ambang | Tindakan otomatis |
|---|---|---|
Lonjakan baris ERROR per menit | dari puluhan menjadi ratusan | Kirim peringatan ke kanal tim (chat, pager) |
| Endpoint kesehatan gagal | /health bukan lagi 200 OK | Bangunkan petugas jaga |
| Ukuran berkas log | melebihi 80% kuota penyimpanan | Jalankan rotasi log |
| Kode referensi berulang | pola sama muncul berulang | Buat tiket dengan prioritas tinggi |
Endpoint kesehatan (health endpoint) layak dijelaskan sebentar karena paling mudah dibangun: satu URL khusus, misalnya /health, yang tugasnya memeriksa dependensi terpenting — koneksi database, penyimpanan berkas — lalu mengembalikan 200 OK bila semuanya sehat dan 503 bila ada yang bermasalah. Alat pemantauan eksternal seperti layanan uptime check memanggil URL ini setiap beberapa detik dari luar server, sehingga gangguan terdeteksi bahkan sebelum pengunjung sempat mengeluh. Peringatan dini dari lonjakan error bekerja serupa: alat pembaca log menghitung baris per satuan waktu dan mengirim notifikasi ketika hitungan menembus ambang yang tidak wajar.
Checklist Siap Produksi #
Rangkuman seluruh strategi ini cukup muat dalam satu tabel periksa. Jadikan tabel berikut sebagai pemeriksaan terakhir setiap kali aplikasi hendak dirilis:
| Item | Pengaturan yang benar |
|---|---|
display_errors | Off — tidak ada pesan mentah ke pengunjung |
log_errors | On dengan error_log menunjuk direktori khusus di luar folder publik |
| Level pelaporan | Tetap E_ALL — catatan untuk developer selengkap mungkin |
| Halaman error ramah | Tersedia untuk 500 dan 503, tanpa detail teknis |
| Kode referensi | Terbit di halaman pengunjung dan di baris log yang sama |
| Exception handler terakhir | Terpasang di front controller, mencatat lalu menampilkan pesan ramah |
| Monitoring | Endpoint kesehatan + peringatan lonjakan error |
| Rotasi log | Terjadwal agar berkas tidak membengkak tanpa batas |
| Uji kegagalan di staging | Lempar exception secara sengaja dan pastikan seluruh alur bekerja |
Baris terakhir pada tabel sering terlupakan padahal paling menentukan: strategi error handling adalah kode yang jarang berjalan, dan kode yang jarang berjalan pantas diuji secara sengaja. Di lingkungan staging, lemparkan exception tiruan dari beberapa titik aplikasi, lalu pastikan log terisi, kode referensi muncul, halaman ramah tampil, dan peringatan monitoring berbunyi.
Ringkasan #
Strategi error handling produksi berdiri di atas pemisahan dua arus: detail lengkap mengalir ke log untuk developer, pesan umum dan kode referensi mengalir ke layar untuk pengunjung. Konfigurasi php.ini memindahkan laporan PHP ke log, deteksi lingkungan menjaga CLI tetap nyaman dikerjakan, halaman ramah menjaga pengalaman pengunjung, exception handler terakhir merangkai semuanya, dan monitoring memastikan kegagalan terdengar sebelum menjadi keluhan. Dengan chapter ini, perjalanan kita di Section Error Handling genap — dari mengenali jenis error, menangkap exception, sampai merancang reaksi aplikasi yang matang di lapangan. Keterampilan ini akan terus terpakai di setiap aplikasi nyata yang Anda bangun selanjutnya.