PHP / Exception зохицуулах

Exception зохицуулах

Програм ажиллаж байх үед олон зүйл буруу болж болно — файл олдохгүй, өгөгдлийн сантай холбогдохгүй, буруу утга ирэх зэрэг. Ийм гэнэтийн нөхцөл байдлыг exception гэж нэрлэдэг. PHP-д exception-ийг try/catch блокоор зохицуулдаг: "туршиж үзэх" (try) ба "алдааг барих" (catch). Exception нь алдааны мэдэгдлийг (error message) хэвлэн зогсохоос илүү сайн шийдэл — алдааг ухаалгаар зохицуулж, хэрэглэгчид ойлгомжтой мэдэгдэл өгдөг.

try / catch — үндсэн бүтэц

try блок дотор exception шидэж болох код байрна. Exception шидэгдвэл catch блок ажиллана. Exception шидэгдэхгүй бол catch алгасагдана:

php
<?php
function хуваах(float $тоо, float $хуваагч): float {
    if ($хуваагч === 0.0) {
        throw new InvalidArgumentException("Тэгд хуваах боломжгүй.");
    }
    return $тоо / $хуваагч;
}

try {
    echo хуваах(10, 2) . "\n";  // 5 — ажиллана
    echo хуваах(10, 0) . "\n";  // Exception шидэнэ
    echo "Энэ мөр хэзээ ч ажиллахгүй\n"; // алгасагдана
} catch (InvalidArgumentException $e) {
    echo "Алдаа: " . $e->getMessage() . "\n"; // Алдаа: Тэгд хуваах боломжгүй.
    echo "Мөр дугаар: " . $e->getLine() . "\n";
    echo "Файл: " . $e->getFile() . "\n";
}

echo "Програм үргэлжилж байна.\n"; // catch-ийн дараа үргэлжилнэ
?>

$e->getMessage() — алдааны мэдэгдэл, $e->getLine() — exception шидэгдсэн мөр, $e->getFile() — файлын нэрийг буцаадаг.

Олон catch — төрлөөр ялгах

Нэг try блокт хэд хэдэн catch бичиж, exception-ийн төрлөөр ялгаж зохицуулж болно. PHP нь дарааллаар шалгаж, тохирсон эхнийхийг ажиллуулдаг:

php
<?php
function файл_унших(string $зам): string {
    if (!file_exists($зам)) {
        throw new RuntimeException("Файл олдсонгүй: {$зам}");
    }
    if (!is_readable($зам)) {
        throw new RuntimeException("Файлыг унших эрхгүй: {$зам}");
    }
    $агуулга = file_get_contents($зам);
    if ($агуулга === false) {
        throw new RuntimeException("Файл уншихад алдаа гарлаа.");
    }
    return $агуулга;
}

function тоо_задлах(string $утга): int {
    if (!is_numeric($утга)) {
        throw new InvalidArgumentException("'{$утга}' тоо биш байна.");
    }
    return (int)$утга;
}

try {
    $агуулга = файл_унших("тохиргоо.txt");
    $тоо     = тоо_задлах("abc"); // Энд exception шидэнэ
} catch (InvalidArgumentException $e) {
    // Буруу утгын алдаа
    echo "Оруулт буруу: " . $e->getMessage() . "\n";
} catch (RuntimeException $e) {
    // Файлын алдаа
    echo "Системийн алдаа: " . $e->getMessage() . "\n";
} catch (Exception $e) {
    // Бусад бүх exception — хамгийн сүүлд бичнэ
    echo "Тодорхойгүй алдаа: " . $e->getMessage() . "\n";
}
?>

Илүү тодорхой exception төрлүүдийг эхэнд, ерөнхий Exception-г хамгийн сүүлд бичнэ — эсрэгээр бол ерөнхий нь бүгдийг барьчихна.

finally — заавал ажилладаг блок

finally блок нь exception шидэгдсэн эсэхээс үл хамааран үргэлж ажилладаг. Нөөц чөлөөлөх, лог бичих, холболт хаах зэрэгт тохиромжтой:

php
<?php
function өгөгдөл_боловсруулах(array $өгөгдөл): void {
    $файл = fopen('гаралт.txt', 'w');

    try {
        foreach ($өгөгдөл as $утга) {
            if (!is_string($утга)) {
                throw new InvalidArgumentException("Зөвхөн мөр өгөгдөл хүлээнэ.");
            }
            fwrite($файл, $утга . "\n");
        }
        echo "Амжилттай боловсруулав.\n";
    } catch (InvalidArgumentException $e) {
        echo "Алдаа: " . $e->getMessage() . "\n";
    } finally {
        // Exception шидэгдсэн ч, шидэгдээгүй ч энд хүрнэ
        fclose($файл);
        echo "Файл хаагдлаа.\n";
    }
}

өгөгдөл_боловсруулах(["Болд", "Сарнай"]);
// Амжилттай боловсруулав.
// Файл хаагдлаа.

өгөгдөл_боловсруулах(["Болд", 42, "Сарнай"]);
// Алдаа: Зөвхөн мөр өгөгдөл хүлээнэ.
// Файл хаагдлаа.  ← finally заавал ажилладаг
?>

Өөрийн Exception класс

Exception классаас өвлөн тусгай exception класс бүтээж болно. Энэ нь алдааны төрлийг илүү тодорхой болгож, тусгай мэдээлэл дамжуулах боломж олгодог:

php
<?php
// Суурь апп exception
class АппАлдаа extends RuntimeException {}

// Тодорхой алдааны төрлүүд
class ЗөвшөөрөлгүйАлдаа extends АппАлдаа {
    public function __construct(string $үйлдэл) {
        parent::__construct("'{$үйлдэл}' үйлдлийг гүйцэтгэх эрхгүй байна.", 403);
    }
}

class ОлдохгүйАлдаа extends АппАлдаа {
    public function __construct(string $нөөц, int|string $id) {
        parent::__construct("'{$нөөц}' (id: {$id}) олдсонгүй.", 404);
    }
}

class БаталгаажуулалтАлдаа extends АппАлдаа {
    private array $алдаанууд;

    public function __construct(array $алдаанууд) {
        parent::__construct("Оруулт баталгаажаагүй.", 422);
        $this->алдаанууд = $алдаанууд;
    }

    public function алдаанууд(): array {
        return $this->алдаанууд;
    }
}

// Ашиглах жишээ
function хэрэглэгч_авах(int $id): array {
    $хэрэглэгчид = [1 => ['нэр' => 'Болд'], 2 => ['нэр' => 'Сарнай']];

    if (!isset($хэрэглэгчид[$id])) {
        throw new ОлдохгүйАлдаа('Хэрэглэгч', $id);
    }
    return $хэрэглэгчид[$id];
}

function шинэ_бүртгэл(array $өгөгдөл): void {
    $алдаанууд = [];
    if (empty($өгөгдөл['нэр']))   $алдаанууд[] = "Нэр заавал шаардлагатай.";
    if (empty($өгөгдөл['имэйл'])) $алдаанууд[] = "И-мэйл заавал шаардлагатай.";

    if (!empty($алдаанууд)) {
        throw new БаталгаажуулалтАлдаа($алдаанууд);
    }
}

try {
    $хэрэглэгч = хэрэглэгч_авах(99);
} catch (ОлдохгүйАлдаа $e) {
    echo $e->getMessage() . " (код: " . $e->getCode() . ")\n";
    // 'Хэрэглэгч' (id: 99) олдсонгүй. (код: 404)
}

try {
    шинэ_бүртгэл(['нэр' => '']);
} catch (БаталгаажуулалтАлдаа $e) {
    echo $e->getMessage() . "\n";
    foreach ($e->алдаанууд() as $алдаа) {
        echo "  - {$алдаа}\n";
    }
}
?>

set_exception_handler — барьж авагдаагүй exception

Аль ч catch-д барьж авагдаагүй exception-уудыг нэг газарт зохицуулах боломжтой:

php
<?php
set_exception_handler(function (Throwable $e): void {
    // Лог бичнэ
    error_log($e->getMessage() . " in " . $e->getFile() . ":" . $e->getLine());

    // Хэрэглэгчид ойлгомжтой мэдэгдэл
    echo "Системд алдаа гарлаа. Дахин оролдоно уу.";
});

// Барьж авагдаагүй exception
throw new RuntimeException("Тест алдаа");
// Хэрэглэгч: "Системд алдаа гарлаа. Дахин оролдоно уу."
// Лог: тодорхой алдааны мэдээлэл
?>

Дараагийн хичээлд:

PHP Data Objects (PDO) ашиглан өгөгдлийн сантай холбогдох аргыг судална. PDO гэж юу болохыг, SQLite болон MySQL-тэй хэрхэн холбогддогийг, prepared statement яагаад ашиглах ёстойг жишээгээр тайлбарлана.