Di PHP, kamu menaruh kode yang mungkin gagal di dalam try { ... } dan menangani kegagalannya di catch (Exception $e) { ... }. Kalau ada yang melempar exception di blok try, PHP langsung melompat ke catch yang cocok, dan $e->getMessage() memberi tahu apa yang salah. Kalau tidak ada yang melempar exception, blok catch dilewati.
Ubah divide(10, 0) menjadi divide(10, 5) lalu jalankan lagi: tidak ada exception, jadi not printed dicetak dan blok catch dilewati. Tanpa try, exception itu akan menghentikan script dengan fatal error "Uncaught". Cara melempar exception sendiri dan menulis class exception ada di halaman exception.
Sintaks try catch
Bentuk lengkapnya punya tiga bagian. Hanya try yang wajib, ditambah setidaknya satu catch atau sebuah finally:
try {
// code that may throw
} catch (SomeException $e) {
// runs if SomeException (or a subclass) was thrown
} catch (OtherException | ThirdException $e) {
// runs for either of these types
} finally {
// always runs, thrown or not
}
Tipe di catch menentukan apa yang ditangkapnya: class itu sendiri dan setiap class yang meng-extend-nya. Sejak PHP 8.0, variabelnya opsional kalau kamu tidak membutuhkannya: catch (JsonException) { ... }.
Menangkap Exception vs Error vs Throwable
Sejak PHP 7, kegagalan yang dideteksi PHP sendiri (memanggil method yang tidak ada, tipe argumen yang salah, pembagian dengan nol) melempar Error, bukan Exception, dan PHP 8 mengubah banyak warning lama menjadi TypeError dan ValueError. Keduanya mengimplementasikan interface Throwable, tetapi tidak saling meng-extend, jadi catch (Exception $e) tidak menangkap Error:
Throwable
├── Exception (problems a program expects and handles)
│ ├── InvalidArgumentException, RuntimeException, JsonException, ...
└── Error (engine errors, usually a bug in the code)
├── TypeError
├── ValueError
├── ArithmeticError
│ └── DivisionByZeroError
└── UnhandledMatchError, ...
Jalankan ini untuk melihat catch mana yang menangani setiap masalah:
Jadi, mana yang sebaiknya ditangkap?
catch (SomeSpecificException $e)kalau kamu tahu apa yang bisa salah dan apa yang harus dilakukan. Ini berlaku untuk kebanyakan kode.catch (Exception $e)untuk "apa pun yang sengaja dilempar oleh library atau kodeku".catch (Throwable $e)di level paling luar (request handler, job runner) untuk mencatat setiap kegagalan, termasuk error dari PHP sendiri, dan menampilkan pesan yang ramah.
Menangkap beberapa tipe exception
Tulis beberapa blok catch untuk menangani tipe yang berbeda dengan cara yang berbeda. PHP mencobanya dari atas ke bawah dan memakai yang pertama cocok, jadi tipe yang spesifik harus ditulis sebelum parent-nya, kalau tidak, parent-nya akan menangkap semuanya lebih dulu. Kalau beberapa tipe ditangani dengan cara yang sama, gabungkan dalam satu catch dengan |:
Ketiga class itu meng-extend Exception, jadi satu catch (Exception $e) juga bisa menangkap semuanya, tetapi dengan begitu kodenya tidak lagi bisa menjawab 404 untuk yang satu dan 400 untuk yang lain.
finally: kode yang selalu dijalankan
Blok finally dijalankan setelah try dan catch, bagaimanapun keduanya berakhir: dengan normal, dengan exception yang tertangkap, dengan exception yang tidak ditangkap siapa pun, atau dengan return. Inilah tempat untuk melepas apa pun yang diambil blok try: menutup file, melepas lock, menghentikan timer.
Untuk good.txt, file ditutup sebelum saved dicetak, karena finally dijalankan sebelum fungsi benar-benar mengembalikan nilai. Untuk bad.txt, tidak ada catch di dalam process(): finally menutup file, lalu exception-nya terus naik ke catch milik pemanggil.
Hindari return di dalam finally: return itu menggantikan nilai yang dikembalikan blok try dan diam-diam membuang exception yang sedang dalam perjalanan keluar:
function f(): string
{
try {
return 'from try';
} finally {
return 'from finally';
}
}
function g(): string
{
try {
throw new RuntimeException('lost');
} finally {
return 'no exception reaches the caller';
}
}
echo f(); // from finally
echo g(); // no exception reaches the caller
Melempar ulang atau membungkus exception
Kadang blok catch hanya bisa menangani sebagian masalah: mencatatnya, membersihkan sesuatu, lalu membiarkannya berlanjut. throw $e; melempar ulang exception yang sama. Yang lebih sering kamu inginkan adalah menambahkan konteks sambil tetap menyimpan penyebab aslinya, dan untuk itulah argumen ketiga constructor, $previous, dibuat:
Pemanggil cukup berurusan dengan satu ConfigException dan tidak perlu tahu bahwa konfigurasinya berupa JSON, sementara getPrevious() menyimpan detail tingkat rendahnya untuk log.
Exception yang tidak tertangkap dan handler global
Exception yang tidak ditangani catch mana pun menghentikan script dengan fatal error yang berisi class, pesan, file dan baris, serta stack trace:
<?php
// No try/catch anywhere in this file
function charge(int $cents): void
{
if ($cents <= 0) {
throw new DomainException("Amount must be positive, got $cents");
}
}
charge(500);
echo "first charge ok\n";
charge(-1);
echo "never printed\n";
PHP mencetak first charge ok lalu:
PHP Fatal error: Uncaught DomainException: Amount must be positive, got -1 in /home/index.php:6
Stack trace:
#0 /home/index.php(12): charge()
#1 {main}
thrown in /home/index.php on line 6
Di situs yang sudah live, pesan itu tidak boleh sampai ke pengunjung. set_exception_handler() mendaftarkan fungsi yang menerima setiap exception yang tidak ditangkap siapa pun, satu tempat untuk mencatatnya dan mencetak halaman yang sopan:
Handler-nya dijalankan, lalu script berhenti; tidak ada cara untuk melanjutkan setelah throw. Apakah teks error dari PHP sendiri ditampilkan di halaman atau tidak diatur oleh display_errors, yang dibahas di error reporting.
Warning bukan exception
try/catch hanya melihat hal-hal yang dilempar. Banyak fungsi PHP lama tidak melempar exception: fungsi-fungsi itu mengembalikan false atau null dan, paling banyak, mencetak warning. json_decode() adalah contoh yang umum, dan membungkusnya dengan try tidak mengubah apa pun sampai kamu memintanya untuk melempar exception:
Untuk fungsi yang tidak punya flag seperti itu, periksa nilai kembaliannya (if ($handle === false)), atau periksa prasyaratnya lebih dulu (file_exists(), isset()). set_error_handler() yang melempar ErrorException mengubah setiap warning menjadi exception sungguhan kalau kamu ingin satu aturan untuk semuanya.
Resep: memvalidasi input formulir dengan exception
Pola yang praktis: fungsi validasi melempar exception, lalu halaman menangkapnya dan menampilkan pesannya di samping formulir. Jalankan, lalu ketik umur ke dalam formulir dan tekan Send: script yang sama berjalan lagi dengan $_POST yang sudah terisi.
Coba kolom kosong, twelve, 9, dan 30. Setiap aturan yang gagal melempar exception dengan pesannya sendiri, dan satu catch mengubah semuanya menjadi baris merah yang sama. Halaman formulir mengembangkan pola ini dengan beberapa kolom dan pesan error di samping masing-masing kolom.
Pertanyaan yang sering diajukan
Bagaimana cara kerja try catch di PHP?
PHP menjalankan kode di dalam try { }. Kalau ada yang melempar exception di sana, PHP melewati sisa blok try dan menjalankan blok catch pertama yang tipenya cocok, dengan objek yang dilempar di dalam variabelnya: catch (Exception $e) { echo $e->getMessage(); }. Kalau tidak ada yang melempar exception, blok catch dilewati.
Bagaimana cara menangkap semua exception dan error di PHP?
Tangkap Throwable: catch (Throwable $e). catch (Exception $e) hanya menangkap exception, bukan error dari engine seperti TypeError, ValueError, atau DivisionByZeroError, yang meng-extend Error. Exception dan Error sama-sama mengimplementasikan Throwable.
Bagaimana cara menangkap beberapa exception dalam satu blok catch di PHP?
Pisahkan tipenya dengan garis tegak: catch (InvalidArgumentException | RangeException $e). Kamu juga bisa menulis beberapa blok catch berurutan; PHP memakai yang pertama cocok, jadi taruh tipe yang paling spesifik lebih dulu.
Apakah finally tetap dijalankan kalau ada return di dalam try?
Ya. finally dijalankan setelah try dan catch, entah kodenya melakukan return, melempar exception, atau selesai dengan normal, termasuk saat try berisi return. Kalau finally sendiri mengembalikan nilai, nilai itu menggantikan nilai dari try, jadi hindari return di dalam finally.
Apakah try catch bisa menangkap warning di PHP?
Tidak. Warning dan notice (seperti membaca key array yang tidak ada) bukan exception, jadi catch tidak pernah melihatnya. Periksa kondisinya sebelum pemanggilan, pakai mode exception milik fungsi itu kalau tersedia (json_decode(..., flags: JSON_THROW_ON_ERROR)), atau ubah warning dengan set_error_handler() dan ErrorException.