Dokumen ini menjelaskan cara membuat aturan validasi kustom secara langsung di dalam controller menggunakan Closure. Metode ini sangat efektif untuk logika validasi yang spesifik untuk satu controller dan tidak memerlukan reuse di tempat lain, sehingga menjaga kode tetap ringkas dan mudah dibaca.
Sebagai contoh, kita akan mengimplementasikan validasi untuk mencegah pembuatan atau pembaruan jadwal terapi yang waktunya tumpang tindih dengan jadwal lain dalam paket terapi dan hari yang sama.
- Controller Anda mewarisi dari
BaseControlleryang sudah memiliki properti$rules. - Aturan validasi dasar sudah didefinisikan di dalam Model terkait (contoh:
TherapySchedule::$rules).
Langkah pertama adalah meng-override metode store (untuk pembuatan data baru) dan update (untuk pembaruan data) di dalam controller Anda.
// app/Http/Controllers/Therapy/TherapyScheduleController.php
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
// ...
public function store(Request $request): JsonResponse
{
// Logika validasi akan ditambahkan di sini
return parent::store($request);
}
public function update($id): JsonResponse
{
// Logika validasi akan ditambahkan di sini
return parent::update($id);
}Di dalam metode yang telah di-override, kita akan menambahkan aturan validasi baru ke properti $this->rules. Karena aturan validasi dari model bisa berupa string ('required|date_format:H:i'), kita perlu mengubahnya menjadi array terlebih dahulu untuk menghindari error [] operator not supported for strings.
// Di dalam metode store()
// Pastikan aturan adalah array
if (is_string($this->rules['start_time'])) {
$this->rules['start_time'] = explode('|', $this->rules['start_time']);
}
// Tambahkan closure sebagai aturan validasi baru
$this->rules['start_time'][] = function ($attribute, $value, $fail) use ($request) {
$this->validateNoTimeOverlap($request, $fail);
};$attribute: Nama field yang divalidasi (start_time).$value: Nilai dari field tersebut.$fail: Sebuah Closure yang dipanggil jika validasi gagal.use ($request): Mengimpor variabel$requestke dalam scope Closure.
Untuk menjaga kode tetap bersih (DRY - Don't Repeat Yourself), kita buat sebuah metode privat yang berisi logika validasi utama. Metode ini akan dipanggil dari Closure di store dan update.
// app/Http/Controllers/Therapy/TherapyScheduleController.php
private function validateNoTimeOverlap(Request $request, \Closure $fail, int $ignoreId = null): void
{
$query = \App\Models\TherapySchedule::where('therapy_package_id', $request->therapy_package_id)
->where('day_of_week', $request->day_of_week)
->where(function ($q) use ($request) {
// Kondisi untuk memeriksa tumpang tindih waktu
// (start_time_baru < end_time_lama) AND (end_time_baru > start_time_lama)
$q->where('start_time', '<', $request->end_time)
->where('end_time', '>', $request->start_time);
});
// Jika sedang dalam mode update, abaikan data yang sedang diedit
if ($ignoreId) {
$query->where('id', '!=', $ignoreId);
}
// Jika ditemukan data yang cocok, validasi gagal
if ($query->exists()) {
$fail('Jadwal yang dimasukkan tumpang tindih dengan jadwal yang sudah ada.');
}
}Berikut adalah implementasi penuh pada TherapyScheduleController:
<?php
namespace App\Http\Controllers\Therapy;
use Illuminate\Http\Request;
use App\Models\TherapySchedule;
use Illuminate\Http\JsonResponse;
use App\Http\Controllers\BaseController;
class TherapyScheduleController extends BaseController
{
// ... constructor ...
public function store(Request $request): JsonResponse
{
$request->merge(['is_active' => $request->has('is_active') ? 1 : 0]);
if (is_string($this->rules['start_time'])) {
$this->rules['start_time'] = explode('|', $this->rules['start_time']);
}
$this->rules['start_time'][] = function ($attribute, $value, $fail) use ($request) {
$this->validateNoTimeOverlap($request, $fail);
};
return parent::store($request);
}
public function update($id): JsonResponse
{
$this->request->merge(['is_active' => $this->request->has('is_active') ? 1 : 0]);
if (is_string($this->rules['start_time'])) {
$this->rules['start_time'] = explode('|', $this->rules['start_time']);
}
$this->rules['start_time'][] = function ($attribute, $value, $fail) use ($id) {
$this->validateNoTimeOverlap($this->request, $fail, $id);
};
return parent::update($id);
}
private function validateNoTimeOverlap(Request $request, \Closure $fail, int $ignoreId = null): void
{
$query = TherapySchedule::where('therapy_package_id', $request->therapy_package_id)
->where('day_of_week', $request->day_of_week)
->where(function ($q) use ($request) {
$q->where('start_time', '<', $request->end_time)
->where('end_time', '>', $request->start_time);
});
if ($ignoreId) {
$query->where('id', '!=', $ignoreId);
}
if ($query->exists()) {
$fail('Jadwal yang dimasukkan tumpang tindih dengan jadwal yang sudah ada.');
}
}
}Menggunakan Closure untuk validasi kustom adalah cara yang rapi dan efisien untuk menangani logika yang unik tanpa harus membuat kelas Rule terpisah. Ini membuat controller tetap menjadi sumber utama untuk logika bisnis yang spesifik, sementara validasi yang lebih umum tetap berada di dalam model.