Praktikum Desain & Pemrograman Web

Buku Panduan Teknis PixelGallery

Dokumentasi ringkas arsitektur aplikasi pengunggahan dan galeri gambar berbasis PHP 8, basis data PostgreSQL Supabase cloud, serta penanganan berkas serverless di Vercel.

Ringkasan Aplikasi

PixelGallery adalah implementasi tugas Jobsheet 08 (Full-Stack CRUD & Basis Data Relasional). Aplikasi ini berfokus pada alur lengkap pengelolaan gambar: pengguna dapat mengunggah file foto (JPG, PNG, WEBP), memberi metadata (judul, pengunggah, kategori, tahun, deskripsi), menjelajahi galeri dalam kartu responsif, mencari berdasarkan kata kunci, serta menghapus data.

Tujuan Praktikum Jobsheet 8
Menerapkan koneksi database relasional menggunakan ekstensi PHP PDO, mengamankan query dengan Prepared Statements, menangani file upload via $_FILES, dan merancang sistem yang kompatibel dengan arsitektur cloud serverless.

Alur Data & Arsitektur Sistem

Aplikasi ini mengadopsi alur data End-to-End yang memisahkan logika antarmuka, validasi server, penyimpanan persisten cloud, dan penyajian data biner secara efisien:

1

Pengguna Mengisi Form & Memilih Gambar

Data teks dan file biner dikirim melalui HTTP POST dengan tipe payload multipart.

2

Validasi Sisi Server & Konversi Base64 (upload-asset.php)

PHP memvalidasi status upload, whitelist ekstensi (JPG/PNG/WEBP), dan batas ukuran 5MB. Berkas di-encode ke Base64 Data URL agar aman di lingkungan serverless yang bersifat read-only.

3

Penyimpanan Persisten ke Cloud Supabase PostgreSQL

Query INSERT dieksekusi menggunakan Prepared Statement PDO ke tabel galeri di Supabase Seoul Pooler (port 6543).

4

Penyajian Gambar Teroptimasi (gambar.php)

Endpoint khusus biner mengalirkan data gambar langsung dengan header Content-Type dan HTTP Browser Cache sehingga ukuran dokumen HTML halaman katalog tetap ringan (< 10 KB).

1. Struktur HTML & CSS

A. Form Upload Berkas (upload-asset.php)

Secara default, form HTML mengirim data dengan encoding teks biasa. Untuk mengirimkan file gambar, form wajib menyertakan atribut enctype="multipart/form-data". Tanpa atribut ini, berkas tidak akan masuk ke array $_FILES di server.

collection/upload-asset.php HTML
<!-- Form pengunggahan gambar -->
<form action="upload-asset.php" method="POST" enctype="multipart/form-data">
    <input type="text" name="judul" placeholder="Judul foto" required>
    <input type="text" name="pengunggah" placeholder="Nama Anda" required>
    <select name="kategori" required>
        <option value="Wallpaper">Wallpaper</option>
        <option value="Pemandangan">Pemandangan & Alam</option>
        <option value="Fotografi">Fotografi Umum</option>
    </select>
    <input type="file" name="gambar" accept="image/png, image/jpeg, image/webp" required>
    <button type="submit">Unggah Gambar</button>
</form>

B. Grid Galeri Responsif (CSS Grid)

Koleksi gambar disusun menggunakan CSS Grid modern dengan fungsi bawaan repeat(auto-fill, minmax(280px, 1fr)). Pendekatan ini membuat galeri otomatis responsif di semua ukuran layar (desktop 3-4 kolom, tablet 2 kolom, dan smartphone 1 kolom) tanpa library eksternal.

assets/css/style.css CSS
.gallery-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
    gap: 1.5rem;
}

C. Modular Header & Footer Layout

Struktur halaman menerapkan prinsip modular DRY (Don't Repeat Yourself). Navigasi atas diletakkan di includes/header.php dan bagian bawah di includes/footer.php.

Variabel dinamis $base secara otomatis menghitung kedalaman direktori (../) menggunakan fungsi dirname() dan substr_count(), sehingga aset CSS/JS dan tautan navigasi tidak pernah rusak saat diakses dari subfolder lokal maupun root domain Vercel.

2. Logika Backend PHP & Basis Data

A. Koneksi Database PDO (includes/koneksi.php)

Koneksi database dibuat menggunakan PHP Data Objects (PDO) dengan driver pgsql. Koneksi dirancang memiliki failover: memprioritaskan cloud PostgreSQL Supabase (port 6543 pooler IPv4), dan jika dijalankan di komputer lokal tanpa internet otomatis mencoba PostgreSQL lokal (port 5433 / 5432).

includes/koneksi.php PHP
$dsn = "pgsql:host={$host};port={$port};dbname={$db};sslmode=require";
$pdo = new PDO($dsn, $user, $pass, [
    PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    PDO::ATTR_EMULATE_PREPARES   => false,
]);

B. Penanganan Upload File & Base64 Serverless (upload-asset.php)

Sebelum berkas disimpan, PHP menjalankan serangkaian validasi ketat:

  1. Status Unggah: Memeriksa $_FILES['gambar']['error'] === UPLOAD_ERR_OK.
  2. Ekstensi Berkas: Mengambil ekstensi menggunakan pathinfo($nama, PATHINFO_EXTENSION) dan mencocokkannya dengan daftar izin (jpg, jpeg, png, webp, gif).
  3. Batas Ukuran: Memastikan $_FILES['gambar']['size'] <= 5MB.
Strategi Serverless Vercel Read-Only
Pada serverless platform seperti Vercel, filesystem bersifat read-only dan ephemeral (sementara). File yang disimpan di folder fisik akan hilang saat container serverless mati. Karena itu, file biner dibaca via file_get_contents(), dikonversi menjadi format Base64 Data URL, dan disimpan persisten di kolom file_gambar (tipe TEXT) di PostgreSQL Supabase cloud.
collection/upload-asset.php PHP
// 1. Baca isi berkas dan encode ke Base64 Data URL
$konten = file_get_contents($_FILES['gambar']['tmp_name']);
$data_simpan = 'data:' . $tipe_mime . ';base64,' . base64_encode($konten);

// 2. Simpan menggunakan Prepared Statement anti-SQL Injection
$sql = "INSERT INTO galeri (judul, pengunggah, kategori, file_gambar, tahun, deskripsi) 
        VALUES (:judul, :pengunggah, :kategori, :file_gambar, :tahun, :deskripsi)";
$stmt = $pdo->prepare($sql);
$stmt->execute([
    ':judul'       => $judul,
    ':pengunggah'  => $pengunggah,
    ':kategori'    => $kategori,
    ':file_gambar' => $data_simpan,
    ':tahun'       => (int)$tahun,
    ':deskripsi'   => $deskripsi
]);

C. Streaming Biner Teroptimasi (gambar.php)

Jika Base64 gambar disematkan langsung di dalam tag <img src="data:image/..."> pada halaman katalog, dokumen HTML akan berukuran hingga puluhan megabyte. Di Vercel, ini akan memicu error fatal 500 FUNCTION_RESPONSE_PAYLOAD_TOO_LARGE (batas respon maksimal 4.5MB).

Solusinya adalah membuat endpoint gambar.php?id=ID yang membaca data gambar dari database, mendekode Base64 kembali ke format biner murni, dan mengirimkannya ke browser dengan header HTTP yang tepat:

gambar.php PHP
$id = (int)($_GET['id'] ?? 0);
$stmt = $pdo->prepare("SELECT file_gambar FROM galeri WHERE id = :id");
$stmt->execute([':id' => $id]);
$foto = $stmt->fetch(PDO::FETCH_ASSOC);

// Kirim HTTP Header & stream biner
header("Content-Type: " . $mime);
header("Cache-Control: public, max-age=86400"); // Cache browser 24 jam
echo base64_decode($biner_data);
exit;

D. Pencarian & Penghapusan Data (catalog.php)

Pencarian: Menggunakan klausa SQL ILIKE :q (PostgreSQL case-insensitive matching) untuk mencocokkan kata kunci pada judul, pengunggah, atau kategori tanpa membedakan huruf besar/kecil.

Penghapusan: Menerima parameter GET ?hapus=ID, divalidasi dengan filter_var(..., FILTER_VALIDATE_INT), lalu mengeksekusi DELETE FROM galeri WHERE id = :id.

E. Flash Message Session State

Pemberitahuan sukses atau gagal disimpan ke variabel sesi $_SESSION['flash'] sebelum melakukan HTTP Redirect (header("Location: ...")). Saat halaman tujuan dimuat, notifikasi dirender dan langsung dibersihkan dengan unset($_SESSION['flash']) agar pesan tidak muncul berulang saat halaman di-refresh.

3. Interaktivitas JavaScript (UX & DOM)

A. Mobile Navigation Toggle (Hamburger Menu)

Pada tampilan mobile, menu navigasi disembunyikan. Saat tombol hamburger #nav-toggle-btn diklik, fungsi initNavToggle() memanipulasi class DOM dengan menjalankan nav.classList.toggle("nav-open") secara mulus tanpa reload halaman.

B. Konfirmasi Hapus Data Interaktif

Tautan tombol hapus pada kartu galeri dilengkapi atribut inline:

Inline JavaScript JS
onclick="return confirm('Apakah Anda yakin ingin menghapus gambar ini?');"

Jika pengguna menekan tombol Cancel, fungsi confirm() mengembalikan nilai false dan peramban membatalkan aksi navigasi URL sehingga data tidak terhapus tanpa sengaja.

C. Validasi Sisi Klien (app.js)

Event listener submit pada form upload memeriksa kelengkapan kolom wajib (judul, nama pengunggah, berkas foto) sebelum data dikirim ke server. Jika terdapat kolom kosong, form menampilkan pesan kesalahan secara instan di bawah input bersangkutan.

4. Cheat Sheet Tanya-Jawab Evaluasi Dosen

Gunakan poin-poin jawaban di bawah ini saat dosen menanyakan konsep teknis aplikasi:

1. Mengapa memilih PHP PDO daripada ekstensi MySQLi?
Kunci Jawaban PDO bersifat database-agnostic: PDO mendukung berbagai driver basis data (PostgreSQL, MySQL, SQLite) hanya dengan mengganti string DSN, sedangkan MySQLi hanya terbatas pada MySQL. Selain itu, PDO menyediakan Prepared Statements native dan penanganan kesalahan berbasis Exception yang konsisten.
2. Bagaimana aplikasi ini mencegah serangan SQL Injection?
Kunci Jawaban Menggunakan Prepared Statements & Parameter Binding: Semua nilai dari input pengguna tidak pernah disambung langsung menggunakan konkatenasi string ($sql = "SELECT ... " . $input). Kami menggunakan placeholder (:judul) melalui $pdo->prepare() dan $stmt->execute(). Mesin basis data memperlakukan nilai tersebut murni sebagai data, bukan perintah SQL yang dapat dieksekusi.
3. Bagaimana cara kerja upload file di serverless Vercel yang read-only?
Kunci Jawaban Encoding Base64 ke Supabase PostgreSQL: Di Vercel, filesystem bersifat read-only dan container serverless bersifat sementara (ephemeral). Jika file disimpan di folder fisik biasa, file akan hilang saat instance dimatikan. Karena itu, file biner dibaca dan diubah ke format Base64 Data URL yang tersimpan aman secara persisten di kolom file_gambar basis data cloud Supabase.
4. Mengapa dibuat file gambar.php terpisah dan tidak dicetak langsung di src?
Kunci Jawaban Mencegah Payload Limit & Mengaktifkan Cache Browser: Menaruh puluhan gambar Base64 langsung di dalam HTML akan membuat ukuran halaman membengkak hingga puluhan MB (memicu error Vercel 500 Function Response Payload Too Large). Dengan endpoint gambar.php?id=ID, HTML tetap sangat kecil (< 10 KB), gambar dialirkan secara independen sebagai biner murni, dan browser dapat melakukan HTTP caching (Cache-Control: public, max-age=86400).
5. Apa fungsi htmlspecialchars() pada saat menampilkan data ke layar?
Kunci Jawaban Pencegahan Serangan XSS (Cross-Site Scripting): Fungsi htmlspecialchars() menyaring karakter khusus HTML (seperti <, >, &, dan ") menjadi entitas karakter HTML yang aman (seperti &lt; dan &gt;). Hal ini mencegah eksekusi skrip berbahaya jika pengguna memasukkan tag <script> pada input judul atau pengunggah.