PDO (PHP Data Objects) adalah cara bawaan PHP untuk bicara dengan database: MySQL, PostgreSQL, SQLite, semuanya lewat method yang sama. Bayangin bon gudang di Warung Bu Tini. Bonnya sudah dicetak: "Ambil ____ dari rak menu". Pelayan cuma mengisi titik-titiknya, dan petugas gudang membaca isian itu sebagai nama barang, gak pernah sebagai perintah baru. Kalaupun pelayan menulis "semua barang" di titik-titik, petugas mencari barang yang namanya persis "semua barang". Itu prepared statement: query-nya dicetak dulu dengan tempat kosong, nilainya diisi belakangan.
🧪 Tebakan cepat: tabel
menupunya kolomid,nama,harga. Kamu menjalankanprint_r($stmt->fetch());untuk baris Mie Ayam tanpa mengatur apa-apa. Array-nya berisi 3 isi atau 6 isi? Tebak dulu, jawabannya ada di bagian "Fetch: ambil hasilnya".
Semua contoh di lesson ini memakai tabel menu milik Warung Bu Tini:
| id | nama | harga |
|---|---|---|
| 1 | Nasi Goreng | 15000 |
| 2 | Mie Ayam | 12000 |
| 3 | Es Teh | 5000 |
| 4 | Kerupuk | 2000 |
Koneksi: new PDO
<?php
$dsn = "mysql:host=localhost;dbname=warung;charset=utf8mb4";
try {
$pdo = new PDO($dsn, "warung_app", "rahasia", [
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
} catch (PDOException $e) {
error_log($e->getMessage());
exit("Layanan sedang gangguan. Coba lagi nanti.");
}
- Parameter pertama disebut DSN (Data Source Name): jenis database, alamat server, nama database, dan charset. Untuk SQLite cukup
"sqlite:warung.db", tanpa user dan password. - Parameter keempat adalah array pengaturan.
PDO::FETCH_ASSOCmembuat hasil query jadi array dengan nama kolom sebagai key (alasannya di bagian Fetch). - Kalau server database mati,
new PDOmelemparPDOExceptiondengan pesan sepertiSQLSTATE[HY000] [2002] Connection refused. Pesan itu dicatat ke log denganerror_log, bukan di-echoke pengunjung, karena bisa membocorkan alamat server dan nama database.
Prepared statement: prepare lalu execute
$stmt = $pdo->prepare("SELECT nama, harga FROM menu WHERE harga <= ?");
$stmt->execute([12000]);
$murah = $stmt->fetchAll();
// Mie Ayam 12000, Es Teh 5000, Kerupuk 2000
prepare()mengirim query dengan tanda?sebagai tempat kosong (placeholder), lalu mengembalikan objectPDOStatement, biasa disimpan di$stmt.execute([12000])mengisi tempat kosong itu, berurutan sesuai isi array, lalu menjalankan query-nya.- Placeholder juga bisa diberi nama. Lebih jelas kalau nilainya banyak:
$stmt = $pdo->prepare("SELECT nama, harga FROM menu WHERE harga <= :maks AND nama <> :kecuali");
$stmt->execute(["maks" => 12000, "kecuali" => "Kerupuk"]);
// Mie Ayam 12000, Es Teh 5000
Kenapa repot? Bandingkan dengan menempelkan input langsung ke query:
$nama = $_GET["nama"]; // pengunjung mengetik: ' OR '1'='1
$pdo->query("SELECT id, nama FROM menu WHERE nama = '$nama'");
// SQL jadi: ... WHERE nama = '' OR '1'='1' -> 4 baris, semua menu
$stmt = $pdo->prepare("SELECT id, nama FROM menu WHERE nama = ?");
$stmt->execute([$nama]);
// mencari menu yang namanya persis ' OR '1'='1 -> 0 baris
Input yang ditempel ke string bisa mengubah bentuk query-nya. Itu SQL injection. Dengan placeholder, input selalu diperlakukan sebagai nilai. Serangannya dibahas lengkap di lesson SQL Injection Prevention di track Security. Aturannya di sini sederhana: setiap nilai dari luar masuk lewat ? atau :nama, gak pernah lewat "...$variabel...".
Fetch: ambil hasilnya
$stmt = $pdo->prepare("SELECT id, nama, harga FROM menu WHERE id = ?");
$stmt->execute([2]);
print_r($stmt->fetch());
Array
(
[id] => 2
[0] => 2
[nama] => Mie Ayam
[1] => Mie Ayam
[harga] => 12000
[2] => 12000
)
Enam isi, bukan tiga. Itu jawaban tebakannya. Mode bawaan PDO adalah PDO::FETCH_BOTH: tiap kolom disimpan dua kali, dengan nama kolom dan dengan nomor urutnya. Karena itu PDO::FETCH_ASSOC biasanya dipasang di koneksi, atau ditulis langsung: $stmt->fetch(PDO::FETCH_ASSOC).
| Mode | fetch() mengembalikan |
Cara membaca nama |
|---|---|---|
PDO::FETCH_BOTH (bawaan) |
array, key nama dan nomor | $row["nama"] atau $row[1] |
PDO::FETCH_ASSOC |
array, key nama kolom | $row["nama"] |
PDO::FETCH_NUM |
array, key nomor | $row[1] |
PDO::FETCH_OBJ |
object stdClass |
$row->nama |
fetch()mengambil satu baris berikutnya. Kalau baris sudah habis, atau query-nya gak menemukan apa-apa, hasilnyafalse.fetchAll()mengambil semua baris sekaligus jadi array of array. Gak ada hasil berarti array kosong[].- Jadi fungsi pencari satu data biasanya ditulis
return $stmt->fetch() ?: null;: baris yang ketemu, ataunullkalau gak ada. Latihan lesson ini memakai pola yang sama dengan database SQLite di memori:findById($pdo, 2)mengembalikan data Ani, dan id yang gak ada mengembalikannull. - Setelah
INSERT,$pdo->lastInsertId()memberi id baris barunya, sebagai string.
Error: PDO::ERRMODE_EXCEPTION
Sejak PHP 8.0, mode error bawaan PDO adalah PDO::ERRMODE_EXCEPTION: query yang gagal melempar PDOException. Salah ketik nama tabel langsung kelihatan:
<?php
$pdo = new PDO("sqlite:warung.db");
$stmt = $pdo->prepare("SELECT nama, harga FROM menuu WHERE harga <= ?");
Fatal error: Uncaught PDOException: SQLSTATE[HY000]: General error: 1 no such table: menuu in /var/www/warung/menu.php:4
(Pesan di atas dari SQLite. MySQL memberi kode dan kalimat yang berbeda untuk kesalahan yang sama.)
SQLSTATE[...]adalah kode error standar SQL, diikuti pesan dari database-nya.- Mode lama
PDO::ERRMODE_SILENTgak melempar apa-apa:prepare()cuma mengembalikanfalse, dan kesalahannya harus dicek sendiri lewat$pdo->errorInfo(). Gampang terlewat, jadi biarkan mode exception. - Tangkap
PDOExceptiondi tempat yang memang bisa berbuat sesuatu, catat$e->getMessage()ke log, dan tunjukkan pesan umum ke pengunjung.
💡 Transaction: semua berhasil atau gak sama sekali
Misalkan tabel menu juga punya kolom stok. Pesanan 3 Es Teh berarti dua perubahan: satu baris baru di tabel pesanan, dan stok Es Teh berkurang 3. Keduanya harus jadi bersama, atau gak sama sekali.
try {
$pdo->beginTransaction();
$pdo->prepare("INSERT INTO pesanan (menu_id, jumlah) VALUES (?, ?)")->execute([3, 3]);
$pdo->prepare("UPDATE menu SET stok = stok - ? WHERE id = ?")->execute([3, 3]);
$pdo->commit();
} catch (PDOException $e) {
$pdo->rollBack();
echo "Gagal: " . $e->getMessage();
}
Kalau stok Es Teh tinggal 2 dan kolom stok punya aturan CHECK (stok >= 0), UPDATE-nya gagal dengan SQLSTATE[23000]: Integrity constraint violation: 19 CHECK constraint failed: stok >= 0. rollBack() membatalkan INSERT yang sudah jalan, jadi tabel pesanan tetap kosong dan stok tetap 2. Konsep transaksinya ada di lesson Transaksi di track SQL.
⚠️ Jebakan yang sering ketemu
- Menempelkan input ke query.
"... WHERE id = $id"dengan$iddari$_GETmembuka SQL injection. Pakaiprepare()danexecute(). - Lupa
fetch()bisafalse.$stmt->fetch()["nama"]untuk id yang gak ada memberi Warning. Cek dulu hasilnya, atau ubah jadinulldengan?: null. - Kaget dengan isi dobel. Itu
PDO::FETCH_BOTH. PasangPDO::FETCH_ASSOC. - Menampilkan
$e->getMessage()ke pengunjung. Pesannya bisa berisi nama tabel, query, bahkan alamat server. Catat ke log, tampilkan pesan umum.
TL;DR: PDO (PHP Data Objects) menghubungkan PHP ke database lewat new PDO($dsn, $user, $password, $opsi). Nilai dari luar selalu masuk lewat placeholder: $stmt = $pdo->prepare("... WHERE id = ?") lalu $stmt->execute([$id]), atau placeholder bernama :maks dengan ["maks" => 12000]. Menempelkan input ke string query membuka SQL injection. fetch() mengambil satu baris (atau false), fetchAll() semua baris; mode bawaan FETCH_BOTH menyimpan tiap kolom dua kali, jadi pasang PDO::FETCH_ASSOC. Sejak PHP 8.0 PDO::ERRMODE_EXCEPTION sudah bawaan: query yang gagal melempar PDOException, yang dicatat ke log, bukan ditampilkan. Perubahan yang harus jadi bersama dibungkus beginTransaction(), commit(), dan rollBack() kalau gagal.