# Modul Pembelajaran: PHP Data Objects (PDO)

## Bagian 1: Pengenalan PDO

### 1.1. Apa itu PDO?
**PDO (PHP Data Objects)** adalah sebuah extension ringan di PHP yang menyediakan antarmuka (interface) yang konsisten untuk mengakses berbagai jenis database. Berbeda dengan extension `mysqli` yang hanya khusus untuk MySQL, PDO mendukung lebih dari 12 driver database yang berbeda, termasuk MySQL, PostgreSQL, SQLite, Oracle, dan SQL Server.

### 1.2. Mengapa Menggunakan PDO?
1.  **Portabilitas Database**: Jika suatu saat Anda perlu mengganti database (misal dari MySQL ke PostgreSQL), Anda hanya perlu mengubah string koneksi (DSN) dan sedikit penyesuaian query, tanpa perlu menulis ulang seluruh kode koneksi.
2.  **Keamanan (Prepared Statements)**: PDO memiliki dukungan native untuk Prepared Statements yang sangat efektif mencegah serangan SQL Injection.
3.  **Object-Oriented**: PDO sepenuhnya menggunakan paradigma Pemrograman Berorientasi Objek (OOP).
4.  **Error Handling**: PDO mendukung mekanisme Exception Handling yang modern (try-catch), sehingga penanganan error menjadi lebih rapi.

---

## Bagian 2: Koneksi Database

### 2.1. Data Source Name (DSN)
Untuk terhubung ke database, PDO membutuhkan DSN. DSN adalah string yang berisi informasi tentang driver database, host, nama database, dan charset.

Format DSN untuk MySQL:
`mysql:host=hostname;dbname=databasename;charset=utf8mb4`

### 2.2. Membuat Koneksi
Koneksi dibuat dengan membuat instance baru dari class `PDO`.

```php
<?php
$host = '127.0.0.1';
$db   = 'test_db';
$user = 'root';
$pass = '';
$charset = 'utf8mb4';

$dsn = "mysql:host=$host;dbname=$db;charset=$charset";
$options = [
    PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION, // Wajib: Agar error melempar Exception
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,       // Opsional: Default fetch array asosiatif
    PDO::ATTR_EMULATE_PREPARES   => false,                  // Opsional: Gunakan native prepared statements
];

try {
    $pdo = new PDO($dsn, $user, $pass, $options);
    echo "Berhasil terkoneksi ke database!";
} catch (\PDOException $e) {
    throw new \PDOException($e->getMessage(), (int)$e->getCode());
}
```

---

## Bagian 3: Prepared Statements & Binding

### 3.1. Masalah SQL Injection
SQL Injection terjadi ketika input dari user digabungkan langsung ke dalam string query SQL.
```php
// CONTOH TIDAK AMAN (JANGAN DITIRU)
$sql = "SELECT * FROM users WHERE email = '" . $_POST['email'] . "'";
```
Jika user memasukkan `' OR '1'='1`, query akan menjadi `SELECT * FROM users WHERE email = '' OR '1'='1'`, yang akan mengembalikan semua user (termasuk admin).

### 3.2. Solusi: Prepared Statements
Prepared Statements memisahkan query SQL dari datanya.
1.  **Prepare**: Database menerima template query dengan placeholder (`:name` atau `?`).
2.  **Bind**: PHP mengirimkan nilai untuk mengisi placeholder tersebut.
3.  **Execute**: Database menjalankan query.

```php
// Menggunakan Named Parameters (:email)
$stmt = $pdo->prepare("SELECT * FROM users WHERE email = :email");
$stmt->execute(['email' => $userInput]);
$user = $stmt->fetch();
```

Dalam contoh di atas, apapun isi `$userInput`, database akan menganggapnya sebagai string literal, bukan kode SQL yang bisa dieksekusi.

---

## Bagian 4: Fetching Data

### 4.1. Metode Fetch
-   `fetch()`: Mengambil satu baris data berikutnya dari hasil query. Efisien memori.
-   `fetchAll()`: Mengambil seluruh baris data sekaligus ke dalam array. Hati-hati jika datanya ribuan/jutaan baris.

### 4.2. Fetch Modes
-   `PDO::FETCH_ASSOC`: Mengembalikan array asosiatif (key = nama kolom).
    ```php
    $row = $stmt->fetch(PDO::FETCH_ASSOC);
    echo $row['name'];
    ```
-   `PDO::FETCH_OBJ`: Mengembalikan object anonymous.
    ```php
    $row = $stmt->fetch(PDO::FETCH_OBJ);
    echo $row->name;
    ```
-   `PDO::FETCH_CLASS`: Memetakan hasil query langsung ke properti Class.
    ```php
    $stmt->setFetchMode(PDO::FETCH_CLASS, 'User');
    $user = $stmt->fetch(); // $user adalah object dari class User
    ```

---

## Bagian 5: CRUD Operations

### 5.1. Create (Insert)
```php
$sql = "INSERT INTO users (name, email) VALUES (:name, :email)";
$stmt = $pdo->prepare($sql);
$stmt->execute(['name' => 'Budi', 'email' => 'budi@example.com']);

// Mendapatkan ID terakhir yang di-insert
$newId = $pdo->lastInsertId();
```

### 5.2. Read (Select)
```php
$stmt = $pdo->query("SELECT * FROM users");
while ($row = $stmt->fetch()) {
    echo $row['name'] . "<br>";
}
```

### 5.3. Update
```php
$sql = "UPDATE users SET name = :name WHERE id = :id";
$stmt = $pdo->prepare($sql);
$stmt->execute(['name' => 'Budi Santoso', 'id' => 1]);

// Mengecek jumlah baris yang berubah
echo $stmt->rowCount() . " baris diupdate.";
```

### 5.4. Delete
```php
$stmt = $pdo->prepare("DELETE FROM users WHERE id = :id");
$stmt->execute(['id' => 1]);
```

---

## Bagian 6: Transactions

Transaction digunakan untuk memastikan sekumpulan operasi database berjalan secara atomik (semua sukses atau semua gagal).

```php
try {
    $pdo->beginTransaction();

    $pdo->exec("INSERT INTO orders ...");
    $pdo->exec("UPDATE inventory ...");

    $pdo->commit(); // Simpan perubahan
} catch (Exception $e) {
    $pdo->rollBack(); // Batalkan semua jika ada error
    echo "Transaksi Gagal: " . $e->getMessage();
}
```
