Pendahuluan — daftar panjang butuh potongan

Artikel ini adalah #63 (ini) di Seri 5: Laravel Lanjutan (di roadmap sering disebut Framework-based). Domain tetap perpustakaan mini.

Di Relasi Eloquent: Anggota & Peminjaman (#62) kamu sudah menghubungkan anggota dan pinjaman. Saat katalog atau daftar pinjam membesar, mengirim semua baris sekaligus membuat jawaban lambat dan sulit dibaca. Kita butuh: potong per halaman, saring status, dan cari judul.

Awam: bayangkan loket mencetak daftar buku 5 baris per halaman, bisa pilih “hanya aktif”, dan bisa ketik “PHP” untuk menyempitkan. Itu pagination + filter + pencarian.

Prasyarat: sudah baca Relasi Eloquent (#62) dan CRUD ubah & hapus (#61). Pakai Laravel 11+.

Spesifikasi fitur — apa yang kita bangun?

Daftar singkat yang bisa dijelaskan ke teman:

  1. Halaman — kirim sebagian daftar (misalnya 5 item) + info “halaman berapa / total”.
  2. Filter — saring menurut status (aktif / kembali) atau field sederhana lain.
  3. Pencarian — cari kata di judul (atau nama) tanpa mengirim seluruh katalog mentah.

Awam: urutan kerja yang nyaman: saring dulu -> cari dulu -> baru potong halaman. Kalau potong dulu baru saring, hasilnya bisa “aneh” (halaman kosong padahal data ada).

Istilah — ringkas untuk daftar panjang

Istilah Arti awam Catatan
Pagination Memotong daftar jadi halaman-halaman Bukan “menghapus data”
Halaman / page Nomor lembar yang diminta pemanggil Mulai dari 1
Per halaman / per_page Berapa item di satu lembar Contoh: 5 atau 10
Filter Saring baris menurut aturan (status, tahun, …) Beda dengan cari teks bebas
Pencarian Cari kata di judul/nama Di SQL sering pakai pola LIKE

Urutan belajar: array PHP dulu -> potong & saring manual -> baru bungkus Laravel.

Kenapa PHP biasa dulu?

Ide “ambil lembar 2 dari daftar yang sudah disaring” lebih mudah dirasakan di array. Kalau alurnya klik, cuplikan paginate() terasa bungkus yang sama.

<?php
// Mini: saring status, cari judul, potong halaman.
$katalog = [
    ["id" => 1, "judul" => "Dasar PHP", "status" => "aktif"],
    ["id" => 2, "judul" => "Belajar Laravel", "status" => "aktif"],
    ["id" => 3, "judul" => "PHP Lanjut", "status" => "kembali"],
    ["id" => 4, "judul" => "API Dasar", "status" => "aktif"],
    ["id" => 5, "judul" => "Eloquent Relasi", "status" => "aktif"],
    ["id" => 6, "judul" => "Validasi Request", "status" => "kembali"],
];

$status = "aktif";
$cari = "PHP";
$page = 1;
$perPage = 2;

$hasil = [];
foreach ($katalog as $buku) {
    if ($buku["status"] !== $status) {
        continue;
    }
    if ($cari !== "" && stripos($buku["judul"], $cari) === false) {
        continue;
    }
    $hasil[] = $buku;
}

$total = count($hasil);
$mulai = ($page - 1) * $perPage;
$isi = array_slice($hasil, $mulai, $perPage);

echo json_encode([
    "ok" => true,
    "page" => $page,
    "per_page" => $perPage,
    "total" => $total,
    "data" => $isi,
], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT), PHP_EOL;

Output (bentuknya mirip):

{
    "ok": true,
    "page": 1,
    "per_page": 2,
    "total": 1,
    "data": [
        {"id": 1, "judul": "Dasar PHP", "status": "aktif"}
    ]
}

Awam: filter status aktif dulu, lalu cari “PHP”, baru potong. “PHP Lanjut” statusnya kembali — tidak ikut. array_slice hanya mengambil lembar yang diminta.

Daftar panjang: Filter -> Cari -> Potong halaman -> JSON Filter status / aturan Cari kata di judul Potong page + per_page JSON data + total Jangan potong dulu baru saring — halaman bisa kosong padahal data masih ada. Relasi tetap (langkah sebelumnya); di sini kita merapikan cara membaca daftar yang panjang.
Setelah relasi jelas, #63 (ini) merapikan cara membaca daftar panjang lewat API.

Alur daftar — PHP sederhana

Pemanggil — aplikasi atau alat yang memanggil API — mengirim page, opsional status, dan opsional kata cari.

<?php
$katalog = [
    ["id" => 1, "judul" => "Dasar PHP", "status" => "aktif"],
    ["id" => 2, "judul" => "Belajar Laravel", "status" => "aktif"],
    ["id" => 3, "judul" => "PHP Lanjut", "status" => "kembali"],
    ["id" => 4, "judul" => "API Dasar", "status" => "aktif"],
];

function daftarBuku(array $katalog, int $page, int $perPage, ?string $status, string $cari): array
{
    if ($page < 1 || $perPage < 1) {
        return ["status" => 422, "body" => ["pesan" => "Isian halaman belum rapi"]];
    }

    $hasil = [];
    foreach ($katalog as $buku) {
        if ($status !== null && $buku["status"] !== $status) {
            continue;
        }
        if ($cari !== "" && stripos($buku["judul"], $cari) === false) {
            continue;
        }
        $hasil[] = $buku;
    }

    $total = count($hasil);
    $mulai = ($page - 1) * $perPage;
    $isi = array_values(array_slice($hasil, $mulai, $perPage));

    return [
        "status" => 200,
        "body" => [
            "ok" => true,
            "page" => $page,
            "per_page" => $perPage,
            "total" => $total,
            "data" => $isi,
        ],
    ];
}

$r = daftarBuku($katalog, 1, 2, "aktif", "PHP");
http_response_code($r["status"]);
echo json_encode($r["body"], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT), PHP_EOL;

Awam: stripos = cari kata di judul tanpa peduli huruf besar/kecil. array_slice = potong lembar. 422 = isian halaman kotor (sama ide dengan isian belum rapi di CRUD ubah & hapus (#61)).

Laravel — cuplikan pagination & filter (bukan file mandiri)

Di proyek Laravel, ide yang sama sering ditulis di pengatur kode (controller) / pekerja (service). Cuplikan di bawah bukan dijalankan dengan php file.php:

<?php
// Cuplikan Laravel (bukan file mandiri) — daftar buku berhalaman.
use App\Models\Buku;
use Illuminate\Http\Request;

public function index(Request $request)
{
    $perPage = (int) $request->integer('per_page', 10);
    $query = Buku::query();

    if ($request->filled('status')) {
        $query->where('status', $request->string('status'));
    }

    if ($request->filled('cari')) {
        $kata = $request->string('cari');
        $query->where('judul', 'like', "%{$kata}%");
    }

    return response()->json($query->paginate($perPage));
}

Awam:

  • paginate($perPage) = potong halaman + isi info ringkas (total, halaman) otomatis
  • where('status', …) = filter aturan tepat
  • like '%kata%' = judul mengandung kata (pola pencarian SQL)
  • filled(...) = “isian ada dan tidak kosong” — boleh diikuti, tidak wajib dihafal
<?php
// Cuplikan Laravel — contoh jawaban paginate (bentuk ringkas).
// Bukan file mandiri; hanya ilustrasi bentuk JSON.
[
    "data" => [ /* item halaman ini */ ],
    "current_page" => 1,
    "per_page" => 10,
    "total" => 42,
]

Awam: nama info tambahan bisa sedikit berbeda antar versi; yang penting kamu paham tiga angka: halaman sekarang, per halaman, dan total.

Pola Dasar — daftar panjang yang rapi

  1. 1
    Pastikan relasi sudah klik
    Fondasi di Relasi Eloquent (#62) membantu daftar pinjam tetap bermakna.
  2. 2
    Saring dulu
    Filter status (atau aturan lain) sebelum potong halaman.
  3. 3
    Cari di judul
    Kata kunci menyempitkan hasil; kosong = tidak menyaring teks.
  4. 4
    Potong halaman
    page + per_page; jaga angka >= 1.
  5. 5
    Kirim info ringkas
    Total + halaman sekarang supaya layar pemanggil bisa buat tombol “berikutnya”.
  6. 6
    Uji tiga jalur
    Halaman kotor · filter ketat · cari + halaman 2.

Kode lengkap — demo mandiri daftar panjang

Simpan sebagai laravel_pagination_filter_pencarian_demo.php, lalu jalankan php laravel_pagination_filter_pencarian_demo.php:

<?php
declare(strict_types=1);

$katalog = [
    ["id" => 1, "judul" => "Dasar PHP", "status" => "aktif"],
    ["id" => 2, "judul" => "Belajar Laravel", "status" => "aktif"],
    ["id" => 3, "judul" => "PHP Lanjut", "status" => "kembali"],
    ["id" => 4, "judul" => "API Dasar", "status" => "aktif"],
    ["id" => 5, "judul" => "Eloquent Relasi", "status" => "aktif"],
    ["id" => 6, "judul" => "Validasi Request", "status" => "kembali"],
];

function daftarBuku(array $katalog, int $page, int $perPage, ?string $status, string $cari): array
{
    if ($page < 1 || $perPage < 1) {
        return ["status" => 422, "body" => ["pesan" => "Isian halaman belum rapi"]];
    }

    $hasil = [];
    foreach ($katalog as $buku) {
        if ($status !== null && $buku["status"] !== $status) {
            continue;
        }
        if ($cari !== "" && stripos($buku["judul"], $cari) === false) {
            continue;
        }
        $hasil[] = $buku;
    }

    $total = count($hasil);
    $mulai = ($page - 1) * $perPage;
    $isi = array_values(array_slice($hasil, $mulai, $perPage));

    return [
        "status" => 200,
        "body" => [
            "ok" => true,
            "page" => $page,
            "per_page" => $perPage,
            "total" => $total,
            "data" => $isi,
        ],
    ];
}

function demo(string $judul, callable $aksi): void
{
    echo "=== {$judul} ===", PHP_EOL;
    $hasil = $aksi();
    echo "status: ", $hasil["status"], PHP_EOL;
    echo json_encode($hasil["body"], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT), PHP_EOL, PHP_EOL;
}

demo("Halaman kotor -> 422", function () use ($katalog) {
    return daftarBuku($katalog, 0, 2, "aktif", "");
});

demo("Aktif + cari PHP -> 200", function () use ($katalog) {
    return daftarBuku($katalog, 1, 5, "aktif", "PHP");
});

demo("Semua status halaman 2 -> 200", function () use ($katalog) {
    return daftarBuku($katalog, 2, 2, null, "");
});

Awam: demo(...) hanya membungkus output di terminal. callable = sesuatu yang bisa dipanggil seperti fungsi. declare(strict_types=1); membuat tipe lebih ketat — boleh diikuti, tidak wajib dihafal. Alur penting: halaman kotor, filter+cari, halaman 2 tanpa filter.

Kesalahan umum

Gejala Penyebab tipikal Perbaikan awam
Halaman kosong padahal data ada Potong dulu baru filter/cari Urutan: saring -> cari -> potong
Hasil “PHP” tidak ketemu Huruf besar/kecil atau salah kolom Pakai pencarian tanpa peduli kapital; cek kolom judul
422 saat buka daftar page / per_page <= 0 Validasi angka >= 1
Daftar lambat saat data banyak Mengirim semua baris tanpa potong Pakai pagination; filter sebelum potong

Latihan singkat

  1. Ubah demo: tambah kasus “cari kosong + status kembali” dan hitung totalnya.
  2. Jelaskan ke teman: kenapa saring harus sebelum potong halaman.
  3. Tulis satu kalimat: beda filter status dengan pencarian judul.

FAQ singkat

Berapa per_page yang bagus?
Untuk belajar, 5–10 sudah nyaman. Di produksi, batasi maksimum supaya pemanggil tidak minta 10.000 sekaligus.

Apakah wajib pakai paginate() Laravel?
Tidak wajib memahami setiap opsi dulu. Yang penting: potong + kirim total. paginate() tinggal membungkus ide yang sama.

Ke mana setelah ini?
Berikutnya: Authorization Policy: Siapa Boleh Ubah (#64) — aturan izin siapa boleh ubah/hapus data.

Kesimpulan

Kamu sudah merapikan daftar panjang: filter menyaring aturan, pencarian menyempitkan teks, pagination memotong lembar. PHP array dulu; Laravel paginate() / where / like adalah bungkus yang sama.

Seri 5 progress: langkah #63 (ini) · 3/8 Laravel Lanjutan · prasyarat: Relasi Eloquent (#62) LIVE. Berikutnya: Authorization Policy: Siapa Boleh Ubah (#64).