Aplikasi web hidup dari data yang dikirim pengguna: nama pendaftar, kata kunci pencarian, isi keranjang belanja. Form HTML adalah pintu masuk data tersebut, dan PHP menyediakan mekanisme penerimaannya sejak bahasa ini pertama kali dirancang. Pada chapter ini kita membedah kerja sama keduanya dari nol: anatomi sebuah form, cara browser mengemas isi field menjadi kiriman, cara PHP membuka kiriman itu kembali melalui $_GET dan $_POST, hingga pola satu berkas yang sekaligus menampilkan dan memproses form. Sepanjang jalan kita juga memasang kebiasaan penting seperti output escaping yang akan dipakai berulang kali di seluruh section ini.
Anatomi Form HTML #
Form HTML dibangun dari elemen <form> yang membungkus field-field input. Dua atribut pada elemen tersebut menentukan ke mana dan bagaimana data dikirim: action berisi URL tujuan pengiriman, sedangkan method menentukan cara browser membungkus datanya. Berikut form pendaftaran paling sederhana yang tetap lengkap:
<form action="/daftar.php" method="post">
<label for="nama">Nama lengkap</label>
<input type="text" id="nama" name="nama">
<label for="email">Alamat email</label>
<input type="email" id="email" name="email">
<button type="submit">Daftar</button>
</form>Mari kita bedah peran setiap bagiannya:
<form action="/daftar.php" method="post">— wadah form sekaligus alamat tujuan; saat tombol ditekan, browser mengirim seluruh isi field ke URL/daftar.php.<label for="nama">— menghubungkan teks label ke field melalui nilaiid; mengeklik label ikut mengaktifkan fieldnya, dan pembaca layar menjadikannya pengenal field.<input ... name="nama">— field isian; atributnameinilah yang menjadi nama data di sisi PHP.<button type="submit">— pemicu pengiriman; browser mengumpulkan nilai semua field bernama, lalu mengirimkannya sebagai satu paket.
<input type="text" id="nama" placeholder="Nama lengkap">Atribut id, placeholder, dan type pada field di atas memperindah tampilan, tetapi ketiadaan name membuat browser melewatkannya saat merangkai kiriman. Kesalahan ini sering terjadi setelah mengganti nama field atau menyalin field dari tempat lain, dan gejalanya khas: PHP menerima field yang hilang tanpa pesan kesalahan apa pun.
Sisi PHP Menerima Kiriman #
Di sisi server, PHP membuka kiriman tersebut menjadi dua superglobal: $_GET untuk data yang ditempelkan pada URL, dan $_POST untuk data yang dikirim di body request. Keduanya adalah array asosiatif yang diisi PHP secara otomatis sebelum baris pertama skrip Anda dijalankan, dengan kunci yang persis mengikuti atribut name di form. Untuk melihat mekanismenya tanpa web server, kita bisa mensimulasikan perjalanan data dengan dua fungsi bawaan: http_build_query() menyusun query string seperti yang dilakukan browser, lalu parse_str() memecahnya kembali seperti yang dilakukan PHP.
<?php
$kataKunci = "kopi gayo";
$halaman = 2;
// Browser menyusun query string dari isi field form
$queryString = http_build_query(["q" => $kataKunci, "halaman" => $halaman]);
echo "GET /pencarian.php?$queryString\n";
// PHP memecahnya kembali menjadi $_GET
parse_str($queryString, $get);
echo "q = {$get['q']}\n";
echo "halaman = {$get['halaman']}\n";Output yang Dihasilkan:
GET /pencarian.php?q=kopi+gayo&halaman=2
q = kopi gayo
halaman = 2Fungsi http_build_query() mengubah array menjadi format standar URL q=kopi+gayo&halaman=2 — format yang akan Anda lihat muncul di address bar setelah sebuah form method="get" dikirim. Fungsi parse_str() kemudian memecah string tersebut kembali menjadi array $get, meniru pekerjaan PHP saat mengisi $_GET dari query string yang diterimanya. Spasi berubah menjadi tanda + saat dikemas dan kembali menjadi spasi saat dibuka — kedua arah perjalanan data tersebut simetris.
Bagaimana dengan kiriman POST? PHP mengisi $_POST dari body request menggunakan mekanisme parsing yang serupa. Struktur akhirnya dapat kita lihat langsung:
<?php
// Di lingkungan uji kita isi $_POST dengan array tiruan;
// di web nyata PHP mengisinya otomatis dari kiriman form
$_POST = [
"nama" => "Budi Santoso",
"email" => "[email protected]",
"hobi" => "memasak",
];
echo "PHP menerima " . count($_POST) . " field:\n";
foreach ($_POST as $field => $nilai) {
echo "- $field => \"$nilai\"\n";
}Output yang Dihasilkan:
PHP menerima 3 field:
- nama => "Budi Santoso"
- email => "[email protected]"
- hobi => "memasak"Pada demo ini kita menulis $_POST secara manual supaya struktur arraynya terlihat tanpa menjalankan web server sungguhan. Perhatikan dua sifat penting yang akan sering Anda temui: kunci array persis mengikuti atribut name pada form, dan setiap nilai datang sebagai string — angka 2 pada field jumlah barang pun tiba sebagai "2", sehingga operasi aritmetika perlu konversi eksplisit ke int atau float. Perbedaan mendalam antara GET dan POST — lokasi data, semantik, hingga perilaku tombol refresh — kita bedah khusus pada chapter berikutnya.
Alur Render–Kirim–Proses #
Satu putaran lengkap form selalu terdiri atas tiga peristiwa: server merender form kosong, pengguna mengisi lalu mengirim, dan server memproses kiriman sebelum mengirim respons. Diagram berikut memetakan putaran tersebut:
Diagram ini menegaskan dua fakta yang membedakan pemrograman web dari skrip CLI. Pertama, halaman form dan hasil pemrosesan adalah dua request yang berbeda — skrip daftar.php dijalankan dua kali dari nol, dan PHP tidak mengingat apa pun di antara keduanya. Kedua, putaran boleh berulang: ketika validasi gagal, respons ketiga berisi form yang sama dengan nilai lama dan pesan error, lalu pengguna mencoba lagi — inilah yang disebut alur render–submit–process.
Pola Self-Processing #
Cara termudah menata alur tersebut adalah pola self-processing: satu berkas sekaligus menampilkan form dan memproses kiriman. Gerbangnya adalah metode request — saat $_SERVER["REQUEST_METHOD"] bernilai "POST", skrip memproses; jika tidak, skrip cukup merender form kosong:
<?php
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$hasil = prosesPendaftaran($_POST);
// ... sukses: tampilkan halaman terima kasih
} else {
// GET biasa: tampilkan form kosong pertama kali
}Inti berkas tersebut adalah handler murni yang menerima array kiriman dan mengembalikan hasil terstruktur — bentuk yang bisa kita uji langsung di CLI tanpa web server maupun browser:
<?php
function prosesPendaftaran(array $kiriman): array
{
$nama = trim($kiriman["nama"] ?? "");
$email = trim($kiriman["email"] ?? "");
$errors = [];
if ($nama === "") {
$errors[] = "Nama wajib diisi";
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errors[] = "Email tidak valid";
}
return ["nama" => $nama, "email" => $email, "errors" => $errors];
}
// Dua kiriman tiruan: satu lengkap, satu kacau
$kiriman = [
["nama" => " Siti Rahayu ", "email" => "[email protected]"],
["nama" => "", "email" => "bukan-email"],
];
foreach ($kiriman as $i => $data) {
$hasil = prosesPendaftaran($data);
if ($hasil["errors"] === []) {
printf("kiriman %d: LOLOS - selamat datang, %s\n", $i + 1, $hasil["nama"]);
} else {
printf("kiriman %d: DITOLAK (%s)\n", $i + 1, implode("; ", $hasil["errors"]));
}
}Output yang Dihasilkan:
kiriman 1: LOLOS - selamat datang, Siti Rahayu
kiriman 2: DITOLAK (Nama wajib diisi; Email tidak valid)Mari kita bedah bagian-bagiannya:
trim($kiriman["nama"] ?? "")— operator??memberi string kosong ketika field tidak ada di kiriman, lalutrim()memangkas spasi di awal dan akhir; kiriman pertama" Siti Rahayu "menjadi"Siti Rahayu".- Blok
$errors— dua aturan validasi dasar; setiap pelanggaran menambah satu pesan ke daftar, bukan langsung berhenti, supaya pengguna melihat semua kesalahan sekaligus. - Nilai balik array — handler mengembalikan data bersih beserta daftar error, bukan mencetak HTML; keputusan menampilkan hasil tetap di tangan pemanggil.
- Baris
printf(...)— hanya simulasi alur di CLI; di web nyata,LOLOSdigantikan render halaman sukses danDITOLAKdigantikan render ulang form.
Berkas pendukung form-demo.php di bagian bawah halaman merangkum pola lengkapnya: fungsi prosesPendaftaran() di atas, fungsi renderForm() yang mencetak ulang form beserta pesan error dan nilai lama, serta simulasi dua request berurutan yang memperlihatkan kedua jalur hasil.
Menampilkan Ulang Nilai dengan Aman #
Ketika validasi gagal, form yang dirender ulang sebaiknya mengembalikan nilai yang sudah diisi pengguna — kebiasaan yang disebut sticky value — supaya pengguna hanya memperbaiki bagian yang salah. Namun nilai tersebut berasal dari pengguna, dan pengguna tidak selalu baik hati. Lihat apa yang terjadi bila field nama diisi muatan jahat:
<?php
// Nama yang diisi pengguna jahil di field "nama"
$nama = 'Siti"><script>alert("kuki-dicuri")</script>';
echo "Tanpa escaping:\n";
echo "<p>Selamat datang, $nama</p>\n\n";
echo "Dengan escaping:\n";
echo "<p>Selamat datang, " . htmlspecialchars($nama, ENT_QUOTES) . "</p>\n";Output yang Dihasilkan:
Tanpa escaping:
<p>Selamat datang, Siti"><script>alert("kuki-dicuri")</script></p>
Dengan escaping:
<p>Selamat datang, Siti"><script>alert("kuki-dicuri")</script></p>Pada baris tanpa escaping, tag <script> tercetak sebagai HTML sungguhan — di browser, kode JavaScript milik penyerang akan dieksekusi dan bisa mencuri cookie sesi pengguna lain yang melihat halaman tersebut. Versi dengan htmlspecialchars() mengubah karakter <, >, ", dan ' menjadi entitas HTML (<, >, ") sehingga browser menampilkannya sebagai teks biasa, bukan mengeksekusinya sebagai markup. Serangan ini bernama cross-site scripting (XSS) dan kita bedah tuntas pada chapter Form Security
.
Form yang Berfungsi Tanpa JavaScript #
Satu sifat form HTML yang sering dilupakan: form bekerja penuh tanpa satu baris JavaScript pun. Browser mengumpulkan field, menvalidasi atribut seperti required dan type="email", lalu mengirim data — semuanya perilaku asli platform web. Atribut validasi HTML5 tersebut berguna sebagai umpan balik cepat, tetapi validasi klien selalu bisa dilewati: pengguna bisa mematikan JavaScript, atau mengirim request rakitan langsung ke URL tujuan.
Karena itu posisi masing-masing lapisan harus jelas: HTML5 untuk kenyamanan, PHP untuk kebenaran. Aturan Nama wajib diisi pada handler kita di atas tetap dijalankan apa pun yang terjadi di sisi browser, dan itulah satu-satunya tempat keputusan diterima atau ditolaknya data. Dengan fondasi ini, kita siap membedah keputusan desain pertama setiap form: mengirim dengan GET atau POST.
Ringkasan #
Form HTML mengemas isi field bernama menjadi kiriman, PHP membukanya menjadi superglobal $_GET dan $_POST, dan pola self-processing menyatukan render serta pemrosesan dalam satu berkas dengan gerbang REQUEST_METHOD. Handler yang menerima dan mengembalikan array memungkinkan alur diuji tanpa web server, sementara htmlspecialchars() menjaga nilai pengguna agar tidak menjadi pintu masuk XSS. Dua method pengiriman yang selama ini kita pakai bergantian punya konsekuensi sangat berbeda — pembahasan itu dibuka pada chapter GET vs POST
.
Referensi #
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
form-demo.php
Unduh berkas<?php
declare(strict_types=1);
// Pola self-processing: satu berkas menampilkan DAN memproses form.
// Di web nyata, gerbangnya: if ($_SERVER["REQUEST_METHOD"] === "POST").
function prosesPendaftaran(array $kiriman): array
{
$nama = trim($kiriman["nama"] ?? "");
$email = trim($kiriman["email"] ?? "");
$errors = [];
if ($nama === "") {
$errors[] = "Nama wajib diisi";
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errors[] = "Email tidak valid";
}
return ["nama" => $nama, "email" => $email, "errors" => $errors];
}
function renderForm(array $errors, array $lama): string
{
$nama = htmlspecialchars($lama["nama"] ?? "", ENT_QUOTES);
$email = htmlspecialchars($lama["email"] ?? "", ENT_QUOTES);
$html = "<form method=\"post\">\n";
foreach ($errors as $error) {
$html .= " <p class=\"error\">" . htmlspecialchars($error, ENT_QUOTES) . "</p>\n";
}
$html .= " <label for=\"nama\">Nama</label>\n";
$html .= " <input type=\"text\" id=\"nama\" name=\"nama\" value=\"$nama\">\n";
$html .= " <label for=\"email\">Email</label>\n";
$html .= " <input type=\"email\" id=\"email\" name=\"email\" value=\"$email\">\n";
$html .= " <button type=\"submit\">Daftar</button>\n";
$html .= "</form>\n";
return $html;
}
// Simulasi dua request POST berurutan
$request = [
["nama" => "", "email" => "bukan-email"],
["nama" => "Budi Santoso", "email" => "[email protected]"],
];
foreach ($request as $kiriman) {
$hasil = prosesPendaftaran($kiriman);
if ($hasil["errors"] === []) {
echo "OK - pendaftaran sukses: {$hasil['nama']} <{$hasil['email']}>\n";
} else {
echo "GAGAL - form dirender ulang:\n" . renderForm($hasil["errors"], $hasil);
}
}