15-bo‘lim

Xatolar va istisnolar

Error va Exception ierarxiyasi, o'z istisno turlaringiz, finally, global ishlovchi va jurnalga yozish.

🕑 10 daqiqa o‘qish 📄 724 so‘z 👁 1 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Ikki ierarxiya
  2. O'z istisno turlaringiz
  3. finally va zanjir
  4. Global ishlovchi
  5. Qachon istisno, qachon qiymat
  6. Xulosa

PHP da xatolarning ikki oilasi bor va ularni chalkashtirish keng tarqalgan muammo.

Bu bo'limda ularni ajratamiz va xatoni to'g'ri boshqarishni o'rganamiz.

Ikki ierarxiya #

PHP
// Har bir misol qat'iy turlar rejimida ishlaydi.
PHP
$sinovlar = [
    "Nolga bo'lish" => static function (): mixed {
        return intdiv(10, 0);
    },
    "Noto'g'ri tur" => static function (): mixed {
        $f = static fn(int $x): int => $x * 2;
        return $f("matn");
    },
    "Yo'q usul" => static function (): mixed {
        $o = new stdClass();
        return $o->yoqUsul();
    },
    "Qo'lda tashlangan" => static function (): mixed {
        throw new RuntimeException("Biror narsa noto'g'ri ketdi");
    },
];

printf("%-20s %-26s %s\n", "HOLAT", "SINF", "OTA SINF");
echo str_repeat("-", 72) . "\n";

foreach ($sinovlar as $nom => $f) {
    try {
        $f();
        printf("%-20s %s\n", $nom, "(xato bo'lmadi)");
    } catch (Throwable $x) {
        $ota = get_parent_class($x) ?: "-";
        printf("%-20s %-26s %s\n", $nom, $x::class, $ota);
    }
}

echo "\n";
echo "Hammasi Throwable dan keladi:\n";
foreach ([new TypeError("x"), new RuntimeException("y"), new DivisionByZeroError("z")] as $x) {
    printf("  %-24s Throwable mi: %s\n", $x::class,
           $x instanceof Throwable ? "ha" : "yo'q");
}
Natija
HOLAT                SINF                       OTA SINF
------------------------------------------------------------------------
Nolga bo'lish        DivisionByZeroError        ArithmeticError
Noto'g'ri tur        TypeError                  Error
Yo'q usul            Error                      -
Qo'lda tashlangan    RuntimeException           Exception

Hammasi Throwable dan keladi:
  TypeError                Throwable mi: ha
  RuntimeException         Throwable mi: ha
  DivisionByZeroError      Throwable mi: ha
PHP xatolar ierarxiyasi Throwable Error Exception DASTURCHI xatosi TypeError ValueError DivisionByZeroError ArgumentCountError Kodni TUZATISH kerak Odatda tutilmaydi KUTILADIGAN holat RuntimeException InvalidArgumentException PDOException JsonException Kod HAL QILADI Tutish o'rinli Hammasini tutish kerak bo'lsa - Throwable ni tuting catch (Exception $x) TypeError ni TUTMAYDI - bu eng ko'p uchraydigan chalkashlik.
Error - kodni tuzatish kerak; Exception - kod hal qiladi
catch (Exception $x) hamma xatoni tutmaydi

PHP 7 dan boshlab ichki xatolar Error sinfidan keladi va u Exception dan voris emas.

Ya'ni bu kod TypeError ni o'tkazib yuboradi:

PHP
try {
    $natija = xavfliAmal();
} catch (Exception $x) {        // TypeError TUTILMAYDI
    // ...
}

To'g'ri variantlar:

YozuvNimani tutadi
catch (Exception $x)Faqat kutiladigan holatlar
catch (Error $x)Faqat dasturchi xatolari
catch (Throwable $x)Hammasini

Amaliy qoida:

  • Aniq turni tuting: catch (PDOException $x).
  • Global ishlovchida esa Throwable ni tuting.
  • Error ni odatda tutmang - u kodda xato borligini bildiradi va uni yashirish foyda keltirmaydi.

Istisno: dastur qulamasligi kerak bo'lgan uzoq ishlaydigan jarayonlarda (navbat ishlovchisi) Throwable tutiladi, jurnalga yoziladi va keyingi vazifaga o'tiladi.

O'z istisno turlaringiz #

PHP
// Bazaviy istisno - butun ilova uchun
abstract class IlovaXatosi extends RuntimeException
{
    abstract public function foydalanuvchiXabari(): string;
}

final class MablagYetarliEmas extends IlovaXatosi
{
    public function __construct(
        public readonly int $soralgan,
        public readonly int $mavjud,
    ) {
        parent::__construct(sprintf(
            "Mablag' yetarli emas: so'ralgan %d, mavjud %d",
            $soralgan, $mavjud
        ));
    }

    public function yetishmaydi(): int
    {
        return $this->soralgan - $this->mavjud;
    }

    public function foydalanuvchiXabari(): string
    {
        return sprintf(
            "Hisobingizda %s so'm yetishmayapti.",
            number_format($this->yetishmaydi() / 100, 2, ",", " ")
        );
    }
}

final class HisobTopilmadi extends IlovaXatosi
{
    public function __construct(public readonly int $hisobId)
    {
        parent::__construct("Hisob topilmadi: {$hisobId}");
    }

    public function foydalanuvchiXabari(): string
    {
        return "Bunday hisob mavjud emas.";
    }
}

function pulYech(int $hisobId, int $tiyin): string
{
    $hisoblar = [1 => 50000000, 2 => 12000000];

    if (!isset($hisoblar[$hisobId])) {
        throw new HisobTopilmadi($hisobId);
    }
    if ($tiyin > $hisoblar[$hisobId]) {
        throw new MablagYetarliEmas($tiyin, $hisoblar[$hisobId]);
    }

    return "Yechildi: " . number_format($tiyin / 100, 2, ",", " ") . " so'm";
}

$urinishlar = [[1, 30000000], [1, 99000000], [7, 1000000], [2, 5000000]];

foreach ($urinishlar as [$id, $summa]) {
    try {
        printf("hisob %d: %s\n", $id, pulYech($id, $summa));
    } catch (IlovaXatosi $x) {
        printf("hisob %d: %s\n", $id, $x->foydalanuvchiXabari());
        printf("          (texnik: %s)\n", $x->getMessage());
    }
}
Natija
hisob 1: Yechildi: 300 000,00 so'm
hisob 1: Hisobingizda 490 000,00 so'm yetishmayapti.
          (texnik: Mablag' yetarli emas: so'ralgan 99000000, mavjud 50000000)
hisob 7: Bunday hisob mavjud emas.
          (texnik: Hisob topilmadi: 7)
hisob 2: Yechildi: 50 000,00 so'm
Istisno ikki xabar olib yursin

Yuqoridagi sinflarda ikkita xabar bor:

UsulKimgaNima uchun
getMessage()Dasturchiga, jurnalgaTafsilot, id, qiymatlar
foydalanuvchiXabari()FoydalanuvchigaTushunarli va xavfsiz

Bu ajratish muhim: foydalanuvchi "Hisob topilmadi: 7" xabarini ko'rmasligi kerak - u ichki id ni oshkor qiladi va hujumchiga hisoblar ro'yxatini sanab chiqish imkonini beradi.

Istisnoga qo'shimcha ma'lumot qo'shish ham foydali: MablagYetarliEmas da soralgan va mavjud xossalari bor. Shunda tutuvchi kod aniq qancha yetishmaganini bilib, mazmunli javob bera oladi.

Ierarxiyani to'g'ri qurish esa tutishni soddalashtiradi: catch (IlovaXatosi $x) bitta qatorda barcha ilova xatolarini tutadi, lekin TypeError ni tutmaydi - va bu aynan kerakli xatti-harakat.

finally va zanjir #

PHP
function faylniQaytaIshla(string $nom, bool $xatoQil): string
{
    $qadamlar = [];
    $qadamlar[] = "ochildi";

    try {
        if ($xatoQil) {
            throw new RuntimeException("o'qishda xato");
        }
        $qadamlar[] = "o'qildi";
        return implode(" -> ", $qadamlar) . " -> qaytarildi";
    } catch (RuntimeException $x) {
        $qadamlar[] = "xato tutildi";
        return implode(" -> ", $qadamlar);
    } finally {
        // BU HAR DOIM ishlaydi - return bo'lsa ham
        echo "  [finally] {$nom} yopildi\n";
    }
}

echo "Muvaffaqiyatli:\n";
echo "  " . faylniQaytaIshla("a.txt", false) . "\n";

echo "\nXato bilan:\n";
echo "  " . faylniQaytaIshla("b.txt", true) . "\n";

echo "\n";
// Istisno zanjiri - asl sababni saqlash
function sozlamaniYukla(string $xom): array
{
    try {
        return json_decode($xom, true, 512, JSON_THROW_ON_ERROR);
    } catch (JsonException $x) {
        // Asl istisnoni UCHINCHI argument sifatida uzatamiz
        throw new RuntimeException("Sozlama fayli buzuq", 0, $x);
    }
}

try {
    sozlamaniYukla("{buzuq}");
} catch (RuntimeException $x) {
    echo "Tutildi:     " . $x->getMessage() . "\n";
    $sabab = $x->getPrevious();
    echo "Asl sabab:   " . ($sabab?->getMessage() ?? "yo'q") . "\n";
    echo "Sabab sinfi: " . ($sabab !== null ? $sabab::class : "-") . "\n";
}
Natija
Muvaffaqiyatli:
  [finally] a.txt yopildi
  ochildi -> o'qildi -> qaytarildi

Xato bilan:
  [finally] b.txt yopildi
  ochildi -> xato tutildi

Tutildi:     Sozlama fayli buzuq
Asl sabab:   Syntax error
Sabab sinfi: JsonException
finally va istisno zanjiri

finally bloki uch holatda ham ishlaydi:

Holatfinally ishlaydimi
Kod muvaffaqiyatli tugadiHa
return bajarildiHa (return dan keyin)
Istisno tashlandiHa

Shuning uchun u resurs bo'shatish uchun ideal: fayl yopish, tranzaksiyani orqaga qaytarish, qulfni ochish.

Istisno zanjiri esa getPrevious() orqali asl sababni saqlaydi:

PHP
throw new RuntimeException("Yuqori darajadagi xabar", 0, $aslIstisno);
//                                                   ^   ^
//                                                 kod  SABAB

Bu nima uchun kerak: yuqori qatlamda mazmunli xabar berasiz ("Sozlama fayli buzuq"), lekin jurnalda aniq texnik sabab ham saqlanadi ("Syntax error").

Ikkalasisiz nima bo'ladi: jurnalda "Sozlama fayli buzuq" yozuvi bor, lekin nimasi buzuqligi noma'lum.

Global ishlovchi #

PHP
<?php
declare(strict_types=1);

// 1. Tutilmagan istisnolar uchun
set_exception_handler(function (Throwable $x): void {
    // Jurnalga TO'LIQ tafsilot
    error_log(sprintf(
        "[%s] %s: %s in %s:%d\n%s",
        date("Y-m-d H:i:s"),
        $x::class,
        $x->getMessage(),
        $x->getFile(),
        $x->getLine(),
        $x->getTraceAsString()
    ));

    // Foydalanuvchiga UMUMIY xabar
    http_response_code(500);
    if (!headers_sent()) {
        header("Content-Type: text/html; charset=UTF-8");
    }
    echo "<h1>Xatolik yuz berdi</h1>";
    echo "<p>Muammo qayd etildi. Iltimos, keyinroq urinib ko'ring.</p>";
    exit(1);
});

// 2. PHP ogohlantirishlarini istisnoga aylantirish
set_error_handler(function (int $daraja, string $xabar, string $fayl, int $qator): bool {
    if (!(error_reporting() & $daraja)) {
        return false;      // @ operatori bilan bosilgan
    }
    throw new ErrorException($xabar, 0, $daraja, $fayl, $qator);
});

// 3. Halokatli xatolar uchun (yuqoridagilar ularni tutmaydi)
register_shutdown_function(function (): void {
    $xato = error_get_last();
    if ($xato !== null && in_array($xato["type"], [E_ERROR, E_PARSE, E_CORE_ERROR], true)) {
        error_log("HALOKATLI: {$xato['message']} in {$xato['file']}:{$xato['line']}");
    }
});
Xato tafsilotini foydalanuvchiga hech qachon ko'rsatmang

Yuqoridagi ishlovchida ajratish aniq:

QayergaNima ketadi
error_log()Sinf, xabar, fayl, qator, to'liq iz
Brauzerga"Xatolik yuz berdi" - boshqa hech nima

2-bo'limda aytilgan sabab shu: xato xabarida fayl yo'llari, SQL so'rovlari va ba'zan parollar bo'ladi.

Uch ishlovchi uch xil narsani qamrab oladi va uchalasi ham kerak:

IshlovchiNimani tutadi
set_exception_handlerTutilmagan Throwable
set_error_handlerWarning, Notice, Deprecated
register_shutdown_functionE_ERROR - halokatli xato

Uchinchisi alohida: xotira tugashi yoki sintaksis xatosi oddiy ishlovchilarga umuman yetib bormaydi.

Yana bir muhim maslahat: @ operatorini ishlatmang. U xatoni yashiradi va muammoni topishni imkonsiz qiladi.

Qachon istisno, qachon qiymat #

PHP
// 1. KUTILADIGAN holat -> qiymat qaytaring
function foydalanuvchiTop(int $id): ?array
{
    $baza = [1 => ["ism" => "Husanboy"], 2 => ["ism" => "Malika"]];
    return $baza[$id] ?? null;          // "topilmadi" - normal javob
}

// 2. KUTILMAGAN holat -> istisno tashlang
function foydalanuvchiTopYoki(int $id): array
{
    $f = foydalanuvchiTop($id);
    if ($f === null) {
        throw new OutOfBoundsException("Foydalanuvchi topilmadi: {$id}");
    }
    return $f;
}

echo "1. Qiymat qaytaruvchi (qidiruv natijasi):\n";
foreach ([1, 99] as $id) {
    $f = foydalanuvchiTop($id);
    printf("   id=%-3d %s\n", $id, $f["ism"] ?? "(topilmadi)");
}

echo "\n2. Istisno tashlovchi (majburiy mavjud bo'lishi kerak):\n";
foreach ([2, 99] as $id) {
    try {
        printf("   id=%-3d %s\n", $id, foydalanuvchiTopYoki($id)["ism"]);
    } catch (OutOfBoundsException $x) {
        printf("   id=%-3d istisno: %s\n", $id, $x->getMessage());
    }
}

echo "\n";
echo "Qoida:\n";
echo "  Qidiruv 'topilmadi' qaytarishi NORMAL   -> null qaytaring\n";
echo "  Kirgan foydalanuvchi topilmasa          -> istisno tashlang\n";
echo "  (chunki bu ma'lumotlar butunligi buzilgani degani)\n";
Natija
1. Qiymat qaytaruvchi (qidiruv natijasi):
   id=1   Husanboy
   id=99  (topilmadi)

2. Istisno tashlovchi (majburiy mavjud bo'lishi kerak):
   id=2   Malika
   id=99  istisno: Foydalanuvchi topilmadi: 99

Qoida:
  Qidiruv 'topilmadi' qaytarishi NORMAL   -> null qaytaring
  Kirgan foydalanuvchi topilmasa          -> istisno tashlang
  (chunki bu ma'lumotlar butunligi buzilgani degani)
Amaliy topshiriq
  1. intdiv(10, 0) ni catch (Exception $x) bilan tutishga urining.
  2. Uni catch (Throwable $x) ga almashtiring.
  3. Error va Exception ning ota sinfini get_parent_class bilan chiqaring.
  4. Bazaviy IlovaXatosi sinfi va ikkita vorisini yozing.
  5. Istisnoga qo'shimcha xossa qo'shing va uni tutuvchi kodda ishlating.
  6. finally blokida echo yozing va return dan keyin ishlashini ko'ring.
  7. Istisno zanjiri yarating va getPrevious() bilan asl sababni oling.
  8. set_exception_handler bilan global ishlovchi yozing.
  9. Jurnalga to'liq, ekranga umumiy xabar chiqaring.
  10. Qaysi funksiyangiz null, qaysi biri istisno qaytarishi kerakligini aniqlang.

Xulosa #

  • PHP da ikki ierarxiya bor: Error (dasturchi xatosi) va Exception (kutiladigan holat).
  • Ikkalasi ham Throwable dan keladi, lekin bir-biridan voris emas.
  • catch (Exception $x) TypeError ni tutmaydi - bu eng keng tarqalgan chalkashlik.
  • Aniq turni tuting; global ishlovchida esa Throwable ni.
  • Error ni odatda tutmang - u kodda xato borligini bildiradi.
  • O'z istisno ierarxiyangizni quring - catch (IlovaXatosi $x) hammasini bir joyda tutadi.
  • Istisnoda ikki xabar bo'lsin: jurnal uchun texnik, foydalanuvchi uchun umumiy.
  • finally return dan keyin ham ishlaydi - resurs bo'shatish uchun ideal.
  • Istisno zanjiri (getPrevious) asl sababni saqlaydi.
  • Uch global ishlovchi kerak: istisno, xato va register_shutdown_function.
  • Xato tafsilotini jurnalga yozing, foydalanuvchiga umumiy xabar bering.
  • @ operatorini ishlatmang - u muammoni yashiradi.

Keyingi bo'limda PDO orqali ma'lumotlar bazasi bilan ishlashni o'rganamiz.

Xatolik topdingizmi?

Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.