15-bo‘lim
Xatolar va istisnolar
Error va Exception ierarxiyasi, o'z istisno turlaringiz, finally, global ishlovchi va jurnalga yozish.
Ushbu bo‘lim mundarijasi
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 #
// Har bir misol qat'iy turlar rejimida ishlaydi.
$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");
}
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
catch (Exception $x) hamma xatoni tutmaydiPHP 7 dan boshlab ichki xatolar Error sinfidan keladi
va u Exception dan voris emas.
Ya'ni bu kod TypeError ni o'tkazib yuboradi:
try {
$natija = xavfliAmal();
} catch (Exception $x) { // TypeError TUTILMAYDI
// ...
}
To'g'ri variantlar:
| Yozuv | Nimani 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
Throwableni tuting. Errorni 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 #
// 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());
}
}
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
Yuqoridagi sinflarda ikkita xabar bor:
| Usul | Kimga | Nima uchun |
|---|---|---|
getMessage() | Dasturchiga, jurnalga | Tafsilot, id, qiymatlar |
foydalanuvchiXabari() | Foydalanuvchiga | Tushunarli 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 #
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";
}
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 zanjirifinally bloki uch holatda ham ishlaydi:
| Holat | finally ishlaydimi |
|---|---|
| Kod muvaffaqiyatli tugadi | Ha |
return bajarildi | Ha (return dan keyin) |
| Istisno tashlandi | Ha |
Shuning uchun u resurs bo'shatish uchun ideal: fayl yopish, tranzaksiyani orqaga qaytarish, qulfni ochish.
Istisno zanjiri esa getPrevious() orqali asl
sababni saqlaydi:
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
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']}");
}
});
Yuqoridagi ishlovchida ajratish aniq:
| Qayerga | Nima 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:
| Ishlovchi | Nimani tutadi |
|---|---|
set_exception_handler | Tutilmagan Throwable |
set_error_handler | Warning, Notice, Deprecated |
register_shutdown_function | E_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 #
// 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";
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)
intdiv(10, 0)nicatch (Exception $x)bilan tutishga urining.- Uni
catch (Throwable $x)ga almashtiring. ErrorvaExceptionning ota sinfiniget_parent_classbilan chiqaring.- Bazaviy
IlovaXatosisinfi va ikkita vorisini yozing. - Istisnoga qo'shimcha xossa qo'shing va uni tutuvchi kodda ishlating.
finallyblokidaechoyozing vareturndan keyin ishlashini ko'ring.- Istisno zanjiri yarating va
getPrevious()bilan asl sababni oling. set_exception_handlerbilan global ishlovchi yozing.- Jurnalga to'liq, ekranga umumiy xabar chiqaring.
- Qaysi funksiyangiz
null, qaysi biri istisno qaytarishi kerakligini aniqlang.
Xulosa #
- PHP da ikki ierarxiya bor:
Error(dasturchi xatosi) vaException(kutiladigan holat). - Ikkalasi ham
Throwabledan keladi, lekin bir-biridan voris emas. catch (Exception $x)TypeErrorni tutmaydi - bu eng keng tarqalgan chalkashlik.- Aniq turni tuting; global ishlovchida esa
Throwableni. Errorni 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.
finallyreturndan 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.
O‘qish tarixini saqlamoqchimisiz?
Tizimga kirsangiz, tugatgan bo‘limlaringiz saqlanadi va qoldirgan joyingizdan davom etasiz.
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.