Middleware adalah lapisan penyaring permintaan HTTP yang dijalankan sebelum dan sesudah rute aplikasi, sehingga pekerjaan lintas rute seperti pemeriksaan sesi, verifikasi akun, dan pencatatan dapat ditempatkan di satu tempat. Topik ini membahas cara pipeline middleware bekerja, cara membuat dan mendaftarkan middleware kustom pada aplikasi catatan, serta cara memberi parameter pada middleware. Setelah topik ini dibaca, urutan eksekusi setiap lapisan tidak lagi berupa kotak hitam, melainkan dapat disimulasikan dan diuji secara langsung.
Pipeline permintaan#
Sekumpulan middleware membentuk pipeline, yaitu rangkaian lapisan yang dilalui permintaan sebelum mencapai rute dan dilalui lagi dalam urutan terbalik setelah respons dibuat. Setiap middleware menerima dua argumen, yaitu objek permintaan dan closure bernama $next yang mewakili sisa pipeline. Kode sebelum pemanggilan $next($request) berjalan pada permintaan masuk, sedangkan kode setelahnya berjalan pada respons keluar.
Alur dua lapisan tersebut digambarkan pada diagram berikut.
Illuminate\Pipeline\Pipeline, yaitu kelas yang membangun pipeline yang sama seperti pipeline yang digunakan framework.
# membuktikan urutan eksekusi pipeline dengan dua lapisan
cd ~/aplikasi-catatan
php artisan tinker --execute='
use Illuminate\Pipeline\Pipeline;
class TandaAwal
{
public function handle($permintaan, Closure $next)
{
echo "TandaAwal: sebelum" . PHP_EOL;
$respons = $next($permintaan);
echo "TandaAwal: sesudah" . PHP_EOL;
return $respons;
}
}
class TandaAkhir
{
public function handle($permintaan, Closure $next)
{
echo "TandaAkhir: sebelum" . PHP_EOL;
$respons = $next($permintaan);
echo "TandaAkhir: sesudah" . PHP_EOL;
return $respons;
}
}
$hasil = app(Pipeline::class)
->send("permintaan catatan")
->through([TandaAwal::class, TandaAkhir::class])
->then(fn ($permintaan) => "sampai ke tujuan");
echo "akhir: " . $hasil . PHP_EOL;
'TandaAwal: sebelum
TandaAkhir: sebelum
TandaAkhir: sesudah
TandaAwal: sesudah
akhir: sampai ke tujuanKeluaran tersebut memperlihatkan pola yang konsisten, yaitu lapisan pertama mencetak keluarannya lebih dahulu, sedangkan keluaran penutupnya justru muncul paling akhir. Simulasi berikut memperlihatkan pola yang sama secara interaktif, termasuk apa yang terjadi ketika salah satu lapisan menghentikan permintaan sebelum mencapai rute. Jalankan simulasi, ubah pilihan setiap lapisan, kemudian amati urutan keluaran yang dihasilkan.
Membuat middleware kustom#
Middleware kustom dibuat dengan perintah make:middleware. Pada aplikasi catatan, sebuah middleware akan digunakan untuk memastikan pengguna yang mengakses catatan sudah memverifikasi emailnya.
# tidak-dijalankan
# membuat middleware bernama PastikanTerverifikasi
cd ~/aplikasi-catatan
php artisan make:middleware PastikanTerverifikasiPerintah tersebut menghasilkan berkas app/Http/Middleware/PastikanTerverifikasi.php beserta direktorinya apabila belum ada. Keluaran aslinya berbentuk berikut.
INFO Middleware [app/Http/Middleware/PastikanTerverifikasi.php] created successfully. Kerangka yang dihasilkan masih murni meneruskan permintaan tanpa melakukan apa pun. Isi berkasnya berbentuk berikut.
// tidak-dijalankan
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class PastikanTerverifikasi
{
/**
* Handle an incoming request.
*
* @param Closure(Request): (Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
return $next($request);
}
}Satu-satunya pekerjaan yang dilakukan kerangka tersebut adalah memanggil $next($request), sehingga permintaan lolos tanpa pemeriksaan. Logika pemeriksaan dituliskan dengan menggantikan baris tersebut, misalnya sebagai berikut.
// tidak-dijalankan
public function handle(Request $request, Closure $next): Response
{
if ($request->user()?->email_verified_at === null) {
return redirect("/email/verify");
}
return $next($request);
}Apabila pengguna belum memverifikasi email, middleware mengembalikan respons pengalihan dan pipeline berhenti di lapisan itu. Apabila email sudah terverifikasi, permintaan diteruskan ke lapisan berikutnya.
Pendaftaran: alias, grup, dan global#
Middleware yang sudah dibuat perlu didaftarkan sebelum dapat digunakan pada rute. Pendaftaran dilakukan pada metode withMiddleware() di berkas bootstrap/app.php.
// tidak-dijalankan
->withMiddleware(function (Middleware $middleware): void {
$middleware->alias([
"terverifikasi" => \App\Http\Middleware\PastikanTerverifikasi::class,
]);
})Setelah alias terdaftar, rute dapat menggunakannya dengan nama pendek, misalnya Route::get(...)->middleware("terverifikasi"). Laravel mengenali tiga cara pemasangan middleware yang masing-masing memiliki cakupan berbeda.
| Cara pemasangan | Cakupan | Cara menulisnya |
|---|---|---|
| Global | Semua permintaan | Dipasang melalui $middleware->append() pada bootstrap/app.php. |
| Grup | Seluruh rute dalam satu grup, misalnya web atau api | Ditambahkan melalui $middleware->group() atau appendToGroup(). |
| Alias | Rute tertentu yang memintanya | Didaftarkan melalui $middleware->alias(), lalu dipasang pada rute. |
Grup web bawaan sudah memuat beberapa middleware penting, antara lain EncryptCookies, StartSession, dan SubstituteBindings yang mengisi parameter model pada rute. Karena itu, rute pada routes/web.php secara bawaan mengenal sesi dan binding model tanpa konfigurasi tambahan.
Parameter middleware#
Middleware dapat menerima parameter yang ditulis pada rute setelah tanda titik dua, misalnya batas:kerja. Parameter berguna ketika satu middleware digunakan untuk banyak keperluan dengan aturan yang sedikit berbeda. Contoh berikut membatasi akses rute berdasarkan kelompok yang dikirim melalui header permintaan.
// tidak-dijalankan
class BatasKategoriMiddleware
{
public function handle($request, Closure $next, string $grup)
{
if ($request->header("X-Grup") !== $grup) {
return response("akses ditolak untuk grup " . $grup, 403);
}
return $next($request);
}
}Perilakunya dapat diuji langsung dengan mendaftarkan alias saat aplikasi berjalan, kemudian mengirim dua permintaan yang berbeda ke rute yang sama.
# menguji middleware berparameter dengan dua permintaan
cd ~/aplikasi-catatan
php artisan tinker --execute='
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
class BatasKategoriMiddleware
{
public function handle($request, Closure $next, string $grup)
{
if ($request->header("X-Grup") !== $grup) {
return response("akses ditolak untuk grup " . $grup, 403);
}
return $next($request);
}
}
Route::aliasMiddleware("batas", BatasKategoriMiddleware::class);
Route::middleware("batas:kerja")->get("/demo-batas", function () {
return "selamat datang di kategori kerja";
});
$dengan = app()->handle(Request::create("/demo-batas", "GET", server: ["HTTP_X_GRUP" => "kerja"]));
echo $dengan->getStatusCode() . ": " . $dengan->getContent() . PHP_EOL;
$tanpa = app()->handle(Request::create("/demo-batas", "GET"));
echo $tanpa->getStatusCode() . ": " . $tanpa->getContent() . PHP_EOL;
'200: selamat datang di kategori kerja
403: akses ditolak untuk grup kerjaPermintaan dengan header X-Grup: kerja lolos dengan status 200, sedangkan permintaan tanpa header ditolak dengan status 403. Lebih dari satu parameter dituliskan pada rute dengan pemisah koma, misalnya batas:kerja,editor, dan setiap parameter menjadi argumen tambahan setelah $next.
Kesalahan umum#
Kesalahan pertama adalah lupa mengembalikan hasil $next($request). Middleware yang tidak mengembalikan respons membuat permintaan berakhir dengan respons kosong tanpa jejak error yang jelas karena pipeline menunggu nilai kembalian dari setiap lapisan. Pastikan setiap cabang handle() mengembalikan hasil $next() atau respons yang dibuat sendiri.
Kesalahan kedua adalah menggunakan alias yang belum didaftarkan pada bootstrap/app.php. Rute yang menggunakannya gagal dengan respons 500 saat diakses, dan penyebabnya hanya terlihat pada log aplikasi. Periksa daftar alias setiap kali alias baru dituliskan pada rute.
Kesalahan ketiga adalah menuliskan kode penting setelah return $next($request) tanpa menampung nilainya. Kode tersebut tidak pernah dijalankan karena baris return langsung mengakhiri metode. Apabila ada pekerjaan yang perlu dilakukan pada respons keluar, tampung hasil $next($request) terlebih dahulu, terapkan pekerjaan tersebut pada objek responsnya, kemudian kembalikan.
Kesalahan keempat adalah menempatkan middleware global pada setiap rute tanpa menimbang beban. Middleware global dieksekusi pada seluruh permintaan, termasuk permintaan yang nantinya ditolak rutenya, sehingga pekerjaan berat semacam pembacaan database yang hanya dibutuhkan satu kelompok rute menjadi beban bagi seluruh aplikasi. Middleware semacam itu lebih tepat dipasang pada grup atau rute tertentu, dan pemeriksaan php artisan route:list yang menampilkan middleware setiap rute membantu menilai apakah penempatannya sudah tepat.
Ringkasan#
- Middleware membentuk pipeline dua arah, yaitu kode sebelum
$next($request)berjalan pada permintaan masuk dan kode setelahnya pada respons keluar. - Middleware kustom dibuat dengan
make:middleware, diisi pada metodehandle(), lalu didaftarkan sebagai alias, anggota grup, atau middleware global padabootstrap/app.php. - Parameter middleware dituliskan pada rute setelah tanda titik dua dan diterima sebagai argumen tambahan setelah
$next. - Middleware yang menghentikan permintaan cukup mengembalikan responsnya sendiri tanpa memanggil
$next, dan lapisan-lapisan di luarnya tetap menjalankan kode pada sisi respons.
Referensi#
- Middleware — Laravel — Dokumentasi resmi middleware, pendaftaran, grup, dan parameter.
- Routing — Laravel — Dokumentasi pemasangan middleware pada rute tertentu.