try / catch / finally / throw în PHP

Erori și excepții PHP 5+ (finally: PHP 5.5+, throw ca expresie: PHP 8.0+)

Prinde erorile (excepțiile) apărute într-un bloc de cod, ca scriptul să nu se oprească brusc.

Sintaxă
try { ... } catch (TipExceptie $e) { ... } finally { ... }
throw new Exception("mesaj");

Ce face try / catch / finally / throw

Când ceva nu merge (fișierul lipsește, baza de date nu răspunde, datele sunt greșite), codul poate arunca o excepție cu throw. Excepția oprește execuția normală și urcă până la primul bloc catch potrivit. Dacă nu o prinde nimeni, scriptul se oprește cu Fatal error: Uncaught ....

Pui codul riscant în try. Fiecare catch prinde un tip de excepție (și tipurile care îl extind). Poți prinde mai multe tipuri în același bloc cu |: catch (TypeError | ValueError $e). Blocul finally rulează mereu, cu sau fără eroare, de exemplu pentru a închide un fișier.

În PHP 8, aproape toate erorile grave ale limbajului (tip greșit, împărțire la zero cu intdiv, metodă inexistentă) sunt și ele aruncate, ca obiecte Error. catch (Throwable $e) prinde absolut tot: și Exception, și Error.

Exemple

Exemplul 1: Aruncare și prindere

<?php
function imparte(float $a, float $b): float
{
    if ($b == 0) {
        throw new InvalidArgumentException("Nu se poate împărți la zero.");
    }
    return $a / $b;
}

try {
    echo imparte(10, 4) . "\n";
    echo imparte(5, 0) . "\n";
    echo "Această linie nu mai rulează.\n";
} catch (InvalidArgumentException $e) {
    echo "Eroare: " . $e->getMessage() . "\n";
}

echo "Scriptul continuă.";

Rezultat

2.5
Eroare: Nu se poate împărți la zero.
Scriptul continuă.

Exemplul 2: Mai multe blocuri catch și finally

PHP alege primul catch care se potrivește. finally rulează în ambele cazuri.

<?php
function proceseaza(mixed $cantitate): void
{
    try {
        echo "Procesez " . var_export($cantitate, true) . ": ";
        echo intdiv(100, $cantitate) . " lei/buc\n";
    } catch (DivisionByZeroError $e) {
        echo "cantitatea nu poate fi 0\n";
    } catch (TypeError $e) {
        echo "cantitatea trebuie să fie număr întreg\n";
    } finally {
        echo "  (verificare terminată)\n";
    }
}

proceseaza(4);
proceseaza(0);
proceseaza("multe");

Rezultat

Procesez 4: 25 lei/buc
  (verificare terminată)
Procesez 0: cantitatea nu poate fi 0
  (verificare terminată)
Procesez 'multe': cantitatea trebuie să fie număr întreg
  (verificare terminată)

Exemplul 3: throw ca expresie (PHP 8+)

<?php
$config = ["db_host" => "localhost"];

try {
    $host = $config["db_host"] ?? throw new RuntimeException("Lipsește db_host");
    echo "Host: $host\n";
    $nume = $config["db_name"] ?? throw new RuntimeException("Lipsește db_name");
} catch (RuntimeException $e) {
    echo $e->getMessage();
}

Rezultat

Host: localhost
Lipsește db_name

Greșeli frecvente

  • Prinzi excepția și nu faci nimic cu ea: catch (Exception $e) {}. Eroarea dispare fără urmă și problema devine foarte greu de găsit. Măcar scrie-o în jurnal cu error_log.
  • Prinzi Exception și te aștepți să prinzi și un TypeError. Erorile limbajului extind Error, nu Exception. Folosește Throwable dacă vrei să prinzi tot.
  • Pui un catch general înaintea unuia specific. Primul care se potrivește câștigă, deci cel specific nu mai rulează niciodată.

Comenzi înrudite

  • Exception: Clasa de bază pentru excepții. Extinde-o ca să creezi tipuri proprii de erori pentru aplicația ta.
  • set_exception_handler: Setează funcția care rulează când o excepție nu este prinsă de niciun bloc catch.
  • error_log: Scrie un mesaj în jurnalul de erori al serverului sau într-un fișier ales de tine.
  • set_error_handler: Înlocuiește tratarea standard a avertismentelor și notificărilor PHP cu o funcție scrisă de tine.