# Tutorial Hari 3: PHP Config & Helpers

## 🎯 Tujuan
- Memahami konsep koneksi database dengan PDO
- Membuat file konfigurasi database
- Membuat fungsi helper yang sering dipakai
- Memahami security dasar (XSS, CSRF)

---

## 📚 Teori: Koneksi Database dengan PDO

### Apa itu PDO?

**PDO** (PHP Data Objects) adalah cara modern untuk mengakses database di PHP. Lebih aman dan fleksibel dibanding cara lama (`mysql_*`).

| Metode | Keamanan | Status |
|--------|----------|--------|
| `mysql_*` | ❌ Tidak aman | Deprecated (jangan pakai!) |
| `mysqli_*` | ⚠️ Cukup aman | Masih dipakai |
| `PDO` | ✅ Paling aman | **Recommended** |

### Keuntungan PDO

| Fitur | Penjelasan |
|-------|------------|
| **Prepared Statements** | Mencegah SQL Injection |
| **Multi-database** | Bisa MySQL, PostgreSQL, SQLite, dll |
| **Exception Handling** | Error handling yang lebih baik |
| **Object Oriented** | Lebih mudah di-maintain |

### Contoh Perbedaan

```php
// ❌ CARA LAMA (TIDAK AMAN!)
$query = "SELECT * FROM users WHERE username = '$username'";
// Hacker bisa inject: ' OR 1=1 --

// ✅ CARA PDO (AMAN)
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = ?");
$stmt->execute([$username]);
// Input di-sanitize otomatis
```

---

## 1. Buat File database.php

### Langkah 1: Buat file baru

1. Di VS Code, klik kanan folder **config**
2. New File → `database.php`

### Langkah 2: Tulis Kode

```php
<?php
/**
 * ==========================================
 * Database Configuration
 * ==========================================
 * File ini berisi pengaturan koneksi database
 * dan fungsi untuk mendapatkan koneksi PDO.
 * ==========================================
 */

// ==========================================
// KONSTANTA KONFIGURASI
// ==========================================
define('DB_HOST', 'localhost');      // Host database
define('DB_NAME', 'pemetaan_desa');  // Nama database
define('DB_USER', 'root');           // Username (default Laragon)
define('DB_PASS', '');               // Password (default kosong)

/**
 * Fungsi untuk mendapatkan koneksi database
 * 
 * Menggunakan pattern SINGLETON - koneksi dibuat sekali
 * dan dipakai berulang (hemat resource)
 * 
 * @return PDO Object koneksi database
 */
function db() {
    // Static variable menyimpan nilai antar pemanggilan
    static $pdo = null;
    
    // Jika koneksi belum ada, buat baru
    if ($pdo === null) {
        try {
            // DSN = Data Source Name (format koneksi)
            $dsn = "mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=utf8mb4";
            
            // Opsi PDO untuk keamanan dan kemudahan
            $options = [
                // Tampilkan error sebagai exception
                PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                
                // Hasil query sebagai associative array
                PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
                
                // Gunakan prepared statement asli (bukan emulasi)
                PDO::ATTR_EMULATE_PREPARES => false
            ];
            
            // Buat koneksi baru
            $pdo = new PDO($dsn, DB_USER, DB_PASS, $options);
            
        } catch (PDOException $e) {
            // Jika gagal, tampilkan error dan hentikan
            die("❌ Koneksi database gagal: " . $e->getMessage());
        }
    }
    
    return $pdo;
}
```

### Penjelasan Kode

| Bagian | Penjelasan |
|--------|------------|
| `define()` | Membuat konstanta (nilai tetap) |
| `static $pdo` | Variable yang nilainya tersimpan antar pemanggilan fungsi |
| `PDO::ATTR_ERRMODE` | Cara menampilkan error |
| `PDO::ERRMODE_EXCEPTION` | Error ditampilkan sebagai exception |
| `PDO::FETCH_ASSOC` | Hasil query berbentuk array asosiatif |
| `PDO::ATTR_EMULATE_PREPARES` | false = prepared statement asli (lebih aman) |

> 💡 **Apa itu Singleton Pattern?**
> Singleton memastikan hanya ada 1 instance dari suatu object. Dalam kasus ini, koneksi database hanya dibuat sekali dan dipakai berulang, sehingga tidak membebani server.

---

## 📚 Teori: Security Web Dasar

### 3 Ancaman Umum

| Serangan | Penjelasan | Pencegahan |
|----------|------------|------------|
| **SQL Injection** | Hacker menyisipkan SQL berbahaya | Prepared statements |
| **XSS** | Hacker menyisipkan JavaScript | `htmlspecialchars()` |
| **CSRF** | Hacker membuat form palsu | Token unik per form |

### XSS (Cross-Site Scripting)

```html
<!-- User input: <script>alert('hacked')</script> -->

<!-- ❌ TIDAK AMAN -->
<p>Nama: <?= $nama ?></p>
<!-- Output: <p>Nama: <script>alert('hacked')</script></p> -->

<!-- ✅ AMAN dengan htmlspecialchars -->
<p>Nama: <?= htmlspecialchars($nama) ?></p>
<!-- Output: <p>Nama: &lt;script&gt;alert('hacked')&lt;/script&gt;</p> -->
```

### CSRF (Cross-Site Request Forgery)

```
Website Asli: bank.com
Website Jahat: evil.com

1. User login di bank.com
2. User buka evil.com (tab baru)
3. evil.com diam-diam kirim request ke bank.com/transfer?to=hacker&amount=1000000
4. Karena user masih login, transfer berhasil!

Solusi: CSRF Token
- Setiap form punya token unik
- Server cek token sebelum proses
- evil.com tidak tahu token-nya
```

---

## 2. Buat File helpers.php

### Langkah 1: Buat file baru

1. Klik kanan folder **lib**
2. New File → `helpers.php`

### Langkah 2: Tulis Kode

```php
<?php
/**
 * ==========================================
 * Helper Functions
 * ==========================================
 * Kumpulan fungsi bantuan yang sering dipakai
 * di seluruh aplikasi.
 * ==========================================
 */

/**
 * Sanitize - Membersihkan input untuk mencegah XSS
 * 
 * @param string $string - Teks yang mau dibersihkan
 * @return string - Teks yang sudah aman
 */
function sanitize($string) {
    return htmlspecialchars($string ?? '', ENT_QUOTES, 'UTF-8');
}

/**
 * Generate CSRF Token
 * Token untuk mencegah serangan CSRF
 * 
 * @return string - Token 64 karakter
 */
function generateCSRFToken() {
    if (session_status() === PHP_SESSION_NONE) {
        session_start();
    }
    
    if (empty($_SESSION['csrf_token'])) {
        // random_bytes menghasilkan bytes acak yang aman
        // bin2hex mengubah binary ke hexadecimal
        $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
    }
    
    return $_SESSION['csrf_token'];
}

/**
 * Validate CSRF Token
 * Mengecek apakah token yang dikirim valid
 * 
 * @param string $token - Token dari form
 * @return bool - true jika valid
 */
function validateCSRFToken($token) {
    if (session_status() === PHP_SESSION_NONE) {
        session_start();
    }
    
    // hash_equals membandingkan string dengan aman
    return isset($_SESSION['csrf_token']) && 
           hash_equals($_SESSION['csrf_token'], $token);
}

/**
 * Set Flash Message
 * Pesan yang tampil sekali lalu hilang
 * 
 * @param string $type - 'success', 'danger', 'warning'
 * @param string $message - Isi pesan
 */
function setFlash($type, $message) {
    if (session_status() === PHP_SESSION_NONE) {
        session_start();
    }
    $_SESSION['flash'] = ['type' => $type, 'message' => $message];
}

/**
 * Get Flash Message
 * Mengambil pesan (sekali ambil langsung dihapus)
 * 
 * @return array|null - Pesan atau null
 */
function getFlash() {
    if (session_status() === PHP_SESSION_NONE) {
        session_start();
    }
    $flash = $_SESSION['flash'] ?? null;
    unset($_SESSION['flash']);
    return $flash;
}

/**
 * Display Flash Message
 * Menampilkan pesan sebagai HTML alert
 */
function displayFlash() {
    $flash = getFlash();
    if ($flash) {
        $class = $flash['type'] === 'success' ? 'alert-success' : 'alert-danger';
        echo '<div class="alert ' . $class . '">' . sanitize($flash['message']) . '</div>';
    }
}

/**
 * Format Date
 * Mengubah format tanggal menjadi lebih mudah dibaca
 * 
 * @param string $date - Tanggal dari database
 * @return string - Tanggal format Indonesia
 */
function formatDate($date) {
    if (!$date) return '-';
    return date('d M Y H:i', strtotime($date));
}

/**
 * JSON Response
 * Mengirim response JSON (untuk AJAX)
 * 
 * @param array $data - Data yang dikirim
 * @param int $statusCode - HTTP status code
 */
function jsonResponse($data, $statusCode = 200) {
    http_response_code($statusCode);
    header('Content-Type: application/json');
    echo json_encode($data);
    exit;
}

/**
 * Redirect
 * Mengalihkan user ke halaman lain
 * 
 * @param string $url - URL tujuan
 */
function redirect($url) {
    header("Location: $url");
    exit;
}
```

### Daftar Fungsi Helper

| Fungsi | Kegunaan | Contoh Penggunaan |
|--------|----------|-------------------|
| `sanitize()` | Mencegah XSS | `echo sanitize($nama)` |
| `generateCSRFToken()` | Buat token CSRF | `<input value="<?= generateCSRFToken() ?>">` |
| `validateCSRFToken()` | Validasi token | `if (validateCSRFToken($_POST['token']))` |
| `setFlash()` | Simpan pesan | `setFlash('success', 'Berhasil!')` |
| `getFlash()` | Ambil pesan | `$flash = getFlash()` |
| `displayFlash()` | Tampilkan pesan | `<?php displayFlash(); ?>` |
| `formatDate()` | Format tanggal | `formatDate('2024-01-01')` → `01 Jan 2024` |
| `jsonResponse()` | Kirim JSON | `jsonResponse(['status' => 'ok'])` |
| `redirect()` | Pindah halaman | `redirect('/dashboard.php')` |

---

## 3. Test Koneksi Database

### Langkah 1: Edit test.php

Buka file `test.php` di folder utama, ganti isinya:

```php
<?php
/**
 * Test Koneksi Database
 */

// Include file konfigurasi
require_once 'config/database.php';

echo "<h2>🧪 Test Koneksi Database</h2>";

try {
    // Coba koneksi
    $pdo = db();
    echo "<p style='color: green;'>✅ Koneksi database BERHASIL!</p>";
    
    // Coba query
    $stmt = $pdo->query("SELECT * FROM users");
    $users = $stmt->fetchAll();
    
    echo "<p>✅ Jumlah user: <strong>" . count($users) . "</strong></p>";
    
    // Tampilkan user
    echo "<h3>Daftar User:</h3>";
    echo "<table border='1' cellpadding='10'>";
    echo "<tr><th>ID</th><th>Username</th><th>Email</th><th>Created</th></tr>";
    
    foreach ($users as $user) {
        echo "<tr>";
        echo "<td>" . $user['id'] . "</td>";
        echo "<td>" . $user['username'] . "</td>";
        echo "<td>" . $user['email'] . "</td>";
        echo "<td>" . $user['created_at'] . "</td>";
        echo "</tr>";
    }
    echo "</table>";
    
    echo "<p style='color: green;'>✅ Semua berjalan dengan baik!</p>";
    
} catch (Exception $e) {
    echo "<p style='color: red;'>❌ Error: " . $e->getMessage() . "</p>";
}
```

### Langkah 2: Jalankan Test

1. Buka browser: `http://localhost/pemetaan-desa/test.php`
2. Harusnya muncul:

```
✅ Koneksi database BERHASIL!
✅ Jumlah user: 1

Daftar User:
┌────┬──────────┬──────────────────┬─────────────────────┐
│ ID │ Username │ Email            │ Created             │
├────┼──────────┼──────────────────┼─────────────────────┤
│ 1  │ demo     │ demo@example.com │ 2024-01-01 00:00:00 │
└────┴──────────┴──────────────────┴─────────────────────┘

✅ Semua berjalan dengan baik!
```

---

## 📁 Struktur Akhir Hari 3

```
📁 pemetaan-desa/
├── 📁 config/
│   └── 📄 database.php   ← BARU: Konfigurasi database
├── 📁 database/
│   └── 📄 schema.sql
├── 📁 lib/
│   └── 📄 helpers.php    ← BARU: Fungsi helper
└── 📄 test.php           ← UPDATED: Test koneksi
```

---

**← [Hari 2: Database](hari2.md) | [Hari 4: Autentikasi](hari4.md) →**
