# Modul Pembelajaran: PHP Exception Handling

## Bagian 1: Konsep Dasar

### 1.1. Error vs Exception
Dalam PHP, masalah yang terjadi saat runtime dibagi menjadi dua kategori besar:

1.  **Error**: Masalah serius yang biasanya berasal dari sistem atau lingkungan eksekusi (misal: memori habis, syntax salah). Secara historis, Error sulit ditangani dan seringkali bersifat fatal (menghentikan script).
2.  **Exception**: Kondisi abnormal yang terjadi saat eksekusi kode logika aplikasi (misal: file tidak ditemukan, koneksi database gagal). Exception didesain untuk **ditangkap (caught)** dan **ditangani (handled)** sehingga aplikasi tidak perlu crash.

Sejak PHP 7, keduanya mengimplementasikan interface yang sama yaitu `Throwable`. Ini memungkinkan kita menangkap Error fatal tertentu (seperti `TypeError` atau `ParseError`) menggunakan blok try-catch, meskipun praktik terbaiknya adalah memperbaiki kode penyebab Error tersebut.

### 1.2. Hierarki Throwable
```
Throwable (Interface)
├── Error
│   ├── ArithmeticError
│   ├── ParseError
│   ├── TypeError
│   └── ...
└── Exception
    ├── LogicException
    ├── RuntimeException
    └── ...
```

---

## Bagian 2: Try, Catch, Finally

### 2.1. Blok Try-Catch
Mekanisme utama penanganan exception adalah blok `try-catch`.
-   **Try**: Berisi kode yang *berpotensi* menyebabkan error.
-   **Catch**: Berisi kode untuk menangani error jika terjadi di blok try.

```php
try {
    // Kode berisiko
    $data = connectToDatabase();
} catch (Exception $e) {
    // Penanganan error
    echo "Gagal koneksi: " . $e->getMessage();
}
```

### 2.2. Multiple Catch
Kita bisa menangkap berbagai jenis Exception secara spesifik. Urutannya harus dari yang **paling spesifik** ke yang **paling umum**.

```php
try {
    processFile('data.txt');
} catch (FileNotFoundException $e) {
    echo "File tidak ada.";
} catch (PermissionDeniedException $e) {
    echo "Tidak punya izin akses.";
} catch (Exception $e) {
    echo "Terjadi kesalahan lain: " . $e->getMessage();
}
```

### 2.3. Blok Finally
Blok `finally` bersifat opsional. Kode di dalamnya akan **SELALU dieksekusi**, tidak peduli apakah terjadi exception atau tidak. Ini sangat berguna untuk "Cleanup" resource, seperti menutup koneksi database atau menutup file handle.

```php
$fh = fopen("log.txt", "a");
try {
    fwrite($fh, "Log entry");
    if (someError()) {
        throw new Exception("Error writing");
    }
} catch (Exception $e) {
    echo "Gagal menulis log.";
} finally {
    fclose($fh); // Pasti dijalankan, mencegah memory leak
}
```

---

## Bagian 3: Throwing Exceptions

### 3.1. Keyword `throw`
Kita bisa memicu (melempar) Exception secara manual menggunakan keyword `throw`. Ini biasanya dilakukan saat validasi atau saat kondisi logika tidak terpenuhi.

```php
function bagi($angka, $pembagi) {
    if ($pembagi == 0) {
        throw new Exception("Tidak bisa membagi dengan nol.");
    }
    return $angka / $pembagi;
}
```

### 3.2. Built-in Exceptions (SPL)
PHP menyediakan Standard PHP Library (SPL) Exceptions yang siap pakai. Gunakanlah yang sesuai dengan konteks errornya:
-   `InvalidArgumentException`: Argumen fungsi tidak sesuai tipe atau nilai yang diharapkan.
-   `OutOfBoundsException`: Mengakses index array yang tidak valid.
-   `LogicException`: Kesalahan pada logika program (bug).
-   `RuntimeException`: Kesalahan yang hanya bisa dideteksi saat runtime (misal: koneksi putus).

---

## Bagian 4: Custom Exceptions

### 4.1. Membuat Custom Exception
Untuk aplikasi yang kompleks, disarankan membuat class Exception sendiri (Custom Exception) yang mewarisi class `Exception`. Ini memberikan makna semantik yang lebih jelas pada error yang terjadi.

```php
class UserNotFoundException extends Exception {}
class InsufficientBalanceException extends Exception {}
```

### 4.2. Penggunaan Custom Exception
```php
function withdraw($amount, $balance) {
    if ($amount > $balance) {
        throw new InsufficientBalanceException("Saldo tidak cukup.");
    }
    return $balance - $amount;
}

// Di bagian pemanggil:
try {
    withdraw(100000, 5000);
} catch (InsufficientBalanceException $e) {
    // Kita tahu persis ini masalah saldo, bisa tampilkan UI topup
    showTopUpModal();
} catch (Exception $e) {
    // Error umum lainnya
    showGenericError();
}
```

---

## Bagian 5: Best Practices

1.  **Jangan gunakan Exception untuk Flow Control**: Exception itu "mahal" secara performa. Jangan gunakan untuk logika if-else biasa (misal: mengecek apakah user login). Gunakan hanya untuk kondisi "Exceptional" (luar biasa/tidak normal).
2.  **Exception Wrapping**: Saat menangkap exception dari library pihak ketiga (misal Guzzle atau PDO), bungkuslah dengan Exception aplikasi Anda sendiri. Ini mencegah ketergantungan kode aplikasi pada detail implementasi library.
    ```php
    try {
        $http->get('...');
    } catch (ConnectException $e) {
        throw new ApiServiceUnavailableException("API Down", 0, $e);
    }
    ```
3.  **Jangan telan Exception (Empty Catch)**: Jangan pernah membiarkan blok catch kosong. Minimal log errornya.
    ```php
    // BAD
    catch (Exception $e) { } 
    
    // GOOD
    catch (Exception $e) { logger()->error($e); }
    ```
4.  **Security**: Jangan pernah menampilkan **Stack Trace** (detail error baris per baris) ke pengguna akhir di production. Itu bisa membocorkan informasi sensitif server Anda. Tampilkan pesan error yang ramah, dan simpan detail teknisnya di log server.
