🔌 Modul 2: Koneksi PDO

Memahami PDO dan cara menghubungkan PHP ke database MySQL

1. Apa itu PDO?

PDO (PHP Data Objects) adalah ekstensi PHP yang menyediakan antarmuka seragam untuk mengakses berbagai jenis database (MySQL, PostgreSQL, SQLite, dll). PDO adalah cara modern dan aman untuk berinteraksi dengan database di PHP.

đŸ›Ąī¸
Aman

Mencegah SQL Injection dengan Prepared Statements

🔄
Fleksibel

Satu cara coding untuk berbagai jenis database

🏆
Modern

Standar industri PHP saat ini, mysql_* sudah dihapus

2. PDO vs mysql_* (Cara Lama)
❌ Cara Lama (mysql_*) — JANGAN DIPAKAI
<?php
// Cara lama - mysql_* sudah DIHAPUS di PHP 7+
$conn = mysql_connect("localhost","root","");
mysql_select_db("pos_sederhana");

// BERBAHAYA: rentan SQL Injection!
$nama = $_GET['nama'];
$sql = "SELECT * FROM produk 
        WHERE nama_produk = '$nama'";
$result = mysql_query($sql);
?>

âš ī¸ Ini berbahaya karena hacker bisa inject SQL!

✅ Cara PDO (GUNAKAN INI)
<?php
// Cara PDO - aman & modern
$pdo = new PDO(
    "mysql:host=localhost;dbname=pos_sederhana",
    "root",
    ""
);

// AMAN: Prepared Statement
$nama = $_GET['nama'];
$stmt = $pdo->prepare(
    "SELECT * FROM produk 
     WHERE nama_produk = ?"
);
$stmt->execute([$nama]);
?>

✅ SQL Injection tidak bisa terjadi!

3. File Koneksi: config/database.php

Buat file config/database.php — file ini akan di-include di setiap halaman yang butuh database.

📄 config/database.php File Utama
<?php
// ============================================
// FILE: config/database.php
// Fungsi: Koneksi PDO ke MySQL
// ============================================

// Konfigurasi database
define('DB_HOST', 'localhost');    // Host database
define('DB_NAME', 'pos_sederhana'); // Nama database
define('DB_USER', 'root');          // Username MySQL
define('DB_PASS', '');              // Password MySQL (kosong di XAMPP)
define('DB_CHARSET', 'utf8mb4');    // Charset

// Data Source Name (DSN)
$dsn = "mysql:host=" . DB_HOST . 
       ";dbname=" . DB_NAME . 
       ";charset=" . DB_CHARSET;

// Opsi PDO yang direkomendasikan
$options = [
    // Lempar exception jika ada error (bukan diam-diam gagal)
    PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
    
    // Hasil query berupa array asosiatif (bisa pakai $row['nama'])
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    
    // Gunakan prepared statements bawaan MySQL (lebih aman)
    PDO::ATTR_EMULATE_PREPARES   => false,
];

// Buat koneksi PDO
try {
    $pdo = new PDO($dsn, DB_USER, DB_PASS, $options);
    // echo "Koneksi berhasil!"; // Aktifkan saat testing
    
} catch (PDOException $e) {
    // Tampilkan error yang ramah pengguna
    // Di production, jangan tampilkan detail error!
    die("<div class='alert alert-danger m-3'>
            <strong>Error Koneksi Database!</strong><br>
            " . htmlspecialchars($e->getMessage()) . "
         </div>");
}
?>
4. Penjelasan Setiap Baris Penting

DSN adalah string koneksi yang memberitahu PDO bagaimana cara terhubung ke database:

$dsn = "mysql:host=localhost;dbname=pos_sederhana;charset=utf8mb4";
//      ↑↑↑↑↑  ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑  ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
//      Driver   Host database       Nama database
  • mysql: → Jenis database (bisa juga pgsql:, sqlite:, dll)
  • host=localhost → Database ada di komputer sendiri
  • dbname=pos_sederhana → Nama database yang dipakai
  • charset=utf8mb4 → Mendukung karakter Indonesia & emoji

Mengatur bagaimana PDO menangani error:

PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
// Artinya: Jika ada error SQL, lempar Exception
// Kita bisa tangkap dengan try-catch

// Tanpa ini, error terjadi diam-diam!
// Kode kita tetap jalan tapi data tidak tersimpan

Menentukan format hasil query:

// Dengan FETCH_ASSOC (array asosiatif):
$row['nama_produk']  // ✅ Bisa akses pakai nama kolom
$row['harga_jual']

// Tanpa pengaturan (default FETCH_BOTH):
$row['nama_produk']  // Bisa
$row[1]              // Juga bisa (index angka) — membingungkan!

try {
    // Coba jalankan kode
    $pdo = new PDO($dsn, $user, $pass, $options);
    
} catch (PDOException $e) {
    // Jika gagal, tangkap errornya
    // $e->getMessage() = pesan error dari PHP
    die("Koneksi gagal: " . $e->getMessage());
}

// Analogi: seperti try-catch di kehidupan nyata
// "COBA buka pintu, KALAU kuncinya salah, tampilkan pesan error"
5. Test Koneksi

Buat file test sementara untuk memastikan koneksi berhasil:

📄 test_koneksi.php (hapus setelah testing!)
<?php
// File: test_koneksi.php
// HAPUS file ini setelah testing!

require_once 'config/database.php';

echo "<div style='font-family:Arial; margin:20px'>";

// Test 1: Koneksi berhasil
echo "<h3>🔌 Test Koneksi PDO</h3>";
echo "<p style='color:green'>✅ Koneksi berhasil!</p>";

// Test 2: Query sederhana
$stmt = $pdo->query("SELECT COUNT(*) as total FROM produk");
$row = $stmt->fetch();
echo "<p>đŸ“Ļ Jumlah produk: <strong>" . $row['total'] . "</strong></p>";

// Test 3: Tampilkan data
$stmt = $pdo->query("SELECT * FROM produk LIMIT 3");
echo "<table border='1' cellpadding='8'>";
echo "<tr><th>Kode</th><th>Nama</th><th>Harga</th></tr>";
while ($row = $stmt->fetch()) {
    echo "<tr>";
    echo "<td>" . $row['kode_produk'] . "</td>";
    echo "<td>" . $row['nama_produk'] . "</td>";
    echo "<td>Rp " . number_format($row['harga_jual'], 0, ',', '.') . "</td>";
    echo "</tr>";
}
echo "</table>";

echo "</div>";
?>
✅ Cara test: Buka browser ke http://localhost/pos_sederhana/test_koneksi.php
Jika muncul tabel produk → koneksi berhasil!
Setelah berhasil, hapus file ini karena berbahaya jika dibiarkan.
6. File Helper Functions

Buat includes/functions.php berisi fungsi-fungsi yang akan sering dipakai:

📄 includes/functions.php
<?php
// ============================================
// FILE: includes/functions.php
// Berisi fungsi-fungsi helper yang sering dipakai
// ============================================

/**
 * Format angka ke format Rupiah
 * Contoh: 15000 → "Rp 15.000"
 */
function formatRupiah($angka) {
    return 'Rp ' . number_format($angka, 0, ',', '.');
}

/**
 * Cek apakah user sudah login
 * Jika belum, redirect ke halaman login
 */
function cekLogin() {
    if (!isset($_SESSION['user_id'])) {
        header('Location: /pos_sederhana/auth/login.php');
        exit();
    }
}

/**
 * Tampilkan alert Bootstrap (success/danger/warning)
 */
function tampilkanAlert($pesan, $tipe = 'success') {
    $icon = match($tipe) {
        'success' => '✅',
        'danger'  => '❌',
        'warning' => 'âš ī¸',
        default   => 'â„šī¸'
    };
    echo "<div class='alert alert-{$tipe} alert-dismissible fade show' role='alert'>
            {$icon} {$pesan}
            <button type='button' class='btn-close' data-bs-dismiss='alert'></button>
          </div>";
}

/**
 * Generate nomor transaksi unik
 * Format: TRX-YYYYMMDD-XXXX (contoh: TRX-20250101-0001)
 */
function generateNoTransaksi($pdo) {
    $tanggal = date('Ymd');
    $prefix = "TRX-{$tanggal}-";
    
    $stmt = $pdo->prepare(
        "SELECT COUNT(*) FROM transaksi 
         WHERE no_transaksi LIKE ?"
    );
    $stmt->execute([$prefix . '%']);
    $count = $stmt->fetchColumn();
    
    return $prefix . str_pad($count + 1, 4, '0', STR_PAD_LEFT);
}

/**
 * Bersihkan input dari karakter berbahaya
 * SELALU gunakan ini sebelum menampilkan input user ke HTML!
 */
function bersihkan($input) {
    return htmlspecialchars(strip_tags(trim($input)));
}
?>
💡 Cara menggunakan fungsi-fungsi ini:
// Di file manapun yang butuh functions.php:
require_once '../includes/functions.php';

// Contoh penggunaan:
echo formatRupiah(15000);    // → Rp 15.000
cekLogin();                  // → Redirect jika belum login
echo bersihkan($_GET['q']);  // → Input aman dari XSS