Custom Exception di Route Model Binding Laravel
Salah satu fitur yang cukup meningkatkan produktivitas pemrogram (programmer) ketika berurusan dengan model adalah Route Model Binding. Model binding sendiri sebenarnya sudah pernah diulas di pos Model Binding di Laravel. Mari kita segarkan kembali bahasan tentang model binding dan penanganan kesalahannya agar meningkatnya pemahaman kita akan fitur ini.
Basic Routing
Kita mulai dari sebuah route sederhana dan method dalam controller yang berfungsi untuk melakukan pencarian detail data user.
Route::get('/user/{id}', 'UserController@show')
->where('id', '[0-9]+')
->name('user.show');
Bisa dilihat pada potongan skrip di atas, di mana argumen kedua merupakan parameter yang dilempar ke method di controller untuk diproses sebagai identifier untuk pencarian data. Jika ditulis dalam dalam bentuk URL, maka akan menghasilkan terjemahan sebagai berikut:
http://domain.com/user/1
http://domain.com/user/2
http://domain.com/user/3...dan seterusnya
Kemudian, untuk menangkap parameter tersebut, di method bisa kalian tuliskan sebagai berikut:
/**
* Show user detail
* @param integer $id
* @return View
*/
public function show($id): \Illuminate\View\View
{
$user = \App\User::where('id', $id)
->first();
return view('user', compact('user'));
}
Sampai di level ini, manajemen exception cukup mudah ditangani. Misal dalam diagram alur terdapat sebuah logika: jika user tidak ditemukan, maka akan menampilkan sebuah pesan kesalahan tertentu. Tentunya, langkah yang perlu dilakukan hanyalah menambah pengecekan jika variabel $user
tidak kosong.
if (empty($user)) {
abort(404, 'Data user tidak ditemukan.');
}
// atau dengan skrip yang lebih singkat
abort_if(empty($user), 404, 'Data user tidak ditemukan.');
Route Model Binding
Berpindah menggunakan fitur model binding di Laravel dengan mengubah beberapa baris skrip di atas menjadi seperti berikut.
Route::get('/user/{user}', 'UserController@show')->name('user.show');
Di bagian route, kita hanya mengubah nama parameter yang sebelumnya {id} menjadi {user}. Sampai di sini, bentuk URL masih sama dengan URL contoh pada skrip di atas.
Kemudian, pada method show()
dalam controller, kita menggunakan argumen dengan type-hint berupa model User
, yang mana parameter yang dilempar ke method show()
harus merupakan object instance dari class User
.
public function show(\App\User $user): \Illuminate\View\View
{
return view('user.show', compact('user'));
}
Di dalam method, kita tidak perlu melakukan pencarian data dengan menambahkan skrip secara manual. Laravel otomatis akan melakukan pencarian berdasarkan type-hint yang kita gunakan pada argumen method. Dalam contoh kasus, Laravel otomatis akan melakukan pencarian data user dengan identifier id
menggunakan model User
berdasarkan parameter dari URL.
Seandainya data user tidak ditemukan, maka Laravel otomatis mengembalikan exception seperti pada gambar berikut.
Kasus yang sering ditanyakan ketika kita ingin membuat custom exception. Bagaimana jika kita ingin menampilkan pesan khusus di halaman? Atau, bagaimana jika kita ingin menampilkan pesan berupa JSON dibanding HTML?
Jika kalian menambahkan handler di controller dengan melakukan pengecekan variabel $user
, maka hal tersebut dipastikan gagal. Karena respons seperti gambar di atas akan dikembalikan terlebih dahulu oleh framework, sebelum logika yang kalian buat dieksekusi.
Jadi, solusinya adalah exception handler yang ada di berkas app\Exceptions\Handler.php
.
Di berkas Handler.php
, sebenarnya kita bisa melakukan pengecekan kesalahan apa saja yang ditimbulkan oleh framework. yang perlu kita ketahui adalah, exception (class) apa yang dilempar ke handler.
Untuk memahaminya langkah demi langkah, mari buka lihat terlebih dahulu isi berkas dari Handler.php
. Dalam berkas tersebut terdapat beberapa method, namun kita fokus pada method render()
yang berfungsi untuk menangkap kesalahan (exception) dan mengembalikan respons HTTP.
/**
* Render an exception into an HTTP response.
*
* @param \Illuminate\Http\Request $request
* @param \Exception $exception
* @return \Illuminate\Http\Response
*/
public function render($request, Exception $exception)
{
return parent::render($request, $exception);
}
Dalam method render()
terdapat dua argumen, yang pertama adalah $request
yang merupakan objek dari class Request
(Illuminate\Http\Request
). Dari object $request
ini, kalian bisa mendapatkan informasi HTTP yang sedang berjalan, misal mendapatkan URL, code, dan lain sebagainya.
Argumen kedua merupakan $exception
dengan type-hint class Exception
. Dari argumen kedua inilah kita akan melakukan pengecekan apabila route model binding seperti kasus di atas datanya tidak ditemukan. Lantas, bagaimana caranya mengetahui class exception yang dilempar oleh model route binding tersebut?
Ini sedikit tricky. Kalau kalian tipikal pemrogram yang malas untuk membuka sumber API Laravel, kalian bisa melakukan debugging langsung nama class yang dilempar oleh route model binding ke exception handler.
/**
* Render an exception into an HTTP response.
*
* @param \Illuminate\Http\Request $request
* @param \Exception $exception
* @return \Illuminate\Http\Response
*/
public function render($request, Exception $exception)
{
dd(get_class($exception));
return parent::render($request, $exception);
}
Jika aplikasi kalian jalankan melalui peramban, maka akan menampilkan sebuah string nama class yang dilempar oleh route model binding.
Illuminate\Database\Eloquent\ModelNotFoundException
Lihat? Sampai di sini kalian sudah bisa menemukan class exception yang digunakan oleh route model binding. langkah selanjutnya melakukan pengecekan, apabila yang diterima oleh handler merupakan class ModelNotFoundException
, maka akan menampilkan pesan kesalahan tertentu.
Pengecekannya sendiri mernggunakan language constructor instanceof yang ada di PHP.
/**
* Render an exception into an HTTP response.
*
* @param \Illuminate\Http\Request $request
* @param \Exception $exception
* @return \Illuminate\Http\Response
*/
public function render($request, Exception $exception)
{
if ($exception instanceof \Illuminate\Database\Eloquent\ModelNotFoundException) {
return abort('404', 'Data dalam model tidak ditemukan.');
}
return parent::render($request, $exception);
}
Walau hasil akhirnya hanya pesannya yang berbeda, tapi dari sini, kita punya kontrol lebih untuk menangani pesan kesalahan yang diterima oleh handler. Selain itu, kalian juga dapat menentukan sendiri respons yang ingin dikembalikan/ditampilkan.
Lantas, bagaimana jika saya ingin mengembalikan data berupa JSON?
Eww, hal ini sebenarnya mudah, cukup perlu kreativitas kalian saja dalam mengeksplorasi fitur. Sebagai bocoran, berikut contoh skripnya.
/**
* Render an exception into an HTTP response.
*
* @param \Illuminate\Http\Request $request
* @param \Exception $exception
* @return \Illuminate\Http\Response
*/
public function render($request, Exception $exception)
{
if ($exception instanceof \Illuminate\Database\Eloquent\ModelNotFoundException) {
return response()->json([
'status' => 404,
'message' => 'Data tidak ditemukan.',
]);
}
return parent::render($request, $exception);
}
Ngomong-ngomong, di Handler.php kita bisa melakukan pengecekan exception apa saja (tidak melulu dari model). Jika ada banyak exception yang perlu kita cek, maka harus menumpuknya di sana?
Jawabannya iya. Sebenarnya tidak ada yang salah menjadikan exception handler pada satu skrip. Kendalanya ke depan mungkin masalah perawatan dan keterbacaan skrip. Untungnya, tim pengembang Laravel menyadari betul hal ini, maka dirilis lah sebuah fitur pemisahan exception. Bahasan ini akan saya jelaskan pada pos terpisah.
Jika kalian tidak sabar untuk mempelajarinya, bahasan ini bisa kalian baca di pos Laravel News pada bagian Exception Rendering.
***
Pos kali ini cukup panjang untuk dibaca dan dipelajari. Tulisannya sendiri memuat cara pembuatan dan penggunaan route model binding di Laravel. Padahal, solusi dari kasus di atas cuma menambahkan beberapa baris skrip PHP.
Saya berharap, tanpa perlu membuka halaman lain, kalian dapat memahami tata cara langkah demi langkah pembuatan route model binding di Laravel, kasus yang biasa dialami, serta bagaimana menyelesaikannya. Itulah kenapa, saya jabarkan ulang walau sebagian bahasan pernah ditulis di pos sebelumnya.
Originally published at www.laravel.web.id on August 7, 2017.