MODUL PEMROGRAMAN WEBSITE 2
Mata Kuliah: Pemrograman Website 2
Kode MK: INF2419
SKS: 3 (Praktikum)
Semester: Genap 2025/2026
Program Studi: Informatika
Fakultas: FEBI / Saintek
Universitas: UIN K.H. Abdurrahman Wahid Pekalongan
Dosen Pengampu: Mohammad Reza Maulana, M.Kom
NIP: 199110082025051002
Pertemuan: 12 dari 16
Durasi: 150 menit (3 Ã 50 menit)
Studi Kasus Berkelanjutan: Sistem Manajemen Perpustakaan
PERTEMUAN 12
CRUD BUKU DENGAN LARAVEL
A. INFORMASI PERTEMUAN
| Aspek | Keterangan |
|---|---|
| Capaian Pembelajaran Lulusan (CPL) | CPL06: Mempunyai pengetahuan dalam mengembangkan algoritma/metode yang diimplementasikan dalam perangkat lunak. |
| Capaian Pembelajaran Mata Kuliah (CPMK) | CPMK06.1: Mampu mengimplementasikan backend web menggunakan PHP dan Laravel yang terintegrasi dengan database. |
| Sub-CPMK | Sub-CPMK06.1.2: Mengimplementasikan CRUD menggunakan Laravel dan ORM (Eloquent). |
| Indikator Pencapaian | Mahasiswa mampu: 1. Membuat form untuk input data buku 2. Implementasi validasi form dengan Laravel validation 3. Menyimpan data buku ke database (Create) 4. Menampilkan data buku dari database (Read) 5. Membuat form edit dan update data buku (Update) 6. Menghapus data buku dari database (Delete) 7. Implementasi CSRF protection 8. Menampilkan flash messages 9. Error handling untuk CRUD operations |
| Alokasi Waktu | âĸ Teori: 60 menit âĸ Praktikum: 90 menit âĸ Total: 150 menit (3 Ã 50 menit) |
B. PENDAHULUAN
1. Deskripsi Singkat
Pertemuan keduabelas ini fokus pada implementasi lengkap operasi CRUD (Create, Read, Update, Delete) untuk data buku menggunakan Laravel 12. Mahasiswa akan mempelajari cara membuat form input, validasi data, menyimpan ke database, menampilkan, mengupdate, dan menghapus data dengan memanfaatkan Eloquent ORM dan fitur-fitur Laravel seperti Form Request validation, CSRF protection, dan flash messages.
2. Keterkaitan dengan Pertemuan Lain
Pertemuan ini merupakan kelanjutan dari pertemuan-pertemuan sebelumnya:
- Pertemuan 7: CRUD native PHP/MySQL - konsep dasar CRUD
- Pertemuan 10: Migration & Model - struktur database dan Eloquent
- Pertemuan 11: Controller & View - menampilkan data (Read operation)
- Pertemuan 12: [SEKARANG] Implementasi lengkap CRUD Buku
- Pertemuan 13: CRUD Anggota - replikasi pola yang sama
- Pertemuan 14: Transaksi - relasi antar tabel dengan CRUD
Progress MVC + CRUD:
Pertemuan 10 â Model (M)
Pertemuan 11 â Controller & View (C & V) - Read
Pertemuan 12 â CRUD Complete (Create, Update, Delete)3. Manfaat Pembelajaran
- Mahir membuat form dengan Laravel Blade
- Mampu implementasi validasi data yang robust
- Menguasai operasi database dengan Eloquent
- Memahami security (CSRF protection)
- Dapat memberikan feedback ke user (flash messages)
- Siap mengembangkan aplikasi web CRUD lainnya
4. Relevansi dengan Studi Kasus
Dalam sistem perpustakaan, CRUD Buku digunakan untuk:
- Create: Menambah buku baru ke koleksi perpustakaan
- Read: Melihat daftar dan detail buku (sudah di pertemuan 11)
- Update: Mengubah informasi buku (harga, stok, deskripsi)
- Delete: Menghapus buku yang sudah tidak tersedia
C. MATERI TEORI
1. CRUD Operations Overview
a. Apa itu CRUD?
CRUD adalah akronim dari Create, Read, Update, Delete - empat operasi dasar dalam manajemen data.
| Operation | SQL | HTTP Method | Laravel Method | Keterangan |
|---|---|---|---|---|
| Create | INSERT | POST | store() | Menambah data baru |
| Read | SELECT | GET | index(), show() | Menampilkan data |
| Update | UPDATE | PUT/PATCH | update() | Mengubah data |
| Delete | DELETE | DELETE | destroy() | Menghapus data |
b. Resource Controller Methods
Laravel Resource Controller menyediakan 7 methods standar untuk CRUD:
class BukuController extends Controller
{
public function index() // GET /buku - Tampil semua
public function create() // GET /buku/create - Form tambah
public function store() // POST /buku - Simpan data
public function show($id) // GET /buku/{id} - Tampil detail
public function edit($id) // GET /buku/{id}/edit - Form edit
public function update($id) // PUT /buku/{id} - Update data
public function destroy($id)// DELETE /buku/{id} - Hapus data
}c. CRUD Workflow
CREATE FLOW:
User â Form Create (create) â Submit â Validation â Store to DB â Redirect
READ FLOW:
User â Request â Controller â Model â Database â View â Response
UPDATE FLOW:
User â Form Edit (edit) â Submit â Validation â Update DB â Redirect
DELETE FLOW:
User â Confirm â Destroy â Delete from DB â Redirect2. Form Handling di Laravel
a. Membuat Form dengan Blade
Form Create/Edit Structure:
<form action="{{ route('buku.store') }}" method="POST">
@csrf
<div class="mb-3">
<label for="judul" class="form-label">Judul Buku</label>
<input type="text"
name="judul"
id="judul"
class="form-control @error('judul') is-invalid @enderror"
value="{{ old('judul') }}">
@error('judul')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<button type="submit" class="btn btn-primary">Simpan</button>
</form>Penjelasan Komponen:
- Action & Method:
<form action="{{ route('buku.store') }}" method="POST">action: URL tujuan submit (gunakan named route)method: HTTP method (POST untuk create, POST untuk update dengan spoofing)
- CSRF Token:
@csrf- Wajib untuk semua form POST/PUT/DELETE
- Proteksi dari Cross-Site Request Forgery
- Laravel otomatis validasi token ini
- Method Spoofing (untuk Update/Delete):
@method('PUT') <!-- Untuk update -->
@method('DELETE') <!-- Untuk delete -->- HTML form hanya support GET dan POST
- Laravel "spoof" method PUT/DELETE lewat hidden input
- Old Input:
value="{{ old('judul') }}"- Menampilkan input lama jika validasi gagal
- User tidak perlu input ulang semua field
- Error Messages:
@error('judul')
<div class="invalid-feedback">{{ $message }}</div>
@enderror- Menampilkan pesan error spesifik per field
- Terintegrasi dengan Laravel validation
- Conditional CSS Class:
class="form-control @error('judul') is-invalid @enderror"- Tambah class
is-invalidjika ada error - Bootstrap otomatis styling error state
b. Form Elements
Text Input:
<input type="text" name="judul" class="form-control" value="{{ old('judul') }}">Number Input:
<input type="number" name="stok" class="form-control" value="{{ old('stok', 0) }}">Textarea:
<textarea name="deskripsi" class="form-control" rows="4">{{ old('deskripsi') }}</textarea>Select Dropdown:
<select name="kategori" class="form-control">
<option value="">-- Pilih Kategori --</option>
<option value="Programming" {{ old('kategori') == 'Programming' ? 'selected' : '' }}>
Programming
</option>
<option value="Database" {{ old('kategori') == 'Database' ? 'selected' : '' }}>
Database
</option>
</select>Date Input:
<input type="date" name="tanggal_terbit" class="form-control" value="{{ old('tanggal_terbit') }}">File Upload (akan dipelajari lanjutan):
<input type="file" name="cover" class="form-control">3. Laravel Validation
a. Validation Rules
Laravel menyediakan banyak built-in validation rules:
Validation di Controller:
$validated = $request->validate([
'judul' => 'required|string|max:200',
'kategori' => 'required|in:Programming,Database,Web Design,Networking,Data Science',
'pengarang' => 'required|string|max:100',
'penerbit' => 'required|string|max:100',
'tahun_terbit' => 'required|integer|min:1900|max:' . date('Y'),
'isbn' => 'nullable|string|max:20|unique:buku,isbn',
'harga' => 'required|numeric|min:0',
'stok' => 'required|integer|min:0',
'deskripsi' => 'nullable|string',
]);Common Validation Rules:
| Rule | Deskripsi | Contoh |
|---|---|---|
required | Field wajib diisi | 'judul' => 'required' |
nullable | Field boleh kosong | 'isbn' => 'nullable' |
string | Harus string | 'judul' => 'string' |
integer | Harus integer | 'stok' => 'integer' |
numeric | Harus angka | 'harga' => 'numeric' |
email | Format email valid | 'email' => 'email' |
min:value | Nilai/panjang minimum | 'stok' => 'min:0' |
max:value | Nilai/panjang maksimum | 'judul' => 'max:200' |
between:min,max | Nilai antara min-max | 'harga' => 'between:0,1000000' |
in:foo,bar | Nilai harus dari list | 'kategori' => 'in:A,B,C' |
unique:table,column | Nilai harus unik | 'isbn' => 'unique:buku,isbn' |
exists:table,column | Nilai harus ada di DB | 'kategori_id' => 'exists:kategoris,id' |
date | Format tanggal valid | 'tanggal' => 'date' |
after:date | Tanggal setelah | 'tanggal_kembali' => 'after:tanggal_pinjam' |
before:date | Tanggal sebelum | 'tanggal_lahir' => 'before:today' |
b. Custom Error Messages
Default Error Messages:
$request->validate([
'judul' => 'required|max:200',
]);
// Error: "The judul field is required."Custom Messages:
$request->validate([
'judul' => 'required|max:200',
'harga' => 'required|numeric|min:0',
], [
'judul.required' => 'Judul buku wajib diisi.',
'judul.max' => 'Judul buku maksimal 200 karakter.',
'harga.required' => 'Harga buku wajib diisi.',
'harga.numeric' => 'Harga harus berupa angka.',
'harga.min' => 'Harga tidak boleh negatif.',
]);Custom Attribute Names:
$request->validate([
'judul' => 'required',
'pengarang' => 'required',
], [], [
'judul' => 'judul buku',
'pengarang' => 'nama pengarang',
]);
// Error: "The judul buku field is required."c. Validation dengan Form Request
Generate Form Request:
php artisan make:request StoreBukuRequestForm Request Class:
File: app/Http/Requests/StoreBukuRequest.php
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StoreBukuRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true; // Set true untuk allow semua user
}
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'kode_buku' => 'required|string|max:20|unique:buku,kode_buku',
'judul' => 'required|string|max:200',
'kategori' => 'required|in:Programming,Database,Web Design,Networking,Data Science',
'pengarang' => 'required|string|max:100',
'penerbit' => 'required|string|max:100',
'tahun_terbit' => 'required|integer|min:1900|max:' . date('Y'),
'isbn' => 'nullable|string|max:20',
'harga' => 'required|numeric|min:0',
'stok' => 'required|integer|min:0',
'deskripsi' => 'nullable|string',
'bahasa' => 'required|string|max:20',
];
}
/**
* Get custom error messages.
*/
public function messages(): array
{
return [
'kode_buku.required' => 'Kode buku wajib diisi.',
'kode_buku.unique' => 'Kode buku sudah digunakan.',
'judul.required' => 'Judul buku wajib diisi.',
'kategori.required' => 'Kategori wajib dipilih.',
'kategori.in' => 'Kategori tidak valid.',
'harga.required' => 'Harga buku wajib diisi.',
'harga.numeric' => 'Harga harus berupa angka.',
'stok.integer' => 'Stok harus berupa angka bulat.',
];
}
}Usage di Controller:
public function store(StoreBukuRequest $request)
{
// Validasi otomatis dijalankan
// Jika gagal, otomatis redirect back dengan error
Buku::create($request->validated());
return redirect()->route('buku.index')
->with('success', 'Buku berhasil ditambahkan!');
}Keuntungan Form Request:
- â Separation of concerns (validasi terpisah dari controller)
- â Reusable (bisa dipakai di multiple methods)
- â Clean controller code
- â Mudah di-test
- â Authorization logic terintegrasi
4. CSRF Protection
a. Apa itu CSRF?
CSRF (Cross-Site Request Forgery) adalah serangan dimana attacker membuat user melakukan aksi yang tidak diinginkan pada aplikasi dimana user ter-autentikasi.
Contoh Serangan CSRF:
<!-- Situs jahat (evil.com) -->
<form action="https://perpustakaan.com/buku/1" method="POST">
@method('DELETE')
<input type="hidden" name="confirm" value="yes">
</form>
<script>
document.forms[0].submit(); // Auto submit
</script>Jika user yang login ke perpustakaan.com mengunjungi evil.com, buku dengan ID 1 bisa terhapus tanpa sepengetahuan user!
b. CSRF Protection di Laravel
Laravel protect semua form POST/PUT/DELETE dengan CSRF token.
Cara Kerja:
- Laravel generate unique token per session
- Token disimpan di session
- Form harus include token via
@csrf - Laravel validasi token saat form di-submit
- Jika token tidak cocok, request ditolak (419 error)
Implementasi:
<form action="{{ route('buku.store') }}" method="POST">
@csrf
<!-- Form fields -->
</form>Laravel compile @csrf menjadi:
<input type="hidden" name="_token" value="random_token_string">Exclude Routes dari CSRF (jarang dipakai):
File: app/Http/Middleware/VerifyCsrfToken.php
protected $except = [
'webhook/*', // Route yang di-exclude
];5. Flash Messages
a. Apa itu Flash Messages?
Flash Messages adalah pesan sementara yang ditampilkan setelah user melakukan aksi (create, update, delete). Pesan ini hanya muncul sekali di page load berikutnya, kemudian hilang.
b. Set Flash Messages
Di Controller:
// Success message
return redirect()->route('buku.index')
->with('success', 'Buku berhasil ditambahkan!');
// Error message
return redirect()->back()
->with('error', 'Gagal menambahkan buku!');
// Info message
return redirect()->route('buku.show', $buku->id)
->with('info', 'Data buku telah diupdate.');
// Warning message
return redirect()->back()
->with('warning', 'Stok buku hampir habis!');
// Multiple messages
return redirect()->route('buku.index')
->with([
'success' => 'Buku berhasil ditambahkan!',
'info' => 'Total buku: ' . Buku::count()
]);c. Display Flash Messages
Di Blade Layout:
File: resources/views/layouts/app.blade.php
<main class="py-4">
<div class="container">
{{-- Flash Messages --}}
@if (session('success'))
<div class="alert alert-success alert-dismissible fade show" role="alert">
<i class="bi bi-check-circle-fill"></i>
{{ session('success') }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
@endif
@if (session('error'))
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<i class="bi bi-exclamation-triangle-fill"></i>
{{ session('error') }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
@endif
@if (session('info'))
<div class="alert alert-info alert-dismissible fade show" role="alert">
<i class="bi bi-info-circle-fill"></i>
{{ session('info') }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
@endif
@if (session('warning'))
<div class="alert alert-warning alert-dismissible fade show" role="alert">
<i class="bi bi-exclamation-circle-fill"></i>
{{ session('warning') }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
@endif
@yield('content')
</div>
</main>d. Auto-hide Flash Messages dengan JavaScript
@push('scripts')
<script>
// Auto hide alerts after 5 seconds
setTimeout(function() {
let alerts = document.querySelectorAll('.alert');
alerts.forEach(function(alert) {
let bsAlert = new bootstrap.Alert(alert);
bsAlert.close();
});
}, 5000);
</script>
@endpush6. Eloquent CRUD Methods
a. Create Operations
Method 1: save()
$buku = new Buku();
$buku->judul = $request->judul;
$buku->kategori = $request->kategori;
$buku->harga = $request->harga;
$buku->save(); // INSERT ke databaseMethod 2: create() - Mass Assignment
Buku::create([
'judul' => $request->judul,
'kategori' => $request->kategori,
'harga' => $request->harga,
]);
// Perlu $fillable di ModelMethod 3: create() dengan validated()
Buku::create($request->validated());
// Best practice dengan Form Requestb. Read Operations
// Get all records
$bukus = Buku::all();
// Get with conditions
$bukus = Buku::where('kategori', 'Programming')->get();
// Find by primary key
$buku = Buku::find($id);
// Find or throw 404
$buku = Buku::findOrFail($id);
// Get first record
$buku = Buku::first();
// Ordering
$bukus = Buku::orderBy('judul', 'asc')->get();
// Limit
$bukus = Buku::take(10)->get();c. Update Operations
Method 1: save()
$buku = Buku::findOrFail($id);
$buku->judul = $request->judul;
$buku->harga = $request->harga;
$buku->save(); // UPDATE databaseMethod 2: update() - Mass Assignment
$buku = Buku::findOrFail($id);
$buku->update([
'judul' => $request->judul,
'harga' => $request->harga,
]);Method 3: update() dengan validated()
$buku = Buku::findOrFail($id);
$buku->update($request->validated());
// Best practiced. Delete Operations
Method 1: delete()
$buku = Buku::findOrFail($id);
$buku->delete(); // DELETE from databaseMethod 2: destroy()
Buku::destroy($id); // Delete by ID
Buku::destroy([1, 2, 3]); // Delete multiple IDsMethod 3: Delete with condition
Buku::where('stok', 0)->delete(); // Delete semua buku stok habis7. Best Practices CRUD
a. Controller Best Practices
â DO:
// 1. Gunakan Form Request
public function store(StoreBukuRequest $request)
{
Buku::create($request->validated());
return redirect()->route('buku.index')
->with('success', 'Buku berhasil ditambahkan!');
}
// 2. Handle exceptions
public function destroy($id)
{
try {
$buku = Buku::findOrFail($id);
$buku->delete();
return redirect()->route('buku.index')
->with('success', 'Buku berhasil dihapus!');
} catch (\Exception $e) {
return redirect()->route('buku.index')
->with('error', 'Gagal menghapus buku!');
}
}
// 3. Use route model binding
public function update(UpdateBukuRequest $request, Buku $buku)
{
$buku->update($request->validated());
return redirect()->route('buku.show', $buku)
->with('success', 'Buku berhasil diupdate!');
}â DON'T:
// 1. Validasi manual di controller
public function store(Request $request)
{
if (empty($request->judul)) {
return back()->with('error', 'Judul wajib diisi');
}
if (strlen($request->judul) > 200) {
return back()->with('error', 'Judul terlalu panjang');
}
// ... banyak validasi manual
}
// 2. Tanpa error handling
public function destroy($id)
{
Buku::find($id)->delete(); // Crash jika ID tidak ada
return redirect()->route('buku.index');
}b. Validation Best Practices
â DO:
// 1. Pisahkan ke Form Request
php artisan make:request StoreBukuRequest
// 2. Validation untuk unique dengan ignore saat update
public function rules(): array
{
return [
'isbn' => 'nullable|unique:buku,isbn,' . $this->buku?->id,
// Ignore current record saat update
];
}
// 3. Conditional validation
public function rules(): array
{
$rules = [
'judul' => 'required|max:200',
];
if ($this->isMethod('post')) {
$rules['kode_buku'] = 'required|unique:buku,kode_buku';
} else {
$rules['kode_buku'] = 'required|unique:buku,kode_buku,' . $this->buku->id;
}
return $rules;
}c. Security Best Practices
â DO:
// 1. Always use @csrf
<form method="POST">
@csrf
@method('PUT')
</form>
// 2. Use $fillable di Model
protected $fillable = ['judul', 'harga', 'stok'];
// 3. Validate all input
$validated = $request->validate([...]);
// 4. Use findOrFail untuk auto 404
$buku = Buku::findOrFail($id);â DON'T:
// 1. Tanpa CSRF
<form method="POST">
<!-- Missing @csrf - vulnerable! -->
</form>
// 2. Mass assignment tanpa protection
protected $guarded = []; // Dangerous!
// 3. Direct input ke database
Buku::create($request->all()); // All input accepted!
// 4. Suppress errors
@Buku::find($id)->delete(); // Silent failD. PRAKTIKUM
1. Tujuan Praktikum
- Membuat form create untuk input buku baru
- Implementasi store method dengan validation
- Membuat form edit untuk update buku
- Implementasi update method
- Implementasi delete method
- Menampilkan flash messages
- Error handling yang baik
2. PRAKTIKUM 1: Form Create Buku
Tujuan
Membuat form untuk menambah buku baru dengan validation.
Langkah-langkah
a. Generate Form Request
php artisan make:request StoreBukuRequest
php artisan make:request UpdateBukuRequestb. Edit StoreBukuRequest
File: app/Http/Requests/StoreBukuRequest.php
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StoreBukuRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'kode_buku' => 'required|string|max:20|unique:buku,kode_buku',
'judul' => 'required|string|max:200',
'kategori' => 'required|in:Programming,Database,Web Design,Networking,Data Science',
'pengarang' => 'required|string|max:100',
'penerbit' => 'required|string|max:100',
'tahun_terbit' => 'required|integer|min:1900|max:' . date('Y'),
'isbn' => 'nullable|string|max:20',
'harga' => 'required|numeric|min:0',
'stok' => 'required|integer|min:0',
'deskripsi' => 'nullable|string',
'bahasa' => 'required|string|max:20',
];
}
/**
* Get custom error messages.
*/
public function messages(): array
{
return [
'kode_buku.required' => 'Kode buku wajib diisi.',
'kode_buku.unique' => 'Kode buku sudah digunakan.',
'kode_buku.max' => 'Kode buku maksimal 20 karakter.',
'judul.required' => 'Judul buku wajib diisi.',
'judul.max' => 'Judul buku maksimal 200 karakter.',
'kategori.required' => 'Kategori wajib dipilih.',
'kategori.in' => 'Kategori tidak valid.',
'pengarang.required' => 'Nama pengarang wajib diisi.',
'penerbit.required' => 'Nama penerbit wajib diisi.',
'tahun_terbit.required' => 'Tahun terbit wajib diisi.',
'tahun_terbit.integer' => 'Tahun terbit harus berupa angka.',
'tahun_terbit.min' => 'Tahun terbit tidak valid.',
'tahun_terbit.max' => 'Tahun terbit tidak boleh melebihi tahun sekarang.',
'isbn.max' => 'ISBN maksimal 20 karakter.',
'harga.required' => 'Harga buku wajib diisi.',
'harga.numeric' => 'Harga harus berupa angka.',
'harga.min' => 'Harga tidak boleh negatif.',
'stok.required' => 'Stok wajib diisi.',
'stok.integer' => 'Stok harus berupa angka bulat.',
'stok.min' => 'Stok tidak boleh negatif.',
'bahasa.required' => 'Bahasa wajib diisi.',
];
}
/**
* Get custom attribute names.
*/
public function attributes(): array
{
return [
'kode_buku' => 'kode buku',
'judul' => 'judul buku',
'kategori' => 'kategori',
'pengarang' => 'nama pengarang',
'penerbit' => 'nama penerbit',
'tahun_terbit' => 'tahun terbit',
'isbn' => 'ISBN',
'harga' => 'harga',
'stok' => 'stok',
'bahasa' => 'bahasa',
];
}
}c. Update BukuController - Method create()
File: app/Http/Controllers/BukuController.php
/**
* Show the form for creating a new resource.
*/
public function create()
{
return view('buku.create');
}d. Buat View Create
File: resources/views/buku/create.blade.php
@extends('layouts.app')
@section('title', 'Tambah Buku')
@section('content')
<div class="row justify-content-center">
<div class="col-md-10">
<div class="card">
<div class="card-header bg-primary text-white">
<h4 class="mb-0">
<i class="bi bi-plus-circle"></i>
Tambah Buku Baru
</h4>
</div>
<div class="card-body">
<form action="{{ route('buku.store') }}" method="POST">
@csrf
<div class="row">
{{-- Kode Buku --}}
<div class="col-md-4 mb-3">
<label for="kode_buku" class="form-label">
Kode Buku <span class="text-danger">*</span>
</label>
<input type="text"
name="kode_buku"
id="kode_buku"
class="form-control @error('kode_buku') is-invalid @enderror"
value="{{ old('kode_buku') }}"
placeholder="Contoh: BK-001">
@error('kode_buku')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
{{-- Judul --}}
<div class="col-md-8 mb-3">
<label for="judul" class="form-label">
Judul Buku <span class="text-danger">*</span>
</label>
<input type="text"
name="judul"
id="judul"
class="form-control @error('judul') is-invalid @enderror"
value="{{ old('judul') }}"
placeholder="Masukkan judul buku">
@error('judul')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
</div>
<div class="row">
{{-- Kategori --}}
<div class="col-md-4 mb-3">
<label for="kategori" class="form-label">
Kategori <span class="text-danger">*</span>
</label>
<select name="kategori"
id="kategori"
class="form-select @error('kategori') is-invalid @enderror">
<option value="">-- Pilih Kategori --</option>
<option value="Programming" {{ old('kategori') == 'Programming' ? 'selected' : '' }}>
Programming
</option>
<option value="Database" {{ old('kategori') == 'Database' ? 'selected' : '' }}>
Database
</option>
<option value="Web Design" {{ old('kategori') == 'Web Design' ? 'selected' : '' }}>
Web Design
</option>
<option value="Networking" {{ old('kategori') == 'Networking' ? 'selected' : '' }}>
Networking
</option>
<option value="Data Science" {{ old('kategori') == 'Data Science' ? 'selected' : '' }}>
Data Science
</option>
</select>
@error('kategori')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
{{-- Pengarang --}}
<div class="col-md-4 mb-3">
<label for="pengarang" class="form-label">
Pengarang <span class="text-danger">*</span>
</label>
<input type="text"
name="pengarang"
id="pengarang"
class="form-control @error('pengarang') is-invalid @enderror"
value="{{ old('pengarang') }}"
placeholder="Nama pengarang">
@error('pengarang')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
{{-- Penerbit --}}
<div class="col-md-4 mb-3">
<label for="penerbit" class="form-label">
Penerbit <span class="text-danger">*</span>
</label>
<input type="text"
name="penerbit"
id="penerbit"
class="form-control @error('penerbit') is-invalid @enderror"
value="{{ old('penerbit') }}"
placeholder="Nama penerbit">
@error('penerbit')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
</div>
<div class="row">
{{-- Tahun Terbit --}}
<div class="col-md-3 mb-3">
<label for="tahun_terbit" class="form-label">
Tahun Terbit <span class="text-danger">*</span>
</label>
<input type="number"
name="tahun_terbit"
id="tahun_terbit"
class="form-control @error('tahun_terbit') is-invalid @enderror"
value="{{ old('tahun_terbit', date('Y')) }}"
min="1900"
max="{{ date('Y') }}">
@error('tahun_terbit')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
{{-- ISBN --}}
<div class="col-md-3 mb-3">
<label for="isbn" class="form-label">
ISBN
</label>
<input type="text"
name="isbn"
id="isbn"
class="form-control @error('isbn') is-invalid @enderror"
value="{{ old('isbn') }}"
placeholder="978-xxx-xxx">
@error('isbn')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
{{-- Bahasa --}}
<div class="col-md-2 mb-3">
<label for="bahasa" class="form-label">
Bahasa <span class="text-danger">*</span>
</label>
<select name="bahasa"
id="bahasa"
class="form-select @error('bahasa') is-invalid @enderror">
<option value="Indonesia" {{ old('bahasa', 'Indonesia') == 'Indonesia' ? 'selected' : '' }}>
Indonesia
</option>
<option value="Inggris" {{ old('bahasa') == 'Inggris' ? 'selected' : '' }}>
Inggris
</option>
</select>
@error('bahasa')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
{{-- Harga --}}
<div class="col-md-2 mb-3">
<label for="harga" class="form-label">
Harga <span class="text-danger">*</span>
</label>
<input type="number"
name="harga"
id="harga"
class="form-control @error('harga') is-invalid @enderror"
value="{{ old('harga', 0) }}"
min="0"
step="1000">
@error('harga')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
{{-- Stok --}}
<div class="col-md-2 mb-3">
<label for="stok" class="form-label">
Stok <span class="text-danger">*</span>
</label>
<input type="number"
name="stok"
id="stok"
class="form-control @error('stok') is-invalid @enderror"
value="{{ old('stok', 0) }}"
min="0">
@error('stok')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
</div>
{{-- Deskripsi --}}
<div class="mb-3">
<label for="deskripsi" class="form-label">Deskripsi</label>
<textarea name="deskripsi"
id="deskripsi"
rows="4"
class="form-control @error('deskripsi') is-invalid @enderror"
placeholder="Deskripsi singkat tentang buku (opsional)">{{ old('deskripsi') }}</textarea>
@error('deskripsi')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<hr>
{{-- Buttons --}}
<div class="d-flex justify-content-between">
<a href="{{ route('buku.index') }}" class="btn btn-secondary">
<i class="bi bi-arrow-left"></i> Kembali
</a>
<button type="submit" class="btn btn-primary">
<i class="bi bi-save"></i> Simpan Buku
</button>
</div>
</form>
</div>
</div>
</div>
</div>
@endsection
@push('scripts')
<script>
// Auto format harga dengan thousand separator
document.getElementById('harga').addEventListener('blur', function() {
let value = this.value.replace(/\D/g, '');
this.value = value;
});
</script>
@endpushe. Test Form Create
- Akses:
http://localhost:8000/buku/create - Verifikasi:
- Form muncul dengan semua field
- Required fields ada tanda *
- Dropdown kategori dan bahasa berfungsi
- Placeholder text muncul
3. PRAKTIKUM 2: Store Method (Create Operation)
Tujuan
Implementasi method untuk menyimpan data buku ke database.
Langkah-langkah
a. Update BukuController - Method store()
File: app/Http/Controllers/BukuController.php
use App\Http\Requests\StoreBukuRequest;
/**
* Store a newly created resource in storage.
*/
public function store(StoreBukuRequest $request)
{
try {
// Create buku baru dengan validated data
Buku::create($request->validated());
// Redirect dengan success message
return redirect()->route('buku.index')
->with('success', 'Buku berhasil ditambahkan!');
} catch (\Exception $e) {
// Redirect dengan error message jika gagal
return redirect()->back()
->withInput()
->with('error', 'Gagal menambahkan buku: ' . $e->getMessage());
}
}b. Test Store Operation
-
Buka form create:
http://localhost:8000/buku/create -
Test 1 - Submit form kosong:
- Klik "Simpan Buku"
- Verifikasi: Error validation muncul untuk required fields
-
Test 2 - Submit dengan data valid:
- Isi semua required fields:
- Kode Buku:
BK-TEST-001 - Judul:
Testing Laravel CRUD - Kategori:
Programming - Pengarang:
John Doe - Penerbit:
Test Publisher - Tahun:
2024 - Harga:
150000 - Stok:
10 - Bahasa:
Indonesia
- Kode Buku:
- Klik "Simpan Buku"
- Verifikasi:
- Redirect ke halaman index
- Flash message "Buku berhasil ditambahkan!" muncul
- Buku baru muncul di list
- Isi semua required fields:
-
Test 3 - Validation errors:
- Kode Buku:
BK-TEST-001(sama dengan sebelumnya - test unique) - Harga:
-5000(test min:0) - Tahun:
2050(test max tahun sekarang) - Klik submit
- Verifikasi: Error validation muncul sesuai rules
- Kode Buku:
-
Test 4 - Old input preserved:
- Isi beberapa field
- Submit dengan error (misal judul kosong)
- Verifikasi: Field yang sudah diisi tetap ada (old input)
c. Cek di Database
php artisan tinker>>> \App\Models\Buku::where('kode_buku', 'BK-TEST-001')->first()
>>> \App\Models\Buku::latest()->first()4. PRAKTIKUM 3: Form Edit & Update Operation
Tujuan
Membuat form edit dan implementasi update data buku.
Langkah-langkah
a. Edit UpdateBukuRequest
File: app/Http/Requests/UpdateBukuRequest.php
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class UpdateBukuRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
// Get buku ID from route parameter
$bukuId = $this->route('buku');
return [
'kode_buku' => 'required|string|max:20|unique:buku,kode_buku,' . $bukuId,
'judul' => 'required|string|max:200',
'kategori' => 'required|in:Programming,Database,Web Design,Networking,Data Science',
'pengarang' => 'required|string|max:100',
'penerbit' => 'required|string|max:100',
'tahun_terbit' => 'required|integer|min:1900|max:' . date('Y'),
'isbn' => 'nullable|string|max:20',
'harga' => 'required|numeric|min:0',
'stok' => 'required|integer|min:0',
'deskripsi' => 'nullable|string',
'bahasa' => 'required|string|max:20',
];
}
/**
* Get custom error messages.
*/
public function messages(): array
{
return [
'kode_buku.required' => 'Kode buku wajib diisi.',
'kode_buku.unique' => 'Kode buku sudah digunakan.',
'judul.required' => 'Judul buku wajib diisi.',
'kategori.required' => 'Kategori wajib dipilih.',
'harga.required' => 'Harga buku wajib diisi.',
'harga.numeric' => 'Harga harus berupa angka.',
'stok.integer' => 'Stok harus berupa angka bulat.',
];
}
}b. Update BukuController - Method edit()
/**
* Show the form for editing the specified resource.
*/
public function edit(string $id)
{
$buku = Buku::findOrFail($id);
return view('buku.edit', compact('buku'));
}c. Buat View Edit
File: resources/views/buku/edit.blade.php
@extends('layouts.app')
@section('title', 'Edit Buku')
@section('content')
<div class="row justify-content-center">
<div class="col-md-10">
<div class="card">
<div class="card-header bg-warning">
<h4 class="mb-0">
<i class="bi bi-pencil-square"></i>
Edit Buku: {{ $buku->judul }}
</h4>
</div>
<div class="card-body">
<form action="{{ route('buku.update', $buku->id) }}" method="POST">
@csrf
@method('PUT')
<div class="row">
{{-- Kode Buku --}}
<div class="col-md-4 mb-3">
<label for="kode_buku" class="form-label">
Kode Buku <span class="text-danger">*</span>
</label>
<input type="text"
name="kode_buku"
id="kode_buku"
class="form-control @error('kode_buku') is-invalid @enderror"
value="{{ old('kode_buku', $buku->kode_buku) }}">
@error('kode_buku')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
{{-- Judul --}}
<div class="col-md-8 mb-3">
<label for="judul" class="form-label">
Judul Buku <span class="text-danger">*</span>
</label>
<input type="text"
name="judul"
id="judul"
class="form-control @error('judul') is-invalid @enderror"
value="{{ old('judul', $buku->judul) }}">
@error('judul')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
</div>
<div class="row">
{{-- Kategori --}}
<div class="col-md-4 mb-3">
<label for="kategori" class="form-label">
Kategori <span class="text-danger">*</span>
</label>
<select name="kategori"
id="kategori"
class="form-select @error('kategori') is-invalid @enderror">
<option value="">-- Pilih Kategori --</option>
@foreach(['Programming', 'Database', 'Web Design', 'Networking', 'Data Science'] as $kat)
<option value="{{ $kat }}"
{{ old('kategori', $buku->kategori) == $kat ? 'selected' : '' }}>
{{ $kat }}
</option>
@endforeach
</select>
@error('kategori')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
{{-- Pengarang --}}
<div class="col-md-4 mb-3">
<label for="pengarang" class="form-label">
Pengarang <span class="text-danger">*</span>
</label>
<input type="text"
name="pengarang"
id="pengarang"
class="form-control @error('pengarang') is-invalid @enderror"
value="{{ old('pengarang', $buku->pengarang) }}">
@error('pengarang')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
{{-- Penerbit --}}
<div class="col-md-4 mb-3">
<label for="penerbit" class="form-label">
Penerbit <span class="text-danger">*</span>
</label>
<input type="text"
name="penerbit"
id="penerbit"
class="form-control @error('penerbit') is-invalid @enderror"
value="{{ old('penerbit', $buku->penerbit) }}">
@error('penerbit')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
</div>
<div class="row">
{{-- Tahun Terbit --}}
<div class="col-md-3 mb-3">
<label for="tahun_terbit" class="form-label">
Tahun Terbit <span class="text-danger">*</span>
</label>
<input type="number"
name="tahun_terbit"
id="tahun_terbit"
class="form-control @error('tahun_terbit') is-invalid @enderror"
value="{{ old('tahun_terbit', $buku->tahun_terbit) }}"
min="1900"
max="{{ date('Y') }}">
@error('tahun_terbit')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
{{-- ISBN --}}
<div class="col-md-3 mb-3">
<label for="isbn" class="form-label">ISBN</label>
<input type="text"
name="isbn"
id="isbn"
class="form-control @error('isbn') is-invalid @enderror"
value="{{ old('isbn', $buku->isbn) }}">
@error('isbn')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
{{-- Bahasa --}}
<div class="col-md-2 mb-3">
<label for="bahasa" class="form-label">
Bahasa <span class="text-danger">*</span>
</label>
<select name="bahasa"
id="bahasa"
class="form-select @error('bahasa') is-invalid @enderror">
<option value="Indonesia" {{ old('bahasa', $buku->bahasa) == 'Indonesia' ? 'selected' : '' }}>
Indonesia
</option>
<option value="Inggris" {{ old('bahasa', $buku->bahasa) == 'Inggris' ? 'selected' : '' }}>
Inggris
</option>
</select>
@error('bahasa')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
{{-- Harga --}}
<div class="col-md-2 mb-3">
<label for="harga" class="form-label">
Harga <span class="text-danger">*</span>
</label>
<input type="number"
name="harga"
id="harga"
class="form-control @error('harga') is-invalid @enderror"
value="{{ old('harga', $buku->harga) }}"
min="0"
step="1000">
@error('harga')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
{{-- Stok --}}
<div class="col-md-2 mb-3">
<label for="stok" class="form-label">
Stok <span class="text-danger">*</span>
</label>
<input type="number"
name="stok"
id="stok"
class="form-control @error('stok') is-invalid @enderror"
value="{{ old('stok', $buku->stok) }}"
min="0">
@error('stok')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
</div>
{{-- Deskripsi --}}
<div class="mb-3">
<label for="deskripsi" class="form-label">Deskripsi</label>
<textarea name="deskripsi"
id="deskripsi"
rows="4"
class="form-control @error('deskripsi') is-invalid @enderror">{{ old('deskripsi', $buku->deskripsi) }}</textarea>
@error('deskripsi')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<hr>
{{-- Buttons --}}
<div class="d-flex justify-content-between">
<a href="{{ route('buku.show', $buku->id) }}" class="btn btn-secondary">
<i class="bi bi-arrow-left"></i> Kembali
</a>
<button type="submit" class="btn btn-warning">
<i class="bi bi-save"></i> Update Buku
</button>
</div>
</form>
</div>
</div>
{{-- Info Update --}}
<div class="card mt-3">
<div class="card-body">
<small class="text-muted">
<i class="bi bi-info-circle"></i>
<strong>Informasi:</strong><br />
- Buku ditambahkan: {{ $buku->created_at->format('d M Y H:i') }}<br />
- Terakhir diupdate: {{ $buku->updated_at->format('d M Y H:i') }}
</small>
</div>
</div>
</div>
</div>
@endsectiond. Update BukuController - Method update()
use App\Http\Requests\UpdateBukuRequest;
/**
* Update the specified resource in storage.
*/
public function update(UpdateBukuRequest $request, string $id)
{
try {
$buku = Buku::findOrFail($id);
// Update buku dengan validated data
$buku->update($request->validated());
// Redirect dengan success message
return redirect()->route('buku.show', $buku->id)
->with('success', 'Buku berhasil diupdate!');
} catch (\Exception $e) {
// Redirect dengan error message jika gagal
return redirect()->back()
->withInput()
->with('error', 'Gagal mengupdate buku: ' . $e->getMessage());
}
}e. Test Update Operation
-
Dari halaman detail buku, klik "Edit Buku"
-
URL:
http://localhost:8000/buku/1/edit -
Verifikasi:
- Form ter-isi dengan data buku yang ada
- Semua field editable
-
Test 1 - Update data:
- Ubah beberapa field (misal: stok dari 10 jadi 15, harga naik)
- Klik "Update Buku"
- Verifikasi:
- Redirect ke detail buku
- Flash message "Buku berhasil diupdate!" muncul
- Data ter-update di halaman detail
- Timestamp "Terakhir diupdate" berubah
-
Test 2 - Validation saat update:
- Kosongkan field required
- Submit
- Verifikasi: Error validation muncul
5. PRAKTIKUM 4: Delete Operation
Tujuan
Implementasi method untuk menghapus data buku.
Langkah-langkah
a. Update BukuController - Method destroy()
/**
* Remove the specified resource from storage.
*/
public function destroy(string $id)
{
try {
$buku = Buku::findOrFail($id);
$judulBuku = $buku->judul;
// Delete buku
$buku->delete();
// Redirect dengan success message
return redirect()->route('buku.index')
->with('success', "Buku '{$judulBuku}' berhasil dihapus!");
} catch (\Exception $e) {
// Redirect dengan error message jika gagal
return redirect()->back()
->with('error', 'Gagal menghapus buku: ' . $e->getMessage());
}
}b. Update View Index - Tambah Delete Button
File: resources/views/buku/index.blade.php
Di dalam card buku, tambahkan form delete:
<div class="btn-group-vertical d-grid gap-2">
<a href="{{ route('buku.show', $buku->id) }}" class="btn btn-sm btn-info text-white">
<i class="bi bi-eye"></i> Detail
</a>
<a href="{{ route('buku.edit', $buku->id) }}" class="btn btn-sm btn-warning">
<i class="bi bi-pencil"></i> Edit
</a>
{{-- Delete Button --}}
<form action="{{ route('buku.destroy', $buku->id) }}"
method="POST"
class="d-inline"
onsubmit="return confirm('Apakah Anda yakin ingin menghapus buku {{ $buku->judul }}?')">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-sm btn-danger w-100">
<i class="bi bi-trash"></i> Hapus
</button>
</form>
</div>c. Update View Show - Delete Button
File: resources/views/buku/show.blade.php
Form delete sudah ada di view show (di card Actions).
d. Test Delete Operation
-
Test dari Index Page:
- Buka halaman index buku
- Klik button "Hapus" pada salah satu buku
- Verifikasi:
- Confirmation dialog muncul
- Jika "OK": Buku terhapus, redirect ke index, flash message muncul
- Jika "Cancel": Buku tidak terhapus
-
Test dari Detail Page:
- Buka detail buku
- Klik button "Hapus Buku" di sidebar
- Verifikasi sama seperti di atas
-
Test delete non-existent ID:
- Manual akses:
http://localhost:8000/buku/999(ID yang tidak ada) - Submit delete form
- Verifikasi: Error 404 atau error message
- Manual akses:
e. Verifikasi di Database
php artisan tinker>>> \App\Models\Buku::count() // Hitung total buku
>>> \App\Models\Buku::onlyTrashed()->get() // Jika pakai soft delete6. PRAKTIKUM 5: Improve UI/UX CRUD
Tujuan
Memperbaiki user experience dengan konfirmasi, loading states, dan auto-hide alerts.
Langkah-langkah
a. SweetAlert2 untuk Konfirmasi Delete
Update layout dengan SweetAlert2:
File: resources/views/layouts/app.blade.php
Tambahkan di <head>:
{{-- SweetAlert2 CSS --}}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/sweetalert2@11.10.0/dist/sweetalert2.min.css">Tambahkan sebelum closing </body>:
{{-- SweetAlert2 JS --}}
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11.10.0/dist/sweetalert2.all.min.js"></script>b. Update Delete Form dengan SweetAlert
File: resources/views/buku/index.blade.php dan show.blade.php
{{-- Delete Button dengan SweetAlert --}}
<form action="{{ route('buku.destroy', $buku->id) }}"
method="POST"
class="d-inline delete-form">
@csrf
@method('DELETE')
<button type="button" class="btn btn-sm btn-danger w-100 btn-delete"
data-judul="{{ $buku->judul }}">
<i class="bi bi-trash"></i> Hapus
</button>
</form>
@push('scripts')
<script>
// SweetAlert confirmation untuk delete
document.querySelectorAll('.btn-delete').forEach(button => {
button.addEventListener('click', function(e) {
e.preventDefault();
const form = this.closest('form');
const judul = this.getAttribute('data-judul');
Swal.fire({
title: 'Konfirmasi Hapus',
text: `Apakah Anda yakin ingin menghapus buku "${judul}"?`,
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: 'Ya, Hapus!',
cancelButtonText: 'Batal'
}).then((result) => {
if (result.isConfirmed) {
form.submit();
}
});
});
});
</script>
@endpushc. Loading State untuk Form Submit
@push('scripts')
<script>
// Loading state saat submit form
document.querySelectorAll('form').forEach(form => {
form.addEventListener('submit', function() {
const submitBtn = this.querySelector('button[type="submit"]');
if (submitBtn && !this.classList.contains('delete-form')) {
submitBtn.disabled = true;
submitBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Menyimpan...';
}
});
});
</script>
@endpushd. Auto-hide Flash Messages
File: resources/views/layouts/app.blade.php
@if (session('success') || session('error') || session('info') || session('warning'))
@push('scripts')
<script>
// Auto hide alerts after 5 seconds
setTimeout(function() {
let alerts = document.querySelectorAll('.alert');
alerts.forEach(function(alert) {
let bsAlert = new bootstrap.Alert(alert);
bsAlert.close();
});
}, 5000);
</script>
@endpush
@endife. Test Improvements
- Test delete dengan SweetAlert - dialog muncul
- Test submit form - button berubah jadi "Menyimpan..."
- Test flash message - otomatis hilang setelah 5 detik
E. TUGAS
Tugas 1: Validation Rules Advanced (30%)
Instruksi: Tambahkan validation rules advanced untuk field-field tertentu.
Spesifikasi:
- Custom Validation Rule untuk Kode Buku:
- Format:
BK-[kategori singkat]-[nomor] - Contoh:
BK-PROG-001,BK-DB-002 - Buat custom validation rule:
- Format:
php artisan make:rule KodeBukuFormatpublic function passes($attribute, $value)
{
return preg_match('/^BK-[A-Z]{2,4}-\d{3}$/', $value);
}
public function message()
{
return 'Format kode buku harus: BK-XXX-000 (contoh: BK-PROG-001)';
}-
Conditional Validation:
- Jika kategori "Programming", field
bahasaharus "Inggris" - Jika tahun terbit < 2000, stok maksimal 5
- Jika kategori "Programming", field
-
Custom Error Messages Indonesia:
- Semua error message harus dalam bahasa Indonesia yang baik
Tugas 2: Bulk Delete Operations (35%)
Instruksi: Implementasi fitur delete multiple buku sekaligus.
Spesifikasi:
- Checkbox di Index Page:
<input type="checkbox" name="buku_ids[]" value="{{ $buku->id }}">- Select All Checkbox:
document.getElementById('select-all').addEventListener('change', function() {
document.querySelectorAll('input[name="buku_ids[]"]').forEach(cb => {
cb.checked = this.checked;
});
});- Controller Method:
public function bulkDelete(Request $request)
{
$ids = $request->buku_ids;
Buku::whereIn('id', $ids)->delete();
return redirect()->route('buku.index')
->with('success', count($ids) . ' buku berhasil dihapus!');
}- Route:
Route::post('/buku/bulk-delete', [BukuController::class, 'bulkDelete'])
->name('buku.bulk-delete');Tugas 3: Export Buku ke CSV (35%)
Instruksi: Tambahkan fitur export data buku ke file CSV.
Spesifikasi:
- Button Export di Index:
<a href="{{ route('buku.export') }}" class="btn btn-success">
<i class="bi bi-download"></i> Export CSV
</a>- Controller Method:
public function export()
{
$bukus = Buku::all();
$filename = 'buku_' . date('Y-m-d_His') . '.csv';
$headers = [
'Content-Type' => 'text/csv',
'Content-Disposition' => 'attachment; filename="' . $filename . '"',
];
$callback = function() use ($bukus) {
$file = fopen('php://output', 'w');
// Header CSV
fputcsv($file, [
'Kode Buku', 'Judul', 'Kategori', 'Pengarang',
'Penerbit', 'Tahun', 'ISBN', 'Harga', 'Stok'
]);
// Data
foreach ($bukus as $buku) {
fputcsv($file, [
$buku->kode_buku,
$buku->judul,
$buku->kategori,
$buku->pengarang,
$buku->penerbit,
$buku->tahun_terbit,
$buku->isbn,
$buku->harga,
$buku->stok,
]);
}
fclose($file);
};
return response()->stream($callback, 200, $headers);
}- Route:
Route::get('/buku/export', [BukuController::class, 'export'])
->name('buku.export');Submission:
- Format: Link repository GitHub (sertakan screenshot di README)
- Deadline: Pertemuan 13
- Upload ke: Ngaji UIN Gusdur (submit link repository GitHub)
F. EVALUASI
1. Kuis Singkat
Pilihan Ganda:
-
Method HTTP untuk create data adalah:
- A. GET
- B. POST
- C. PUT
- D. DELETE
-
Directive Blade untuk CSRF token adalah:
- A.
@token - B.
@csrf - C.
@protection - D.
@security
- A.
-
Method untuk update data dengan mass assignment:
- A.
save() - B.
update() - C.
edit() - D.
modify()
- A.
-
Validation rule untuk email valid:
- A.
'email' => 'email' - B.
'email' => 'valid_email' - C.
'email' => 'email_valid' - D.
'email' => 'is_email'
- A.
-
Cara passing flash message success:
- A.
->message('success', 'OK') - B.
->flash('success', 'OK') - C.
->with('success', 'OK') - D.
->session('success', 'OK')
- A.
-
Method spoofing untuk UPDATE menggunakan:
- A.
@method('UPDATE') - B.
@method('PUT') - C.
@method('PATCH') - D. B dan C benar
- A.
-
Eloquent method untuk delete by ID:
- A.
delete($id) - B.
remove($id) - C.
destroy($id) - D.
erase($id)
- A.
-
Validation rule untuk nilai unik:
- A.
'field' => 'unique:table' - B.
'field' => 'distinct' - C.
'field' => 'one' - D.
'field' => 'single'
- A.
-
Command untuk generate Form Request:
- A.
php artisan make:form - B.
php artisan make:request - C.
php artisan create:request - D.
php artisan generate:request
- A.
-
Function untuk retrieve old input:
- A.
input('field') - B.
get('field') - C.
old('field') - D.
previous('field')
- A.
Essay:
-
Jelaskan perbedaan
create()dansave()untuk insert data! Kapan sebaiknya menggunakan masing-masing? (15 poin) -
Apa fungsi CSRF protection? Bagaimana implementasinya di Laravel? (15 poin)
-
Buatlah validation rules untuk field "email" yang wajib diisi, harus format email valid, dan harus unik di tabel users! (10 poin)
-
Jelaskan alur lengkap update data dari form hingga database! (15 poin)
-
Apa keuntungan menggunakan Form Request dibanding validasi di controller? (10 poin)
2. Checklist Kompetensi
| No | Kompetensi | Belum | Cukup | Mahir |
|---|---|---|---|---|
| 1 | Membuat form input | â | â | â |
| 2 | Implementasi validation | â | â | â |
| 3 | Create data (store) | â | â | â |
| 4 | Read data (index, show) | â | â | â |
| 5 | Update data (edit, update) | â | â | â |
| 6 | Delete data (destroy) | â | â | â |
| 7 | CSRF protection | â | â | â |
| 8 | Flash messages | â | â | â |
| 9 | Error handling | â | â | â |
| 10 | Form Request usage | â | â | â |
G. REFERENSI
1. Dokumentasi Laravel 12
- Validation: https://laravel.com/docs/12.x/validation (opens in a new tab)
- Form Requests: https://laravel.com/docs/12.x/validation#form-request-validation (opens in a new tab)
- CSRF Protection: https://laravel.com/docs/12.x/csrf (opens in a new tab)
- Eloquent: https://laravel.com/docs/12.x/eloquent (opens in a new tab)
2. Tools
- SweetAlert2: https://sweetalert2.github.io/ (opens in a new tab)
- Bootstrap Forms: https://getbootstrap.com/docs/5.3/forms/overview/ (opens in a new tab)
H. PERSIAPAN PERTEMUAN 13
Topik: CRUD Anggota dengan Laravel
Preview:
- Replikasi pola CRUD untuk Anggota
- Form dengan date picker
- Validation untuk data personal
- DRY principle implementation
Yang Perlu Disiapkan:
- CRUD Buku sudah berjalan sempurna
- Pahami Form Request pattern
- Siap refactor code untuk reusability
Pre-reading:
- DRY Principle
- Code Refactoring Best Practices
Selamat Belajar! đđ
End of Module - Pertemuan 12
Next: Pertemuan 13 - CRUD Anggota dengan Laravel