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: 13 dari 16
Durasi: 150 menit (3 Ã 50 menit)
Studi Kasus Berkelanjutan: Sistem Manajemen Perpustakaan
PERTEMUAN 13
CRUD ANGGOTA 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. Menerapkan pola CRUD yang konsisten 2. Implementasi DRY (Don't Repeat Yourself) principle 3. Membuat form untuk data anggota dengan validasi advanced 4. Handle input tanggal dengan date picker 5. Custom validation untuk data personal 6. Refactoring code untuk reusability 7. Mengorganisir code dengan baik 8. Menyiapkan relasi antar tabel |
| Alokasi Waktu | âĸ Teori: 60 menit âĸ Praktikum: 90 menit âĸ Total: 150 menit (3 Ã 50 menit) |
B. PENDAHULUAN
1. Deskripsi Singkat
Pertemuan ketigabelas ini fokus pada implementasi CRUD untuk data anggota perpustakaan dengan mereplikasi pola yang telah dipelajari di pertemuan 12 (CRUD Buku). Mahasiswa akan belajar menerapkan DRY principle, refactoring code, dan membuat validasi yang lebih advanced untuk data personal seperti email, telepon, dan tanggal lahir. Pertemuan ini juga menyiapkan fondasi untuk relasi antar tabel di pertemuan berikutnya.
2. Keterkaitan dengan Pertemuan Lain
Pertemuan ini melanjutkan pembelajaran CRUD operations:
- Pertemuan 10: Model & Migration Anggota sudah dibuat
- Pertemuan 11: Pola Controller & Blade View dipelajari melalui BukuController â pola yang sama akan direplikasi untuk Anggota di pertemuan ini
- Pertemuan 12: Pola CRUD Buku (Create/Update/Delete) sebagai template yang diikuti
- Pertemuan 13: [SEKARANG] CRUD Anggota lengkap â mulai dari Read (index & show) hingga Create, Update, Delete
- Pertemuan 14: Relasi Anggota-Transaksi untuk peminjaman
- Pertemuan 15: Integrasi sistem lengkap
Konsistensi Pattern:
CRUD Buku (P12) â CRUD Anggota (P13)
ââ Form Create/Edit â ââ Form Create/Edit (+ date picker)
ââ Validation Rules â ââ Validation Advanced (email, phone)
ââ Store/Update â ââ Store/Update (same pattern)
ââ Delete â ââ Delete (same pattern)
ââ Flash Messages â ââ Flash Messages (same pattern)3. Manfaat Pembelajaran
- Mahir menerapkan pola CRUD secara konsisten
- Memahami dan menerapkan DRY principle
- Dapat membuat validasi untuk berbagai tipe data
- Mampu handle input tanggal dengan baik
- Terampil refactoring code untuk maintainability
- Siap mengembangkan modul CRUD lainnya
4. Relevansi dengan Studi Kasus
Dalam sistem perpustakaan, CRUD Anggota digunakan untuk:
- Create: Mendaftarkan anggota baru perpustakaan
- Read: Melihat daftar dan detail anggota (sudah di pertemuan 11)
- Update: Mengubah data anggota (alamat, telepon, status)
- Delete: Menonaktifkan atau menghapus anggota yang tidak aktif
C. MATERI TEORI
1. DRY Principle (Don't Repeat Yourself)
a. Apa itu DRY Principle?
DRY (Don't Repeat Yourself) adalah prinsip pemrograman yang menekankan untuk menghindari duplikasi code. Setiap logic harus memiliki single, unambiguous representation dalam sistem.
Quote Original (Andy Hunt & Dave Thomas):
"Every piece of knowledge must have a single, unambiguous, authoritative representation within a system."
b. Mengapa DRY Penting?
Tanpa DRY:
// BukuController
public function store(Request $request) {
$request->validate([
'judul' => 'required|max:200',
'harga' => 'required|numeric',
]);
Buku::create($request->all());
return redirect()->route('buku.index')
->with('success', 'Data berhasil disimpan');
}
// AnggotaController
public function store(Request $request) {
$request->validate([
'nama' => 'required|max:100',
'email' => 'required|email',
]);
Anggota::create($request->all());
return redirect()->route('anggota.index')
->with('success', 'Data berhasil disimpan');
}
// Masalah:
// 1. Pattern yang sama diulang-ulang
// 2. Jika ada perubahan, harus update di banyak tempat
// 3. Risk of inconsistencyDengan DRY:
// Gunakan Form Request (single source of truth untuk validation)
class StoreBukuRequest extends FormRequest {
public function rules() {
return [ /* validation rules */ ];
}
}
// Controller jadi sangat simple
public function store(StoreBukuRequest $request) {
Buku::create($request->validated());
return redirect()->route('buku.index')
->with('success', 'Data berhasil disimpan');
}c. Penerapan DRY di Laravel
1. Form Request untuk Validation:
// â WRONG: Validasi di controller (repetitive)
public function store(Request $request) {
$request->validate([...]);
}
public function update(Request $request) {
$request->validate([...]); // Sama dengan store!
}
// â
CORRECT: Gunakan Form Request
public function store(StoreBukuRequest $request) { }
public function update(UpdateBukuRequest $request) { }2. Eloquent Accessors & Mutators:
// â WRONG: Format harga di semua view
@foreach($bukus as $buku)
Rp {{ number_format($buku->harga, 0, ',', '.') }}
@endforeach
// â
CORRECT: Accessor di Model (single source)
// Model
public function getHargaFormatAttribute() {
return 'Rp ' . number_format($this->harga, 0, ',', '.');
}
// View
{{ $buku->harga_format }}3. Blade Components:
// â WRONG: Duplikasi form elements
<div class="mb-3">
<label>Judul</label>
<input type="text" name="judul" class="form-control">
</div>
<div class="mb-3">
<label>Kategori</label>
<input type="text" name="kategori" class="form-control">
</div>
// â
CORRECT: Blade Component
<x-input name="judul" label="Judul" />
<x-input name="kategori" label="Kategori" />4. Traits untuk Reusable Logic:
trait HasCreatedBy {
public static function bootHasCreatedBy() {
static::creating(function ($model) {
$model->created_by = auth()->id();
});
}
}
// Gunakan di multiple models
class Buku extends Model {
use HasCreatedBy;
}
class Anggota extends Model {
use HasCreatedBy;
}d. WET vs DRY
WET (Write Everything Twice / We Enjoy Typing):
- Copy-paste code
- Duplikasi logic
- Hard to maintain
- Inconsistent behavior
DRY (Don't Repeat Yourself):
- Abstraction & reusability
- Single source of truth
- Easy to maintain
- Consistent behavior
2. Code Organization Best Practices
a. Folder Structure
Laravel Standard Structure:
app/
âââ Http/
â âââ Controllers/
â â âââ BukuController.php
â â âââ AnggotaController.php
â â âââ TransaksiController.php
â âââ Requests/
â â âââ StoreBukuRequest.php
â â âââ UpdateBukuRequest.php
â â âââ StoreAnggotaRequest.php
â â âââ UpdateAnggotaRequest.php
â âââ Middleware/
âââ Models/
â âââ Buku.php
â âââ Anggota.php
â âââ Transaksi.php
âââ Services/ (optional - for business logic)
âââ TransaksiService.php
resources/
âââ views/
â âââ layouts/
â â âââ app.blade.php
â â âââ navbar.blade.php
â â âââ footer.blade.php
â âââ components/
â â âââ alert.blade.php
â â âââ input.blade.php
â âââ buku/
â â âââ index.blade.php
â â âââ create.blade.php
â â âââ edit.blade.php
â â âââ show.blade.php
â âââ anggota/
â âââ index.blade.php
â âââ create.blade.php
â âââ edit.blade.php
â âââ show.blade.phpb. Naming Conventions
Controllers:
- Singular, PascalCase
- Suffix:
Controller - Example:
BukuController,AnggotaController
Models:
- Singular, PascalCase
- No suffix
- Example:
Buku,Anggota,Transaksi
Form Requests:
- Action + Model name
- Example:
StoreBukuRequest,UpdateAnggotaRequest
Views:
- Plural folder, lowercase
- File: action name
- Example:
buku/create.blade.php,anggota/edit.blade.php
Routes:
- Lowercase, plural
- Example:
/buku,/anggota,/transaksi
c. Controller Organization
Thin Controller Pattern:
// â
GOOD: Thin controller
class AnggotaController extends Controller
{
public function store(StoreAnggotaRequest $request)
{
Anggota::create($request->validated());
return redirect()->route('anggota.index')
->with('success', 'Anggota berhasil ditambahkan!');
}
}
// â BAD: Fat controller
class AnggotaController extends Controller
{
public function store(Request $request)
{
// Validation logic (should be in FormRequest)
if (empty($request->nama)) {
return back()->with('error', 'Nama wajib diisi');
}
// Business logic (should be in Service/Model)
$kode = 'AGT-' . str_pad(Anggota::count() + 1, 3, '0', STR_PAD_LEFT);
// Formatting logic (should be in Model)
$telepon = str_replace(['-', ' '], '', $request->telepon);
// Database operation
Anggota::create([
'kode_anggota' => $kode,
'nama' => $request->nama,
'telepon' => $telepon,
// ... many more fields
]);
return redirect()->route('anggota.index')
->with('success', 'Anggota berhasil ditambahkan!');
}
}3. Advanced Form Validation
a. Validation untuk Data Personal
Email Validation:
'email' => [
'required',
'email', // Format email valid
'unique:anggota,email', // Unik di tabel anggota
'max:100',
]Phone Number Validation:
'telepon' => [
'required',
'regex:/^(\+62|62|0)[0-9]{9,12}$/', // Format Indonesia
'min:10',
'max:15',
]
// Penjelasan regex:
// ^(\+62|62|0) : Diawali +62, 62, atau 0
// [0-9]{9,12} : Diikuti 9-12 digit angka
// $ : End of stringDate Validation:
'tanggal_lahir' => [
'required',
'date', // Format tanggal valid
'before:today', // Sebelum hari ini
'after:1900-01-01', // Setelah tahun 1900
]
'tanggal_daftar' => [
'required',
'date',
'before_or_equal:today', // Hari ini atau sebelumnya
]Conditional Validation:
public function rules()
{
$rules = [
'nama' => 'required|max:100',
'email' => 'required|email|unique:anggota,email',
];
// Tambah validation untuk update (ignore current record)
if ($this->method() == 'PUT') {
$rules['email'] = 'required|email|unique:anggota,email,' . $this->anggota->id;
}
// Conditional: jika pekerjaan = "Mahasiswa", NIM required
if ($this->pekerjaan == 'Mahasiswa') {
$rules['nim'] = 'required|string|max:20';
}
return $rules;
}b. Custom Validation Rules
Method 1: Inline Rule Object
use Illuminate\Validation\Rule;
'status' => [
'required',
Rule::in(['Aktif', 'Nonaktif']),
]Method 2: Custom Rule Class
php artisan make:rule MinimalUsia<?php
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
use Carbon\Carbon;
class MinimalUsia implements Rule
{
private $minimalUsia;
public function __construct($minimalUsia = 17)
{
$this->minimalUsia = $minimalUsia;
}
public function passes($attribute, $value)
{
$umur = Carbon::parse($value)->age;
return $umur >= $this->minimalUsia;
}
public function message()
{
return 'Umur minimal harus ' . $this->minimalUsia . ' tahun.';
}
}Usage:
use App\Rules\MinimalUsia;
'tanggal_lahir' => [
'required',
'date',
new MinimalUsia(17), // Minimal 17 tahun
]Method 3: Closure Validation
'telepon' => [
'required',
function ($attribute, $value, $fail) {
if (!preg_match('/^08[0-9]{8,11}$/', $value)) {
$fail('Format nomor telepon tidak valid. Harus diawali 08.');
}
},
]c. Validation Messages Indonesia
public function messages()
{
return [
'nama.required' => 'Nama anggota wajib diisi.',
'email.required' => 'Email wajib diisi.',
'email.email' => 'Format email tidak valid.',
'email.unique' => 'Email sudah terdaftar.',
'telepon.required' => 'Nomor telepon wajib diisi.',
'telepon.regex' => 'Format nomor telepon tidak valid. Contoh: 081234567890',
'tanggal_lahir.required' => 'Tanggal lahir wajib diisi.',
'tanggal_lahir.date' => 'Format tanggal tidak valid.',
'tanggal_lahir.before' => 'Tanggal lahir harus sebelum hari ini.',
'alamat.required' => 'Alamat wajib diisi.',
'jenis_kelamin.required' => 'Jenis kelamin wajib dipilih.',
'jenis_kelamin.in' => 'Jenis kelamin tidak valid.',
];
}
public function attributes()
{
return [
'tanggal_lahir' => 'tanggal lahir',
'tanggal_daftar' => 'tanggal pendaftaran',
'jenis_kelamin' => 'jenis kelamin',
];
}4. Handle Date Input di Laravel
a. Date Picker Implementation
Frontend (Bootstrap Datepicker atau HTML5):
Option 1: HTML5 Date Input (Simple)
<input type="date"
name="tanggal_lahir"
class="form-control"
max="{{ date('Y-m-d') }}"
value="{{ old('tanggal_lahir', $anggota->tanggal_lahir?->format('Y-m-d')) }}">Option 2: Flatpickr (Better UX)
{{-- Include Flatpickr CSS --}}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">
<input type="text"
name="tanggal_lahir"
id="tanggal_lahir"
class="form-control"
value="{{ old('tanggal_lahir', $anggota->tanggal_lahir?->format('Y-m-d')) }}">
{{-- Include Flatpickr JS --}}
<script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
<script>
flatpickr("#tanggal_lahir", {
dateFormat: "Y-m-d",
maxDate: "today",
locale: "id",
});
</script>b. Date Casting di Model
class Anggota extends Model
{
protected $casts = [
'tanggal_lahir' => 'date',
'tanggal_daftar' => 'date',
];
// Accessor untuk umur
public function getUmurAttribute()
{
return $this->tanggal_lahir?->age;
}
// Accessor untuk lama menjadi anggota (hari)
public function getLamaAnggotaAttribute()
{
return $this->tanggal_daftar?->diffInDays(now());
}
}Usage di View:
{{-- Format date --}}
{{ $anggota->tanggal_lahir->format('d F Y') }}
{{-- Accessor --}}
Umur: {{ $anggota->umur }} tahun
Lama anggota: {{ $anggota->lama_anggota }} haric. Date Validation
'tanggal_lahir' => [
'required',
'date',
'before:today', // Harus sebelum hari ini
'after:1900-01-01', // Harus setelah 1900
],
'tanggal_daftar' => [
'required',
'date',
'before_or_equal:today', // Hari ini atau sebelumnya
'after_or_equal:' . now()->subYears(10)->format('Y-m-d'), // Max 10 tahun lalu
],5. Perbedaan Validation Buku vs Anggota
| Aspek | Buku | Anggota |
|---|---|---|
| Primary Identifier | Kode Buku (string) | Kode Anggota (string) |
| Unique Fields | ISBN (nullable) | Email (required) |
| Numeric Fields | Harga, Stok, Tahun | - |
| Date Fields | - | Tanggal Lahir, Tanggal Daftar |
| Enum Fields | Kategori (5 options) | Jenis Kelamin (2 options), Status (2 options) |
| Text Fields | Judul, Pengarang, Penerbit | Nama, Alamat, Pekerjaan |
| Special Validation | Year range, Price min:0 | Email format, Phone regex, Age validation |
D. PRAKTIKUM
1. Tujuan Praktikum
- Membuat Form Request untuk Anggota
- Implementasi form create dan edit anggota
- Handle date input dengan date picker
- Validation advanced untuk data personal
- Store, update, dan delete anggota
- Konsistensi pattern dengan CRUD Buku
2. PRAKTIKUM 1: Form Request Anggota
Tujuan
Membuat Form Request untuk validasi data anggota.
Langkah-langkah
a. Generate Form Request
php artisan make:request StoreAnggotaRequest
php artisan make:request UpdateAnggotaRequestb. Edit StoreAnggotaRequest
File: app/Http/Requests/StoreAnggotaRequest.php
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StoreAnggotaRequest 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_anggota' => 'required|string|max:20|unique:anggota,kode_anggota',
'nama' => 'required|string|max:100',
'email' => 'required|email|unique:anggota,email|max:100',
'telepon' => [
'required',
'regex:/^(\+62|62|0)[0-9]{9,12}$/',
'min:10',
'max:15',
],
'alamat' => 'required|string',
'tanggal_lahir' => [
'required',
'date',
'before:today',
'after:1900-01-01',
],
'jenis_kelamin' => 'required|in:Laki-laki,Perempuan',
'pekerjaan' => 'nullable|string|max:50',
'tanggal_daftar' => [
'required',
'date',
'before_or_equal:today',
],
'status' => 'required|in:Aktif,Nonaktif',
];
}
/**
* Get custom error messages.
*/
public function messages(): array
{
return [
'kode_anggota.required' => 'Kode anggota wajib diisi.',
'kode_anggota.unique' => 'Kode anggota sudah digunakan.',
'kode_anggota.max' => 'Kode anggota maksimal 20 karakter.',
'nama.required' => 'Nama anggota wajib diisi.',
'nama.max' => 'Nama maksimal 100 karakter.',
'email.required' => 'Email wajib diisi.',
'email.email' => 'Format email tidak valid.',
'email.unique' => 'Email sudah terdaftar.',
'email.max' => 'Email maksimal 100 karakter.',
'telepon.required' => 'Nomor telepon wajib diisi.',
'telepon.regex' => 'Format nomor telepon tidak valid. Contoh: 081234567890 atau +6281234567890',
'telepon.min' => 'Nomor telepon minimal 10 karakter.',
'telepon.max' => 'Nomor telepon maksimal 15 karakter.',
'alamat.required' => 'Alamat wajib diisi.',
'tanggal_lahir.required' => 'Tanggal lahir wajib diisi.',
'tanggal_lahir.date' => 'Format tanggal lahir tidak valid.',
'tanggal_lahir.before' => 'Tanggal lahir harus sebelum hari ini.',
'tanggal_lahir.after' => 'Tanggal lahir tidak valid.',
'jenis_kelamin.required' => 'Jenis kelamin wajib dipilih.',
'jenis_kelamin.in' => 'Jenis kelamin tidak valid.',
'pekerjaan.max' => 'Pekerjaan maksimal 50 karakter.',
'tanggal_daftar.required' => 'Tanggal pendaftaran wajib diisi.',
'tanggal_daftar.date' => 'Format tanggal pendaftaran tidak valid.',
'tanggal_daftar.before_or_equal' => 'Tanggal pendaftaran tidak boleh di masa depan.',
'status.required' => 'Status wajib dipilih.',
'status.in' => 'Status tidak valid.',
];
}
/**
* Get custom attribute names.
*/
public function attributes(): array
{
return [
'kode_anggota' => 'kode anggota',
'nama' => 'nama',
'email' => 'email',
'telepon' => 'nomor telepon',
'alamat' => 'alamat',
'tanggal_lahir' => 'tanggal lahir',
'jenis_kelamin' => 'jenis kelamin',
'pekerjaan' => 'pekerjaan',
'tanggal_daftar' => 'tanggal pendaftaran',
'status' => 'status',
];
}
}c. Edit UpdateAnggotaRequest
File: app/Http/Requests/UpdateAnggotaRequest.php
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class UpdateAnggotaRequest 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 anggota ID from route parameter
$anggotaId = $this->route('anggota');
return [
'kode_anggota' => 'required|string|max:20|unique:anggota,kode_anggota,' . $anggotaId,
'nama' => 'required|string|max:100',
'email' => 'required|email|unique:anggota,email,' . $anggotaId . '|max:100',
'telepon' => [
'required',
'regex:/^(\+62|62|0)[0-9]{9,12}$/',
'min:10',
'max:15',
],
'alamat' => 'required|string',
'tanggal_lahir' => [
'required',
'date',
'before:today',
'after:1900-01-01',
],
'jenis_kelamin' => 'required|in:Laki-laki,Perempuan',
'pekerjaan' => 'nullable|string|max:50',
'tanggal_daftar' => [
'required',
'date',
'before_or_equal:today',
],
'status' => 'required|in:Aktif,Nonaktif',
];
}
/**
* Get custom error messages.
*/
public function messages(): array
{
return [
'kode_anggota.unique' => 'Kode anggota sudah digunakan.',
'email.unique' => 'Email sudah terdaftar.',
'telepon.regex' => 'Format nomor telepon tidak valid. Contoh: 081234567890',
'tanggal_lahir.before' => 'Tanggal lahir harus sebelum hari ini.',
'tanggal_daftar.before_or_equal' => 'Tanggal pendaftaran tidak boleh di masa depan.',
];
}
}3. PRAKTIKUM 2: Form Create Anggota
Tujuan
Membuat form untuk mendaftarkan anggota baru dengan date picker.
Langkah-langkah
a. Update AnggotaController - Method create()
File: app/Http/Controllers/AnggotaController.php
/**
* Show the form for creating a new resource.
*/
public function create()
{
return view('anggota.create');
}b. Buat View Create
File: resources/views/anggota/create.blade.php
@extends('layouts.app')
@section('title', 'Tambah Anggota')
@push('styles')
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">
@endpush
@section('content')
<div class="row justify-content-center">
<div class="col-md-10">
<div class="card">
<div class="card-header bg-success text-white">
<h4 class="mb-0">
<i class="bi bi-person-plus"></i>
Tambah Anggota Baru
</h4>
</div>
<div class="card-body">
<form action="{{ route('anggota.store') }}" method="POST">
@csrf
<div class="row">
{{-- Kode Anggota --}}
<div class="col-md-4 mb-3">
<label for="kode_anggota" class="form-label">
Kode Anggota <span class="text-danger">*</span>
</label>
<input type="text"
name="kode_anggota"
id="kode_anggota"
class="form-control @error('kode_anggota') is-invalid @enderror"
value="{{ old('kode_anggota') }}"
placeholder="Contoh: AGT-001">
@error('kode_anggota')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
<small class="text-muted">Format: AGT-XXX</small>
</div>
{{-- Nama --}}
<div class="col-md-8 mb-3">
<label for="nama" class="form-label">
Nama Lengkap <span class="text-danger">*</span>
</label>
<input type="text"
name="nama"
id="nama"
class="form-control @error('nama') is-invalid @enderror"
value="{{ old('nama') }}"
placeholder="Nama lengkap anggota">
@error('nama')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
</div>
<div class="row">
{{-- Email --}}
<div class="col-md-6 mb-3">
<label for="email" class="form-label">
Email <span class="text-danger">*</span>
</label>
<input type="email"
name="email"
id="email"
class="form-control @error('email') is-invalid @enderror"
value="{{ old('email') }}"
placeholder="email@example.com">
@error('email')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
{{-- Telepon --}}
<div class="col-md-6 mb-3">
<label for="telepon" class="form-label">
Nomor Telepon <span class="text-danger">*</span>
</label>
<input type="text"
name="telepon"
id="telepon"
class="form-control @error('telepon') is-invalid @enderror"
value="{{ old('telepon') }}"
placeholder="081234567890">
@error('telepon')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
<small class="text-muted">Format: 08xxxxxxxxxx atau +628xxxxxxxxxx</small>
</div>
</div>
{{-- Alamat --}}
<div class="mb-3">
<label for="alamat" class="form-label">
Alamat Lengkap <span class="text-danger">*</span>
</label>
<textarea name="alamat"
id="alamat"
rows="3"
class="form-control @error('alamat') is-invalid @enderror"
placeholder="Alamat lengkap dengan kota dan kode pos">{{ old('alamat') }}</textarea>
@error('alamat')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="row">
{{-- Tanggal Lahir --}}
<div class="col-md-4 mb-3">
<label for="tanggal_lahir" class="form-label">
Tanggal Lahir <span class="text-danger">*</span>
</label>
<input type="date"
name="tanggal_lahir"
id="tanggal_lahir"
class="form-control @error('tanggal_lahir') is-invalid @enderror"
value="{{ old('tanggal_lahir') }}"
max="{{ date('Y-m-d') }}">
@error('tanggal_lahir')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
{{-- Jenis Kelamin --}}
<div class="col-md-4 mb-3">
<label for="jenis_kelamin" class="form-label">
Jenis Kelamin <span class="text-danger">*</span>
</label>
<select name="jenis_kelamin"
id="jenis_kelamin"
class="form-select @error('jenis_kelamin') is-invalid @enderror">
<option value="">-- Pilih Jenis Kelamin --</option>
<option value="Laki-laki" {{ old('jenis_kelamin') == 'Laki-laki' ? 'selected' : '' }}>
Laki-laki
</option>
<option value="Perempuan" {{ old('jenis_kelamin') == 'Perempuan' ? 'selected' : '' }}>
Perempuan
</option>
</select>
@error('jenis_kelamin')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
{{-- Pekerjaan --}}
<div class="col-md-4 mb-3">
<label for="pekerjaan" class="form-label">Pekerjaan</label>
<input type="text"
name="pekerjaan"
id="pekerjaan"
class="form-control @error('pekerjaan') is-invalid @enderror"
value="{{ old('pekerjaan') }}"
placeholder="Contoh: Mahasiswa, Pegawai, dll">
@error('pekerjaan')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
</div>
<div class="row">
{{-- Tanggal Daftar --}}
<div class="col-md-6 mb-3">
<label for="tanggal_daftar" class="form-label">
Tanggal Pendaftaran <span class="text-danger">*</span>
</label>
<input type="date"
name="tanggal_daftar"
id="tanggal_daftar"
class="form-control @error('tanggal_daftar') is-invalid @enderror"
value="{{ old('tanggal_daftar', date('Y-m-d')) }}"
max="{{ date('Y-m-d') }}">
@error('tanggal_daftar')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
{{-- Status --}}
<div class="col-md-6 mb-3">
<label for="status" class="form-label">
Status <span class="text-danger">*</span>
</label>
<select name="status"
id="status"
class="form-select @error('status') is-invalid @enderror">
<option value="Aktif" {{ old('status', 'Aktif') == 'Aktif' ? 'selected' : '' }}>
Aktif
</option>
<option value="Nonaktif" {{ old('status') == 'Nonaktif' ? 'selected' : '' }}>
Nonaktif
</option>
</select>
@error('status')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
</div>
<hr>
{{-- Buttons --}}
<div class="d-flex justify-content-between">
<a href="{{ route('anggota.index') }}" class="btn btn-secondary">
<i class="bi bi-arrow-left"></i> Kembali
</a>
<button type="submit" class="btn btn-success">
<i class="bi bi-save"></i> Simpan Anggota
</button>
</div>
</form>
</div>
</div>
</div>
</div>
@endsection
@push('scripts')
<script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
<script src="https://cdn.jsdelivr.net/npm/flatpickr/dist/l10n/id.js"></script>
<script>
// Initialize Flatpickr untuk tanggal lahir
flatpickr("#tanggal_lahir", {
dateFormat: "Y-m-d",
maxDate: "today",
locale: "id",
altInput: true,
altFormat: "d F Y",
});
// Initialize Flatpickr untuk tanggal daftar
flatpickr("#tanggal_daftar", {
dateFormat: "Y-m-d",
maxDate: "today",
locale: "id",
altInput: true,
altFormat: "d F Y",
defaultDate: "today",
});
// Auto format telepon (hapus karakter non-digit)
document.getElementById('telepon').addEventListener('input', function() {
let value = this.value.replace(/[^\d+]/g, '');
this.value = value;
});
</script>
@endpushc. Test Form Create
- Akses:
http://localhost:8000/anggota/create - Verifikasi:
- Form muncul dengan semua field
- Date picker Flatpickr berfungsi (UI lebih baik dari HTML5 date)
- Placeholder text ada
- Default value tanggal daftar = hari ini
4. PRAKTIKUM 3: Store Method Anggota
Tujuan
Implementasi method untuk menyimpan data anggota ke database.
Langkah-langkah
a. Update AnggotaController - Method store()
File: app/Http/Controllers/AnggotaController.php
use App\Http\Requests\StoreAnggotaRequest;
/**
* Store a newly created resource in storage.
*/
public function store(StoreAnggotaRequest $request)
{
try {
// Create anggota baru dengan validated data
Anggota::create($request->validated());
// Redirect dengan success message
return redirect()->route('anggota.index')
->with('success', 'Anggota berhasil ditambahkan!');
} catch (\Exception $e) {
// Redirect dengan error message jika gagal
return redirect()->back()
->withInput()
->with('error', 'Gagal menambahkan anggota: ' . $e->getMessage());
}
}b. Test Store Operation
-
Buka form create:
http://localhost:8000/anggota/create -
Test 1 - Submit form kosong:
- Klik "Simpan Anggota"
- Verifikasi: Error validation muncul untuk required fields
-
Test 2 - Submit dengan data valid:
- Isi semua required fields:
- Kode Anggota:
AGT-TEST-001 - Nama:
John Doe Testing - Email:
john.test@example.com - Telepon:
081234567890 - Alamat:
Jl. Testing No. 1, Jakarta - Tanggal Lahir:
1995-05-15 - Jenis Kelamin:
Laki-laki - Pekerjaan:
Mahasiswa - Tanggal Daftar:
hari ini - Status:
Aktif
- Kode Anggota:
- Klik "Simpan Anggota"
- Verifikasi:
- Redirect ke halaman index
- Flash message "Anggota berhasil ditambahkan!" muncul
- Anggota baru muncul di list
- Isi semua required fields:
-
Test 3 - Validation errors:
- Email:
bukan-email(test format email) - Telepon:
123(test regex format) - Tanggal Lahir:
tanggal masa depan(test before:today) - Klik submit
- Verifikasi: Error validation muncul sesuai rules
- Email:
-
Test 4 - Unique validation:
- Gunakan email yang sama dengan sebelumnya
- Klik submit
- Verifikasi: Error "Email sudah terdaftar."
5. PRAKTIKUM 4: Form Edit & Update Anggota
Tujuan
Membuat form edit dan implementasi update data anggota.
Langkah-langkah
a. Update AnggotaController - Method edit()
/**
* Show the form for editing the specified resource.
*/
public function edit(string $id)
{
$anggota = Anggota::findOrFail($id);
return view('anggota.edit', compact('anggota'));
}b. Buat View Edit
File: resources/views/anggota/edit.blade.php
@extends('layouts.app')
@section('title', 'Edit Anggota')
@push('styles')
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">
@endpush
@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 Anggota: {{ $anggota->nama }}
</h4>
</div>
<div class="card-body">
<form action="{{ route('anggota.update', $anggota->id) }}" method="POST">
@csrf
@method('PUT')
<div class="row">
{{-- Kode Anggota --}}
<div class="col-md-4 mb-3">
<label for="kode_anggota" class="form-label">
Kode Anggota <span class="text-danger">*</span>
</label>
<input type="text"
name="kode_anggota"
id="kode_anggota"
class="form-control @error('kode_anggota') is-invalid @enderror"
value="{{ old('kode_anggota', $anggota->kode_anggota) }}">
@error('kode_anggota')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
{{-- Nama --}}
<div class="col-md-8 mb-3">
<label for="nama" class="form-label">
Nama Lengkap <span class="text-danger">*</span>
</label>
<input type="text"
name="nama"
id="nama"
class="form-control @error('nama') is-invalid @enderror"
value="{{ old('nama', $anggota->nama) }}">
@error('nama')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
</div>
<div class="row">
{{-- Email --}}
<div class="col-md-6 mb-3">
<label for="email" class="form-label">
Email <span class="text-danger">*</span>
</label>
<input type="email"
name="email"
id="email"
class="form-control @error('email') is-invalid @enderror"
value="{{ old('email', $anggota->email) }}">
@error('email')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
{{-- Telepon --}}
<div class="col-md-6 mb-3">
<label for="telepon" class="form-label">
Nomor Telepon <span class="text-danger">*</span>
</label>
<input type="text"
name="telepon"
id="telepon"
class="form-control @error('telepon') is-invalid @enderror"
value="{{ old('telepon', $anggota->telepon) }}">
@error('telepon')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
</div>
{{-- Alamat --}}
<div class="mb-3">
<label for="alamat" class="form-label">
Alamat Lengkap <span class="text-danger">*</span>
</label>
<textarea name="alamat"
id="alamat"
rows="3"
class="form-control @error('alamat') is-invalid @enderror">{{ old('alamat', $anggota->alamat) }}</textarea>
@error('alamat')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="row">
{{-- Tanggal Lahir --}}
<div class="col-md-4 mb-3">
<label for="tanggal_lahir" class="form-label">
Tanggal Lahir <span class="text-danger">*</span>
</label>
<input type="date"
name="tanggal_lahir"
id="tanggal_lahir"
class="form-control @error('tanggal_lahir') is-invalid @enderror"
value="{{ old('tanggal_lahir', $anggota->tanggal_lahir?->format('Y-m-d')) }}"
max="{{ date('Y-m-d') }}">
@error('tanggal_lahir')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
{{-- Jenis Kelamin --}}
<div class="col-md-4 mb-3">
<label for="jenis_kelamin" class="form-label">
Jenis Kelamin <span class="text-danger">*</span>
</label>
<select name="jenis_kelamin"
id="jenis_kelamin"
class="form-select @error('jenis_kelamin') is-invalid @enderror">
<option value="">-- Pilih Jenis Kelamin --</option>
@foreach(['Laki-laki', 'Perempuan'] as $jk)
<option value="{{ $jk }}"
{{ old('jenis_kelamin', $anggota->jenis_kelamin) == $jk ? 'selected' : '' }}>
{{ $jk }}
</option>
@endforeach
</select>
@error('jenis_kelamin')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
{{-- Pekerjaan --}}
<div class="col-md-4 mb-3">
<label for="pekerjaan" class="form-label">Pekerjaan</label>
<input type="text"
name="pekerjaan"
id="pekerjaan"
class="form-control @error('pekerjaan') is-invalid @enderror"
value="{{ old('pekerjaan', $anggota->pekerjaan) }}">
@error('pekerjaan')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
</div>
<div class="row">
{{-- Tanggal Daftar --}}
<div class="col-md-6 mb-3">
<label for="tanggal_daftar" class="form-label">
Tanggal Pendaftaran <span class="text-danger">*</span>
</label>
<input type="date"
name="tanggal_daftar"
id="tanggal_daftar"
class="form-control @error('tanggal_daftar') is-invalid @enderror"
value="{{ old('tanggal_daftar', $anggota->tanggal_daftar?->format('Y-m-d')) }}"
max="{{ date('Y-m-d') }}">
@error('tanggal_daftar')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
{{-- Status --}}
<div class="col-md-6 mb-3">
<label for="status" class="form-label">
Status <span class="text-danger">*</span>
</label>
<select name="status"
id="status"
class="form-select @error('status') is-invalid @enderror">
@foreach(['Aktif', 'Nonaktif'] as $st)
<option value="{{ $st }}"
{{ old('status', $anggota->status) == $st ? 'selected' : '' }}>
{{ $st }}
</option>
@endforeach
</select>
@error('status')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
</div>
<hr>
{{-- Buttons --}}
<div class="d-flex justify-content-between">
<a href="{{ route('anggota.show', $anggota->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 Anggota
</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 />
- Anggota terdaftar: {{ $anggota->created_at->format('d M Y H:i') }}<br />
- Terakhir diupdate: {{ $anggota->updated_at->format('d M Y H:i') }}<br />
- Lama menjadi anggota: {{ $anggota->lama_anggota }} hari ({{ round($anggota->lama_anggota / 365, 1) }} tahun)
</small>
</div>
</div>
</div>
</div>
@endsection
@push('scripts')
<script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
<script src="https://cdn.jsdelivr.net/npm/flatpickr/dist/l10n/id.js"></script>
<script>
// Initialize Flatpickr
flatpickr("#tanggal_lahir", {
dateFormat: "Y-m-d",
maxDate: "today",
locale: "id",
altInput: true,
altFormat: "d F Y",
});
flatpickr("#tanggal_daftar", {
dateFormat: "Y-m-d",
maxDate: "today",
locale: "id",
altInput: true,
altFormat: "d F Y",
});
</script>
@endpushc. Update AnggotaController - Method update()
use App\Http\Requests\UpdateAnggotaRequest;
/**
* Update the specified resource in storage.
*/
public function update(UpdateAnggotaRequest $request, string $id)
{
try {
$anggota = Anggota::findOrFail($id);
// Update anggota dengan validated data
$anggota->update($request->validated());
// Redirect dengan success message
return redirect()->route('anggota.show', $anggota->id)
->with('success', 'Data anggota berhasil diupdate!');
} catch (\Exception $e) {
// Redirect dengan error message jika gagal
return redirect()->back()
->withInput()
->with('error', 'Gagal mengupdate anggota: ' . $e->getMessage());
}
}d. Test Update Operation
- Dari halaman detail anggota, klik "Edit Anggota"
- URL:
http://localhost:8000/anggota/1/edit - Verifikasi:
- Form ter-isi dengan data anggota yang ada
- Date picker menampilkan tanggal dengan format Indonesia
- Test update:
- Ubah beberapa field (email, telepon, status)
- Klik "Update Anggota"
- Verifikasi:
- Redirect ke detail anggota
- Flash message muncul
- Data ter-update
6. PRAKTIKUM 5: Delete Anggota
Tujuan
Implementasi method untuk menghapus data anggota.
Langkah-langkah
a. Update AnggotaController - Method destroy()
/**
* Remove the specified resource from storage.
*/
public function destroy(string $id)
{
try {
$anggota = Anggota::findOrFail($id);
$namaAnggota = $anggota->nama;
// Delete anggota
$anggota->delete();
// Redirect dengan success message
return redirect()->route('anggota.index')
->with('success', "Anggota '{$namaAnggota}' berhasil dihapus!");
} catch (\Exception $e) {
// Redirect dengan error message jika gagal
return redirect()->back()
->with('error', 'Gagal menghapus anggota: ' . $e->getMessage());
}
}b. Update View Index - Delete Button sudah ada di pertemuan 11
c. Update View Show - Delete Button sudah ada di pertemuan 11
d. Test Delete Operation
- Test delete dari index atau detail page
- Verifikasi confirmation dialog muncul
- Verifikasi anggota terhapus dan flash message muncul
E. TUGAS
Tugas 1: Auto-Generate Kode Anggota (30%)
Instruksi:
Implementasi auto-generate kode anggota dengan format: AGT-[TAHUN]-[NOMOR_URUT]
Spesifikasi:
- Helper Function di AnggotaController:
private function generateKodeAnggota()
{
$tahun = date('Y');
$lastAnggota = Anggota::whereYear('created_at', $tahun)
->orderBy('kode_anggota', 'desc')
->first();
if ($lastAnggota) {
$lastNumber = intval(substr($lastAnggota->kode_anggota, -3));
$newNumber = $lastNumber + 1;
} else {
$newNumber = 1;
}
return 'AGT-' . $tahun . '-' . str_pad($newNumber, 3, '0', STR_PAD_LEFT);
}- Update create() method:
public function create()
{
$kodeAnggota = $this->generateKodeAnggota();
return view('anggota.create', compact('kodeAnggota'));
}- Update View:
<input type="text"
name="kode_anggota"
class="form-control"
value="{{ old('kode_anggota', $kodeAnggota) }}"
readonly>Tugas 2: Export Anggota ke Excel (40%)
Instruksi: Implementasikan fitur export data anggota ke file Excel menggunakan package Laravel Excel (maatwebsite/excel) versi terbaru.
Spesifikasi:
- Install Package:
composer require maatwebsite/excel- Buat Export Class:
php artisan make:export AnggotaExport --model=Anggota- Isi Export Class:
File:
app/Exports/AnggotaExport.php
namespace App\Exports;
use App\Models\Anggota;
use Maatwebsite\Excel\Concerns\FromCollection;
use Maatwebsite\Excel\Concerns\WithHeadings;
class AnggotaExport implements FromCollection, WithHeadings
{
public function collection()
{
return Anggota::select([
'kode_anggota', 'nama', 'email', 'telepon', 'alamat',
'tanggal_lahir', 'jenis_kelamin', 'pekerjaan', 'status', 'tanggal_daftar',
])->get();
}
public function headings(): array
{
return [
'Kode', 'Nama', 'Email', 'Telepon', 'Alamat',
'Tanggal Lahir', 'Jenis Kelamin', 'Pekerjaan', 'Status', 'Tanggal Daftar',
];
}
}- Controller Method:
use App\Exports\AnggotaExport;
use Maatwebsite\Excel\Facades\Excel;
public function export()
{
return Excel::download(new AnggotaExport, 'anggota_' . date('Y-m-d_His') . '.xlsx');
}- Button di Index:
<a href="{{ route('anggota.export') }}" class="btn btn-success">
<i class="bi bi-file-excel"></i> Export Excel
</a>Referensi:
- Dokumentasi resmi: https://laravel-excel.com/ (opens in a new tab)
Catatan:
- Jangan gunakan box/spout atau SimpleExcel karena sudah tidak direkomendasikan dan tidak lagi dikembangkan.
Tugas 3: Advanced Search & Filter (30%)
Instruksi: Tambahkan fitur search dan filter advanced untuk anggota.
Spesifikasi:
- Form Search di Index:
<form action="{{ route('anggota.search') }}" method="GET">
<div class="row">
<div class="col-md-3">
<input type="text" name="keyword" class="form-control"
placeholder="Cari nama/email/telepon">
</div>
<div class="col-md-2">
<select name="jenis_kelamin" class="form-select">
<option value="">Semua Jenis Kelamin</option>
<option value="Laki-laki">Laki-laki</option>
<option value="Perempuan">Perempuan</option>
</select>
</div>
<div class="col-md-2">
<select name="status" class="form-select">
<option value="">Semua Status</option>
<option value="Aktif">Aktif</option>
<option value="Nonaktif">Nonaktif</option>
</select>
</div>
<div class="col-md-2">
<select name="pekerjaan" class="form-select">
<option value="">Semua Pekerjaan</option>
<option value="Mahasiswa">Mahasiswa</option>
<option value="Pegawai">Pegawai</option>
<option value="Wiraswasta">Wiraswasta</option>
</select>
</div>
<div class="col-md-3">
<button type="submit" class="btn btn-primary">
<i class="bi bi-search"></i> Cari
</button>
<a href="{{ route('anggota.index') }}" class="btn btn-secondary">
<i class="bi bi-x"></i> Reset
</a>
</div>
</div>
</form>- Controller Method:
public function search(Request $request)
{
$query = Anggota::query();
if ($request->keyword) {
$query->where(function($q) use ($request) {
$q->where('nama', 'like', '%' . $request->keyword . '%')
->orWhere('email', 'like', '%' . $request->keyword . '%')
->orWhere('telepon', 'like', '%' . $request->keyword . '%');
});
}
if ($request->jenis_kelamin) {
$query->where('jenis_kelamin', $request->jenis_kelamin);
}
if ($request->status) {
$query->where('status', $request->status);
}
if ($request->pekerjaan) {
$query->where('pekerjaan', $request->pekerjaan);
}
$anggotas = $query->latest()->get();
// Statistics
$totalAnggota = $anggotas->count();
$anggotaAktif = $anggotas->where('status', 'Aktif')->count();
$anggotaNonaktif = $anggotas->where('status', 'Nonaktif')->count();
return view('anggota.index', compact(
'anggotas',
'totalAnggota',
'anggotaAktif',
'anggotaNonaktif'
));
}Submission:
- Format: Link repository GitHub (sertakan screenshot di README)
- Deadline: Pertemuan 14
- Upload ke: Ngaji UIN Gusdur (submit link repository GitHub)
F. EVALUASI
1. Kuis Singkat
Pilihan Ganda:
-
DRY principle artinya:
- A. Do Repeat Yourself
- B. Don't Repeat Yourself
- C. Don't Run Yesterday
- D. Do Repeat Yesterday
-
Validation regex untuk nomor telepon Indonesia:
- A.
/^08[0-9]{8,11}$/ - B.
/^(\+62|62|0)[0-9]{9,12}$/ - C.
/^[0-9]{10,13}$/ - D. Semua benar
- A.
-
Validation untuk tanggal lahir harus:
- A.
before:today - B.
after:today - C.
equal:today - D.
between:today
- A.
-
Format date untuk MySQL adalah:
- A.
d-m-Y - B.
Y-m-d - C.
m/d/Y - D.
d/m/Y
- A.
-
Accessor di Model untuk umur menggunakan Carbon:
- A.
$this->tanggal_lahir->age - B.
$this->tanggal_lahir->year - C.
$this->tanggal_lahir->old - D.
$this->tanggal_lahir->calculate
- A.
-
Flatpickr digunakan untuk:
- A. Pilih warna
- B. Pilih file
- C. Pilih tanggal
- D. Pilih gambar
-
Validation untuk email unique saat update:
- A.
unique:anggota,email - B.
unique:anggota,email,$id - C.
unique:anggota - D.
email|unique
- A.
-
Form Request disimpan di folder:
- A.
app/Requests - B.
app/Http/Requests - C.
app/Forms - D.
resources/Requests
- A.
-
Method untuk ignore current record di unique validation:
- A. Skip current
- B. Except
- C. Ignore
- D. Tambah ID di akhir
-
Casting date di Model menggunakan:
- A.
protected $dates = [] - B.
protected $casts = ['field' => 'date'] - C.
protected $dateFormat - D.
protected $dateFields
- A.
Essay:
-
Jelaskan apa itu DRY principle dan berikan 3 contoh penerapannya di Laravel! (15 poin)
-
Apa perbedaan validation untuk create dan update pada field unique? Jelaskan dengan contoh code! (15 poin)
-
Buatlah validation rules untuk field "telepon" dengan regex format Indonesia (08xxxxxxxxxx)! (10 poin)
-
Jelaskan cara handle date input di Laravel dari form hingga disimpan ke database! (15 poin)
-
Apa keuntungan menggunakan Flatpickr dibanding HTML5 date input? (10 poin)
2. Checklist Kompetensi
| No | Kompetensi | Belum | Cukup | Mahir |
|---|---|---|---|---|
| 1 | Menerapkan DRY principle | â | â | â |
| 2 | Form Request validation | â | â | â |
| 3 | Advanced validation rules | â | â | â |
| 4 | Handle date input | â | â | â |
| 5 | Regex validation | â | â | â |
| 6 | CRUD consistency | â | â | â |
| 7 | Email validation | â | â | â |
| 8 | Unique validation update | â | â | â |
| 9 | Date picker implementation | â | â | â |
| 10 | Code organization | â | â | â |
G. REFERENSI
1. Dokumentasi Laravel 12
- Validation: https://laravel.com/docs/12.x/validation (opens in a new tab)
- Custom Validation Rules: https://laravel.com/docs/12.x/validation#custom-validation-rules (opens in a new tab)
- Date Mutators: https://laravel.com/docs/12.x/eloquent-mutators#date-mutators (opens in a new tab)
2. Libraries
- Flatpickr: https://flatpickr.js.org/ (opens in a new tab)
- Carbon (Date): https://carbon.nesbot.com/docs/ (opens in a new tab)
3. Regex Reference
- Regex101: https://regex101.com/ (opens in a new tab)
- Regular Expressions: https://www.regular-expressions.info/ (opens in a new tab)
H. PERSIAPAN PERTEMUAN 14
Topik: Authentication & Transaksi Peminjaman
Preview:
- Laravel Breeze installation
- Login & Register system
- Middleware authentication
- Database relationships
- Transaksi peminjaman buku
Yang Perlu Disiapkan:
- CRUD Buku dan Anggota sudah lengkap
- Pahami relasi database (hasMany, belongsTo)
- Study Laravel authentication
Pre-reading:
- Laravel Breeze: https://laravel.com/docs/12.x/starter-kits#laravel-breeze (opens in a new tab)
- Eloquent Relationships: https://laravel.com/docs/12.x/eloquent-relationships (opens in a new tab)
Selamat Belajar! đđĨ
End of Module - Pertemuan 13
Next: Pertemuan 14 - Authentication & Transaksi Peminjaman