Section baru ini membalik posisi kita: sepanjang course kita berbicara ke MySQL lewat klien mysql, mulai sekarang kita berada di sisi aplikasi yang berbicara dari dalam kode program. Pasangan pertama yang kita bahas adalah PHP — bahasa yang sejak era web 1990-an tumbuh berdampingan dengan MySQL hingga keduanya identik dengan tumpukan LAMP. Dalam chapter ini kita membedakan dua jalur resmi untuk mengakses MySQL dari PHP, menulis pola koneksi-query-fetch dengan mysqli, memakai prepared statement dari sisi aplikasi, lalu menata pola error agar pengguna tidak melihat pesan mentah server.

Dua Pintu ke MySQL: mysqli dan PDO #

PHP menyediakan dua antarmuka resmi ke MySQL, dan keduanya sudah terpasang secara default pada PHP modern. mysqli (MySQL Improved) adalah ekstensi yang dibuat khusus untuk MySQL: API-nya mengikuti fitur server secara persis, mulai dari prepared statement sampai multiple statement. PDO (PHP Data Objects) adalah lapisan abstraksi yang lebih luas — driver MySQL-nya hanya satu dari belasan driver (PostgreSQL, SQLite, dan lainnya), sehingga kode akses database bisa dipindahkan antar server dengan perubahan minimal.

Tabel berikut merangkum perbedaan yang paling terasa saat kita menulis kode sehari-hari:

AspekmysqliPDO
Cakupan driverkhusus MySQL dan MariaDBmulti-database (MySQL, PostgreSQL, SQLite, …)
Gaya APIprosedural (mysqli_connect) dan OOP (new mysqli)OOP murni (new PDO)
Prepared statementya, placeholder ? dengan bind_paramya, placeholder ? maupun bernama (:batas)
Jenis parameter dideklarasikan eksplisitya ('ssi' = string, string, integer)tidak, tipe disimpulkan otomatis
Mode fetchfetch_assoc(), fetch_row(), …banyak konstanta (FETCH_ASSOC, FETCH_OBJ, FETCH_CLASS, …)

Keduanya sama-sama cepat untuk kebutuhan web biasa, jadi pilihan lebih merupakan keputusan arsitektur: proyek yang terikat MySQL selamanya cocok dengan mysqli, proyek yang menginginkan kebebasan pindah database atau sudah memakai framework (Laravel, Symfony) akan bertemu PDO di lapisan bawahnya.

Menyiapkan Data Latihan #

Kode PHP kita akan membaca tabel produk dari database belajar_app. Kita bangun dulu datanya lewat klien — blok berikut juga berfungsi sebagai patokan: hasil SELECT di bawah adalah hasil yang sama yang akan diterima kode PHP nanti:

sql
DROP DATABASE IF EXISTS belajar_app;
CREATE DATABASE belajar_app;
USE belajar_app;

CREATE TABLE produk (
  id    INT PRIMARY KEY,
  nama  VARCHAR(100) NOT NULL,
  harga INT NOT NULL
);

INSERT INTO produk (id, nama, harga) VALUES
  (1, 'Kopi Toraja', 52000),
  (2, 'Teh Melati', 28000),
  (3, 'Gula Aren', 32000);

SELECT id, nama, harga FROM produk ORDER BY id;

Output yang Dihasilkan:

text
Query OK, 0 rows affected, 1 warning (0.00 sec)

Query OK, 1 row affected (0.00 sec)

Database changed
Query OK, 0 rows affected (0.00 sec)

Query OK, 3 rows affected (0.00 sec)
Records: 3  Duplicates: 0  Warnings: 0

+----+-------------+-------+
| id | nama        | harga |
+----+-------------+-------+
|  1 | Kopi Toraja | 52000 |
|  2 | Teh Melati  | 28000 |
|  3 | Gula Aren   | 32000 |
+----+-------------+-------+
3 rows in set (0.00 sec)

Tiga statement pembuka mengembalikan keadaan bersih (DROP ... IF EXISTS) supaya chapter ini mandiri. Kolom nama dan harga sengaja sederhana agar fokus kita tetap di alur aplikasi — koneksi, kirim query, terima baris — bukan di skema.

Koneksi dan Query dengan mysqli #

Berikut pola dasar mysqli yang akan kita pakai berulang kali di seluruh proyek PHP — koneksi, atur charset, kirim query, ambil baris demi baris sebagai array asosiatif:

php
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);

$mysqli = new mysqli('127.0.0.1', 'root', '', 'belajar_app');
$mysqli->set_charset('utf8mb4');

$hasil = $mysqli->query('SELECT id, nama, harga FROM produk ORDER BY id');

while ($baris = $hasil->fetch_assoc()) {
    echo $baris['id'] . ' | ' . $baris['nama'] . ' | ' . $baris['harga'] . PHP_EOL;
}

$hasil->free();
$mysqli->close();

Output yang Dihasilkan (kode ilustratif, hasil disarikan dari SELECT di atas):

text
1 | Kopi Toraja | 52000
2 | Teh Melati | 28000
3 | Gula Aren | 32000

Mari kita bedah perannya satu per satu:

  1. mysqli_report(...) mengatur mode error sebelum koneksi dibuka. Dengan MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT, setiap kegagalan — koneksi ditolak, SQL salah — dilempar sebagai mysqli_sql_exception, bukan sekadar warning senyap. Sejak PHP 8.1 kombinasi ini adalah perilaku default, tetapi menuliskannya eksplisit membuat kode aman juga di versi lebih lama.
  2. new mysqli(host, user, password, database) membuka koneksi dalam satu langkah. Urutan parameternya tetap sama meski kita memakai gaya prosedural: mysqli_connect('127.0.0.1', 'root', '', 'belajar_app').
  3. set_charset('utf8mb4') memastikan koneksi memakai charset yang sama dengan default server modern. Tanpa baris ini, koneksi lama bisa jatuh ke latin1 dan teks Indonesia beraksen akan rusak.
  4. query(...) mengirim satu statement dan mengembalikan objek hasil (mysqli_result) untuk SELECT, atau boolean untuk INSERT/UPDATE.
  5. fetch_assoc() mengambil satu baris sebagai array asosiatif ($baris['nama']); perulangan while berjalan sampai fetch_assoc() mengembalikan NULL — itulah sinyal baris habis.
  6. free() dan close() melepas memori hasil lalu menutup koneksi. PHP_EOL dipakai agar baris baru benar di sistem operasi mana pun.

Query yang dikirim baris ke-4 sama persis dengan statement terakhir blok SQL di atas — itulah sebabnya kita bisa meminjam output nyatanya.

Prepared Statement dengan mysqli #

Untuk query yang menerima input pengguna, mysqli menyediakan prepared statement: struktur SQL dikirim dengan placeholder ?, nilai dikirim terpisah saat eksekusi. Contoh berikut mencari produk di bawah harga tertentu:

php
<?php
$hargaMaks = 40000;

$stmt = $mysqli->prepare('SELECT id, nama, harga FROM produk WHERE harga < ? ORDER BY id');
$stmt->bind_param('i', $hargaMaks);
$stmt->execute();

$hasil = $stmt->get_result();
while ($baris = $hasil->fetch_assoc()) {
    echo $baris['nama'] . ' - ' . $baris['harga'] . PHP_EOL;
}

$stmt->close();

Output yang Dihasilkan (kode ilustratif, dari SELECT ... WHERE harga < 40000 pada data latihan):

text
Teh Melati - 28000
Gula Aren - 32000

Tiga baris intinya bekerja begini. prepare(...) mengirim teks SQL dengan tanda ? dan menerima kembali handle statement. bind_param('i', $hargaMaks) memasangkan variabel ke placeholder — string 'i' mendeklarasikan tipenya (i integer, d double, s string, b blob), satu karakter per placeholder sesuai urutan. execute() menjalankan statement, lalu get_result() mengembalikan objek hasil dengan API fetch_assoc() yang sudah kita kenal. Bila dua nilai dicari, tandanya menjadi 'ii' dengan dua variabel — misalnya rentang WHERE harga BETWEEN ? AND ?.

Padanan PDO-nya lebih ringkas karena tipe disimpulkan otomatis dan placeholder bisa bernama:

php
<?php
$pdo = new PDO('mysql:host=127.0.0.1;dbname=belajar_app;charset=utf8mb4', 'root', '', [
    PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);

$stmt = $pdo->prepare('SELECT id, nama, harga FROM produk WHERE harga < :batas ORDER BY id');
$stmt->execute(['batas' => 40000]);

foreach ($stmt as $baris) {
    echo $baris['nama'] . ' - ' . $baris['harga'] . PHP_EOL;
}

$pdo = null;

DSN PDO (mysql:host=...;dbname=...;charset=...) memuat parameter koneksi sebagai satu string — formatnya berbeda dari mysqli, tetapi informasinya sama. Opsi ERRMODE_EXCEPTION mengaktifkan pola exception seperti mysqli_report tadi, dan FETCH_ASSOC menjadikan foreach langsung menghasilkan array asosiatif.

Pola Error: Tangkap, Log, Sampaikan Halus #

Dengan mode exception, semua kegagalan database terpusat di satu tempat: blok try/catch. Pola produksi yang sehat punya tiga lapis — tangkap, catat lengkap untuk pengembang, sampaikan singkat untuk pengguna:

php
<?php
try {
    $mysqli = new mysqli('127.0.0.1', 'root', '', 'belajar_app');
    $mysqli->query('SELECT id, nama FROM tabel_tidak_ada');
} catch (mysqli_sql_exception $e) {
    error_log('Galat MySQL: [' . $e->getCode() . '] ' . $e->getMessage());
    echo 'Maaf, layanan sedang bermasalah. Coba beberapa saat lagi.' . PHP_EOL;
} finally {
    if (isset($mysqli) && $mysqli instanceof mysqli) {
        $mysqli->close();
    }
}

Blok finally memastikan koneksi tertutup bahkan saat exception terbang — kebocoran koneksi pada request yang gagal adalah salah satu penyebab klasik server kehabisan slot koneksi. Chapter Menangani Database Errors di akhir section ini memetakan kode error MySQL (1062, 1452, 2003, dan kawan-kawannya) satu per satu beserta tindakannya.

Kebiasaan Penutup #

Menutup hasil (free()) dan koneksi (close()) adalah kebiasaan yang murah di skrip pendek dan wajib di skrip panjang. Pada PHP-FPM yang sibuk, membuka koneksi baru tiap request tetap boleh, tetapi memasang prefiks p: pada host (new mysqli('p:127.0.0.1', ...)) mengaktifkan persistent connection — koneksi yang dipulihkan antar-request; pola pooling penuh kita bahas di chapter Database Connection Pooling.

Membersihkan Latihan #

sql
DROP DATABASE IF EXISTS belajar_app;

Referensi #

Bahasa berikutnya menawarkan kontras yang menarik: Go tidak punya ekstensi khusus MySQL, justru menyeragamkan semua database lewat satu antarmuka standar — itulah chapter MySQL dan Go.