refactor: optimize AcademicTermService methods and improve query handling

This commit is contained in:
Yoga Pangestu 2026-09-03 12:52:32 +07:00
parent f69ed0bc15
commit 8055d4ec5d
3 changed files with 47 additions and 39 deletions

View File

@ -241,18 +241,26 @@ ## 5. Kode Ringkas & Idiomatis Laravel
| `App::make('Class')` | `app('Class')` |
- **Jangan panggil `env()` di luar file config.** Simpan dulu ke `config/*.php`, lalu akses lewat `config('nama.key')`. Ini berlaku juga untuk kredensial/flag baru yang ditambahkan.
- **Tidak pakai DocBlock** untuk mendeskripsikan fungsi. Gunakan nama method yang deskriptif + return type/param type hint dari PHP. DocBlock hanya boleh dipakai kalau memang dibutuhkan untuk generic type (`@return Collection<int, Student>`) yang tidak bisa diekspresikan native PHP — bukan untuk narasi.
```php
// Bad
/**
* The function checks if given string is a valid ASCII string
* @param string $string
* @return bool
*/
public function checkString($string) { }
- **DocBlock boleh dipakai kalau memang dibutuhkan dan penting** — bukan default yang dipasang di semua method. Untuk method standar (CRUD biasa, atau apa pun yang sudah jelas maksudnya dari nama method + return/param type PHP), **jangan** tambah DocBlock — itu pemborosan. DocBlock baru layak ditulis untuk kasus yang memang penting, misalnya:
1. **Generic type** yang tidak bisa diekspresikan native PHP, mis. `@return Collection<int, Student>`, `@param array<int, string>`.
2. **Perilaku non-obvious** yang bisa bikin pembaca salah paham kalau tidak dijelaskan: aturan bisnis tersembunyi, constraint yang tidak kelihatan dari nama method, workaround keterbatasan interface/library.
// Good
public function isValidAsciiString(string $string): bool { }
```php
// Bad — DocBlock generik yang cuma mengulang nama method, tidak nambah informasi
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array { ... }
// Good — tanpa DocBlock sama sekali, karena nama + return type sudah cukup jelas
public function rules(): array { ... }
// Good — DocBlock dipertahankan karena memang menjelaskan hal yang tidak
// kelihatan dari signature method (fallback logic yang bisa mengejutkan)
/**
* Falls back to the one with the latest start date if none is explicitly active.
*/
public function getActive(): ?AcademicTerm { ... }
```
- Komentar kode hanya untuk menjelaskan **kenapa**, bukan **apa** — kalau nama variabel/method sudah jelas, jangan tambah komentar.

View File

@ -24,7 +24,7 @@ public function index(PaginatedRequest $request): Response
'academicTerms' => $this->service->paginated(
...$request->validatedWithDefaults(),
semester: $request->validated('semester'),
isActive: $request->filled('is_active') ? filter_var($request->validated('is_active'), FILTER_VALIDATE_BOOLEAN) : null,
isActive: $request->filled('is_active') ? $request->boolean('is_active') : null,
),
'filters' => $request->only(['semester', 'is_active']),
]);

View File

@ -9,30 +9,14 @@
class AcademicTermService
{
public function getAllForSelect(): Collection
{
return AcademicTerm::select(['id', 'academic_year', 'semester', 'start_date', 'end_date', 'is_active', 'open_semesters'])->latest()->get();
}
/**
* The academic term currently marked active, falling back to the one with
* the latest start date if none is explicitly active.
*/
public function getActive(): ?AcademicTerm
{
return AcademicTerm::where('is_active', true)->first()
?? AcademicTerm::orderByDesc('start_date')->first();
}
public function paginated(int $perPage = 25, string $search = '', ?string $semester = null, ?bool $isActive = null): LengthAwarePaginator
{
return AcademicTerm::query()
->select(['id', 'academic_year', 'semester', 'start_date', 'end_date', 'is_active', 'open_semesters'])
->when($search, fn($q) => $q->where('academic_year', 'like', "%{$search}%"))
->when($semester, fn($q) => $q->where('semester', $semester))
->when($isActive !== null, fn($q) => $q->where('is_active', $isActive))
->when($search, fn ($q) => $q->where('academic_year', 'like', "%{$search}%"))
->when($semester, fn ($q) => $q->where('semester', $semester))
->when($isActive !== null, fn ($q) => $q->where('is_active', $isActive))
->latest()
->paginate($perPage);
->paginate($perPage, ['id', 'academic_year', 'semester', 'start_date', 'end_date', 'is_active', 'open_semesters']);
}
public function create(array $data): AcademicTerm
@ -45,13 +29,14 @@ public function create(array $data): AcademicTerm
public function update(AcademicTerm $academicTerm, array $data): AcademicTerm
{
$academicTerm->academic_year = $data['academic_year'];
$academicTerm->semester = $data['semester'];
$academicTerm->start_date = $data['start_date'];
$academicTerm->end_date = $data['end_date'];
$academicTerm->is_active = $data['is_active'];
$academicTerm->open_semesters = $this->normalizeOpenSemesters($data['open_semesters'] ?? []);
$academicTerm->update();
$academicTerm->update([
'academic_year' => $data['academic_year'],
'semester' => $data['semester'],
'start_date' => $data['start_date'],
'end_date' => $data['end_date'],
'is_active' => $data['is_active'],
'open_semesters' => $this->normalizeOpenSemesters($data['open_semesters'] ?? []),
]);
return $academicTerm;
}
@ -72,6 +57,21 @@ public function updateActiveStatus(AcademicTerm $academicTerm, bool $isActive):
});
}
public function getAllForSelect(): Collection
{
return AcademicTerm::latest()->get(['id', 'academic_year', 'semester', 'start_date', 'end_date', 'is_active', 'open_semesters']);
}
/**
* The academic term currently marked active, falling back to the one with
* the latest start date if none is explicitly active.
*/
public function getActive(): ?AcademicTerm
{
return AcademicTerm::where('is_active', true)->first()
?? AcademicTerm::orderByDesc('start_date')->first();
}
/**
* @param array<int, mixed> $openSemesters
* @return array<int, int>