Strategi Pengambilan Data

Mode fetch dan hidrasi baris ke objek.

Daftar isi

Baris hasil query dapat dikembalikan dalam beberapa bentuk, dan pemilihan bentuknya menentukan kenyamanan kode pembacanya. Topik ini membahas mode fetch (fetch mode) bawaan dan dua mode array lainnya, serta hidrasi baris ke objek dengan PDO::FETCH_OBJ dan PDO::FETCH_CLASS. Bagian terakhir membandingkan fetchAll() dengan perulangan fetch() baris demi baris. Setelah topik ini dipelajari, bentuk kembalian setiap query dapat dipilih secara sadar sesuai kebutuhan kodenya.

Mode fetch dan bentuk barisnya#

Mode fetch menentukan tipe data PHP yang mewakili satu baris hasil query. Mode bawaan PDO::FETCH_BOTH mengembalikan array yang memuat indeks angka dan kunci nama kolom sekaligus, sehingga setiap nilai tersimpan dua kali dalam satu baris. Mode PDO::FETCH_ASSOC memuat kunci nama kolom saja, sedangkan PDO::FETCH_NUM memuat indeks angka saja. Mode dapat ditetapkan untuk seluruh koneksi melalui opsi PDO::ATTR_DEFAULT_FETCH_MODE, atau untuk satu pernyataan melalui setFetchMode(). Untuk kejelasan kode, PDO::FETCH_ASSOC menjadi pilihan yang paling sering dipakai karena kuncinya menyebut nama kolomnya secara langsung.

Skrip berikut membaca baris yang sama dua kali, pertama dengan mode bawaan dan kedua setelah mode default koneksi diubah menjadi PDO::FETCH_ASSOC.

php mode-array.php
<?php
declare(strict_types=1);

$pdo = new PDO('sqlite::memory:', null, null, [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]);

$pdo->exec('CREATE TABLE buku (id INTEGER PRIMARY KEY, judul TEXT NOT NULL, tahun_terbit INTEGER NOT NULL)');
$pdo->exec("INSERT INTO buku (judul, tahun_terbit) VALUES
    ('Algoritma Dasar', 2021),
    ('Jaringan Komputer', 2019),
    ('Basis Data Relasional', 2022)");

$stmt = $pdo->query('SELECT id, judul, tahun_terbit FROM buku WHERE id = 1');
var_dump($stmt->fetch());

$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);

$stmt = $pdo->query('SELECT id, judul, tahun_terbit FROM buku WHERE id = 1');
var_dump($stmt->fetch());
text Keluaran
array(6) {
  ["id"]=>
  int(1)
  [0]=>
  int(1)
  ["judul"]=>
  string(15) "Algoritma Dasar"
  [1]=>
  string(15) "Algoritma Dasar"
  ["tahun_terbit"]=>
  int(2021)
  [2]=>
  int(2021)
}
array(3) {
  ["id"]=>
  int(1)
  ["judul"]=>
  string(15) "Algoritma Dasar"
  ["tahun_terbit"]=>
  int(2021)
}

Keluaran pertama memuat enam kunci untuk tiga kolom karena PDO::FETCH_BOTH menduplikasi setiap nilai dengan indeks angka dan kunci nama. Keluaran kedua hanya memuat tiga kunci setelah mode default diubah. Keluaran tersebut juga menunjukkan tipe kolom yang dikembalikan secara alami, yaitu int untuk kolom INTEGER dan string untuk kolom TEXT, sehingga nilai tahun tidak perlu dikonversi lagi di sisi PHP.

Hidrasi baris ke objek#

Baris juga dapat dihidrasi menjadi objek, yaitu proses mengisi nilai kolom ke properti objek yang baru dibuat. Mode PDO::FETCH_OBJ membuat objek stdClass yang propertinya dinamai menurut nama kolom. Mode PDO::FETCH_CLASS berjalan satu langkah lebih jauh karena PDO membuat objek dari kelas pilihan dan mengisi propertinya dari kolom yang bernama sama. Dengan demikian, metode kelas tersebut dapat dipanggil pada baris yang dihidrasi. Syaratnya, nama kolom dan nama properti harus sama persis, sedangkan kolom tanpa properti padanan diberikan sebagai properti dinamis yang sejak PHP 8.2 memicu peringatan.

Skrip berikut menghidrasi baris yang sama dengan kedua mode tersebut. Kelas Buku menyediakan metode ringkas() yang menyusun teks dari dua propertinya.

php mode-objek.php
<?php
declare(strict_types=1);

class Buku
{
    public int $id = 0;
    public string $judul = '';
    public int $tahun_terbit = 0;

    public function ringkas(): string
    {
        return "{$this->judul} ({$this->tahun_terbit})";
    }
}

$pdo = new PDO('sqlite::memory:', null, null, [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]);

$pdo->exec('CREATE TABLE buku (id INTEGER PRIMARY KEY, judul TEXT NOT NULL, tahun_terbit INTEGER NOT NULL)');
$pdo->exec("INSERT INTO buku (judul, tahun_terbit) VALUES
    ('Algoritma Dasar', 2021),
    ('Jaringan Komputer', 2019)");

$stmt = $pdo->query('SELECT id, judul, tahun_terbit FROM buku WHERE id = 1');
$baris = $stmt->fetch(PDO::FETCH_OBJ);
echo get_class($baris), ': ', $baris->judul, "\n";

$stmt = $pdo->query('SELECT id, judul, tahun_terbit FROM buku ORDER BY id');
$semua = $stmt->fetchAll(PDO::FETCH_CLASS, Buku::class);
foreach ($semua as $buku) {
    echo $buku->ringkas(), "\n";
}

echo get_class($semua[0]), "\n";
text Keluaran
stdClass: Algoritma Dasar
Algoritma Dasar (2021)
Jaringan Komputer (2019)
Buku

Baris pertama keluaran menegaskan bahwa PDO::FETCH_OBJ menghasilkan objek stdClass, sedangkan tiga baris berikutnya menunjukkan objek Buku yang propertinya berisi nilai kolom sehingga metodenya dapat dipanggil. Pemanggilan fetchAll() dengan argumen kelas menghidrasi seluruh baris sekaligus ke dalam array objek Buku, dan pemeriksaan get_class() pada elemen pertamanya menegaskan kelasnya.

fetchAll dan perulangan baris demi baris#

Metode fetchAll() memuat seluruh baris hasil ke dalam satu array, sehingga jumlah barisnya langsung terbaca dengan count() dan isinya dapat diakses bebas berulang kali. Sebaliknya, fetch() mengembalikan satu baris saja pada setiap pemanggilan dan false setelah baris habis, sehingga pola perulangan while memproses satu baris pada satu waktu. Kedua pola membaca pernyataan yang sama, tetapi kebutuhan memorinya berbeda, karena fetchAll() menahan seluruh hasil di memori sedangkan perulangan hanya menahan baris yang sedang diproses.

Skrip berikut menampilkan tabel yang sama dengan kedua pola tersebut.

php semua-baris.php
<?php
declare(strict_types=1);

$pdo = new PDO('sqlite::memory:', null, null, [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);

$pdo->exec('CREATE TABLE buku (id INTEGER PRIMARY KEY, judul TEXT NOT NULL, penulis TEXT NOT NULL)');
$pdo->exec("INSERT INTO buku (judul, penulis) VALUES
    ('Algoritma Dasar', 'Citra'),
    ('Jaringan Komputer', 'Budi'),
    ('Basis Data Relasional', 'Dewi')");

$semua = $pdo->query('SELECT judul, penulis FROM buku')->fetchAll();
echo 'fetchAll: ', count($semua), ' baris', "\n";

$stmt = $pdo->query('SELECT judul, penulis FROM buku');
while ($baris = $stmt->fetch()) {
    echo $baris['judul'], ' oleh ', $baris['penulis'], "\n";
}
text Keluaran
fetchAll: 3 baris
Algoritma Dasar oleh Citra
Jaringan Komputer oleh Budi
Basis Data Relasional oleh Dewi

Baris pertama keluaran berasal dari fetchAll() yang jumlah barisnya dihitung langsung dari arraynya, sedangkan tiga baris berikutnya dicetak dari perulangan yang berhenti ketika fetch() mengembalikan false. Untuk pernyataan SELECT, hasil rowCount() tidak dijamin konsisten antar driver, sehingga jumlah baris dibaca dari array hasil fetchAll(). Sebaliknya, rowCount() dapat diandalkan untuk INSERT, UPDATE, dan DELETE karena mengembalikan jumlah baris yang terpengaruh.

Kesalahan umum#

  • Nama kolom dan nama properti tidak sama pada PDO::FETCH_CLASS. Kolom tahun_terbit tidak akan mengisi properti $tahunTerbit, sehingga nilainya muncul sebagai properti dinamis, sedangkan properti bertipe $tahunTerbit tetap memegang nilai awalnya. Samakan namanya, atau ganti nama kolom pada query dengan SELECT tahun_terbit AS tahunTerbit.
  • Memanggil fetch() setelah fetchAll(). Kursor pernyataan, yaitu penanda posisi pembacaan baris, sudah berada di ujung hasil setelah seluruh baris dimuat, sehingga panggilan berikutnya mengembalikan false tanpa pesan apa pun. Ambil data satu kali, lalu baca kembali dari variabel yang menampungnya.
  • Membiarkan mode BOTH untuk hasil yang besar. Setiap baris tersimpan dua kali di memori tanpa keuntungan apa pun bila indeks angkanya tidak dipakai. Tetapkan PDO::FETCH_ASSOC sebagai mode default koneksi sejak awal.
  • Menganggap satu panggilan fetch() memuat seluruh hasil. Metode fetch() mengembalikan satu baris saja, sehingga kode yang mengharapkan array berisi semua baris kehilangan data tanpa pesan error. Gunakan fetchAll() bila seluruh baris memang dibutuhkan sekaligus, atau bungkus fetch() dalam perulangan yang berhenti pada false.

Ringkasan#

  • Mode fetch menentukan bentuk satu baris hasil, yaitu array campuran pada PDO::FETCH_BOTH, array asosiatif pada PDO::FETCH_ASSOC, atau array indeks angka pada PDO::FETCH_NUM.
  • PDO::FETCH_OBJ menghidrasi baris ke stdClass, sedangkan PDO::FETCH_CLASS menghidrasinya ke kelas pilihan dengan syarat nama kolom dan properti sama.
  • fetchAll() memuat seluruh baris sekaligus dan mudah dihitung, sedangkan perulangan fetch() memproses satu baris pada satu waktu dan lebih hemat memori.
  • rowCount() diandalkan untuk INSERT, UPDATE, dan DELETE, bukan untuk menghitung baris hasil SELECT.

Referensi#