19-bo‘lim
Zamonaviy PHP 8
enum, readonly, nullsafe, birinchi darajali chaqiriluvchilar, match va PHP 8 ning boshqa yangiliklari.
Ushbu bo‘lim mundarijasi
PHP 8 tilni sezilarli o'zgartirdi. Bu bo'limda oldingi bo'limlarda qisman ko'rgan yangiliklarni bir joyga yig'amiz va ular qaysi eski usulni almashtirishini ko'rsatamiz.
enum - cheklangan qiymatlar to'plami #
// Har bir misol qat'iy turlar rejimida ishlaydi.
// PHP 8.1 dan oldin: doimiylar va hech qanday kafolat yo'q
final class EskiHolat
{
public const YANGI = "yangi";
public const YUBORILGAN = "yuborilgan";
public const YAKUNLANGAN = "yakunlangan";
}
function eskiUsul(string $holat): string
{
// Har safar QO'LDA tekshirish kerak
$ruxsat = [EskiHolat::YANGI, EskiHolat::YUBORILGAN, EskiHolat::YAKUNLANGAN];
if (!in_array($holat, $ruxsat, true)) {
return "NOMA'LUM holat: {$holat}";
}
return "Holat: {$holat}";
}
echo eskiUsul(EskiHolat::YANGI) . "\n";
echo eskiUsul("qandaydir-narsa") . "\n";
echo "(satr bo'lgani uchun ISTALGAN qiymat kelishi mumkin)\n";
echo "\n" . str_repeat("=", 50) . "\n\n";
// PHP 8.1 dan: enum - noto'g'ri qiymatni TUR TIZIMI rad etadi
enum Holat: string
{
case Yangi = "yangi";
case Yuborilgan = "yuborilgan";
case Yakunlangan = "yakunlangan";
case Bekor = "bekor";
// Enum ichida usul bo'lishi mumkin
public function matni(): string
{
return match ($this) {
Holat::Yangi => "Yangi buyurtma",
Holat::Yuborilgan => "Yo'lda",
Holat::Yakunlangan => "Yetkazildi",
Holat::Bekor => "Bekor qilingan",
};
}
public function yakunlanganmi(): bool
{
return $this === Holat::Yakunlangan || $this === Holat::Bekor;
}
public function keyingisi(): ?Holat
{
return match ($this) {
Holat::Yangi => Holat::Yuborilgan,
Holat::Yuborilgan => Holat::Yakunlangan,
default => null,
};
}
}
function yangiUsul(Holat $holat): string
{
// Tekshirish KERAK EMAS - tur kafolatlaydi
return sprintf("%-12s %-16s yakunmi: %s", $holat->value, $holat->matni(),
$holat->yakunlanganmi() ? "ha" : "yo'q");
}
foreach (Holat::cases() as $h) {
echo yangiUsul($h) . "\n";
}
echo "\n";
echo "Zanjir: ";
$h = Holat::Yangi;
$yol = [$h->value];
while (($h = $h->keyingisi()) !== null) {
$yol[] = $h->value;
}
echo implode(" -> ", $yol) . "\n";
Holat: yangi
NOMA'LUM holat: qandaydir-narsa
(satr bo'lgani uchun ISTALGAN qiymat kelishi mumkin)
==================================================
yangi Yangi buyurtma yakunmi: yo'q
yuborilgan Yo'lda yakunmi: yo'q
yakunlangan Yetkazildi yakunmi: ha
bekor Bekor qilingan yakunmi: ha
Zanjir: yangi -> yuborilgan -> yakunlangan
enum doimiylardan nimasi bilan yaxshiroq| Doimiylar | enum | |
|---|---|---|
| Tur xavfsizligi | Yo'q - oddiy satr | Ha |
| Noto'g'ri qiymat | Qo'lda tekshirilsa topiladi | TypeError - o'zi rad etadi |
| Usul qo'shish | Mumkin emas | Mumkin |
| Barcha variantlarni olish | Qo'lda ro'yxat | ::cases() |
match to'liqligi | Tekshirilmaydi | Tekshiriladi |
PHP kodni oldindan kompilyatsiya qilmaydi, shuning uchun
tekshiruv chaqiruv paytida bo'ladi. Lekin farq katta:
yangiUsul("qandaydir") yozsangiz, PHP darhol
TypeError tashlaydi - siz uni qo'lda tekshirishingiz
shart emas. Statik tahlilchi (PHPStan, Psalm) esa buni
ishga tushirishdan oldin ham topadi.
Oxirgi qator ayniqsa qimmatli: enum ga yangi case
qo'shsangiz va uni match da hisobga olmasangiz, PHP
UnhandledMatchError tashlaydi. Ya'ni yangi holat
qo'shilganda barcha unutilgan joylar o'zini ko'rsatadi.
Ikki xil enum bor:
| Tur | Yozilishi | Qachon |
|---|---|---|
| Toza | enum Holat { case Yangi; } | Qiymat kerak emas |
| Qiymatli | enum Holat: string { case Yangi = "yangi"; } | Bazaga saqlanadi |
Bazaga saqlash uchun qiymatli enum kerak:
$holat = Holat::from($qator["holat"]); // topilmasa - xato
$holat = Holat::tryFrom($qator["holat"]); // topilmasa - null
tryFrom odatda xavfsizroq: bazada eski yoki noto'g'ri
qiymat bo'lishi mumkin.
enum bazaga saqlash #
enum Daraja: int
{
case Nosozlik = 10;
case Ogohlantirish = 20;
case Xato = 30;
case Halokat = 40;
public function belgi(): string
{
return match ($this) {
Daraja::Nosozlik => "i",
Daraja::Ogohlantirish => "!",
Daraja::Xato => "X",
Daraja::Halokat => "#",
};
}
public static function engKamidan(int $qiymat): array
{
return array_values(array_filter(
self::cases(),
static fn(Daraja $d): bool => $d->value >= $qiymat
));
}
}
echo "Barcha darajalar:\n";
foreach (Daraja::cases() as $d) {
printf(" [%s] %-14s = %d\n", $d->belgi(), $d->name, $d->value);
}
echo "\n";
echo "Bazadan kelgan qiymatlarni aylantirish:\n";
foreach ([10, 30, 99] as $xom) {
$d = Daraja::tryFrom($xom);
printf(" %-4d -> %s\n", $xom, $d?->name ?? "(noma'lum - null)");
}
echo "\n";
echo "from() esa istisno tashlaydi:\n";
try {
Daraja::from(99);
} catch (ValueError $x) {
echo " ValueError: " . $x->getMessage() . "\n";
}
echo "\n";
$jiddiy = Daraja::engKamidan(30);
echo "Xato va undan yuqori: " . implode(", ", array_column($jiddiy, "name")) . "\n";
Barcha darajalar:
[i] Nosozlik = 10
[!] Ogohlantirish = 20
[X] Xato = 30
[#] Halokat = 40
Bazadan kelgan qiymatlarni aylantirish:
10 -> Nosozlik
30 -> Xato
99 -> (noma'lum - null)
from() esa istisno tashlaydi:
ValueError: 99 is not a valid backing value for enum Daraja
Xato va undan yuqori: Xato, Halokat
Nullsafe va birinchi darajali chaqiriluvchilar #
final class Manzil
{
public function __construct(
public readonly string $shahar,
public readonly ?string $kocha = null,
) {}
public function toliq(): string
{
return $this->kocha === null ? $this->shahar : "{$this->shahar}, {$this->kocha}";
}
}
final class Foydalanuvchi
{
public function __construct(
public readonly string $ism,
public readonly ?Manzil $manzil = null,
) {}
}
$foydalanuvchilar = [
new Foydalanuvchi("Husanboy", new Manzil("Namangan", "Alisher Navoiy ko'chasi")),
new Foydalanuvchi("Malika", new Manzil("Toshkent")),
new Foydalanuvchi("Nodira"),
];
echo "Nullsafe operatori (?->):\n";
foreach ($foydalanuvchilar as $f) {
// Eski usul: $f->manzil !== null ? $f->manzil->toliq() : "noma'lum"
printf(" %-10s %s\n", $f->ism, $f->manzil?->toliq() ?? "(manzil ko'rsatilmagan)");
}
echo "\n";
echo "Zanjir ham ishlaydi - birinchi null da to'xtaydi:\n";
foreach ($foydalanuvchilar as $f) {
printf(" %-10s shahar: %s\n", $f->ism, $f->manzil?->shahar ?? "-");
}
echo "\n";
// Birinchi darajali chaqiriluvchi sintaksisi (PHP 8.1)
$ismlar = array_map(
static fn(Foydalanuvchi $f): string => $f->ism,
$foydalanuvchilar
);
echo "Birinchi darajali chaqiriluvchi (...):\n";
$kattaHarf = mb_strtoupper(...); // funksiyaga HAVOLA
$uzunlik = mb_strlen(...);
echo " katta harf: " . implode(", ", array_map($kattaHarf, $ismlar)) . "\n";
echo " uzunliklar: " . implode(", ", array_map($uzunlik, $ismlar)) . "\n";
echo "\n";
echo "Eski usul \"mb_strtoupper\" satri edi - unda:\n";
echo " - muharrir funksiyani topa olmaydi\n";
echo " - nomini xato yozsangiz, ish paytida bilinadi\n";
echo " - qayta nomlash vositasi ishlamaydi\n";
Nullsafe operatori (?->):
Husanboy Namangan, Alisher Navoiy ko'chasi
Malika Toshkent
Nodira (manzil ko'rsatilmagan)
Zanjir ham ishlaydi - birinchi null da to'xtaydi:
Husanboy shahar: Namangan
Malika shahar: Toshkent
Nodira shahar: -
Birinchi darajali chaqiriluvchi (...):
katta harf: HUSANBOY, MALIKA, NODIRA
uzunliklar: 8, 6, 6
Eski usul "mb_strtoupper" satri edi - unda:
- muharrir funksiyani topa olmaydi
- nomini xato yozsangiz, ish paytida bilinadi
- qayta nomlash vositasi ishlamaydi
?-> null ni tarqatadi - buni bilib ishlating$a?->b()?->c() zanjiri birinchi null da to'xtaydi
va butun ifoda null qaytaradi.
Bu qulay, lekin tuzoq ham: null jimgina tarqaladi
va muammo uzoqda ko'rinadi.
$narx = $buyurtma?->mahsulot?->narx; // null bo'lishi mumkin
$jami = $narx * $soni; // null * 5 = 0 (!)
Bu yerda $jami nol bo'lib chiqadi va hech qanday
xato bo'lmaydi.
Qachon ?-> o'rinli:
| O'rinli | O'rinsiz |
|---|---|
Ixtiyoriy bog'liqlik (?Manzil) | Majburiy bo'lishi kerak bo'lgan narsa |
Chiqarish uchun (?? "-") | Hisob-kitobda |
| Jurnalga yozishda | Biznes mantiqda |
Majburiy qiymat yo'q bo'lsa - ?-> bilan yashirish
o'rniga istisno tashlang (15-bo'lim). Shunda muammo
paydo bo'lgan joyda ko'rinadi.
Nomli argumentlar va readonly amalda #
final readonly class Sahifalash
{
public function __construct(
public int $sahifa = 1,
public int $hajm = 20,
public string $tartib = "sana",
public bool $kamayish = true,
) {}
public function ofset(): int
{
return ($this->sahifa - 1) * $this->hajm;
}
public function sql(): string
{
$yonalish = $this->kamayish ? "DESC" : "ASC";
return sprintf(
"ORDER BY %s %s LIMIT %d OFFSET %d",
$this->tartib, $yonalish, $this->hajm, $this->ofset()
);
}
// O'zgarmas obyektni "o'zgartirish" - YANGISINI qaytarish
public function sahifaBilan(int $sahifa): self
{
return new self($sahifa, $this->hajm, $this->tartib, $this->kamayish);
}
}
$s = new Sahifalash();
echo "Standart: " . $s->sql() . "\n";
// Nomli argumentlar - faqat kerakligini beramiz
$s2 = new Sahifalash(hajm: 50, tartib: "narx");
echo "Nomli argument: " . $s2->sql() . "\n";
$s3 = new Sahifalash(sahifa: 3, hajm: 10, kamayish: false);
echo "Uchinchi sahifa: " . $s3->sql() . "\n";
echo "\n";
$s4 = $s3->sahifaBilan(5);
echo "sahifaBilan(5): " . $s4->sql() . "\n";
echo "Asl obyekt: " . $s3->sql() . "\n";
echo "(o'zgarmadi - yangi obyekt qaytarildi)\n";
echo "\n";
try {
$s3->sahifa = 99;
} catch (Error $x) {
echo "readonly class himoyasi: " . $x->getMessage() . "\n";
}
Standart: ORDER BY sana DESC LIMIT 20 OFFSET 0
Nomli argument: ORDER BY narx DESC LIMIT 50 OFFSET 0
Uchinchi sahifa: ORDER BY sana ASC LIMIT 10 OFFSET 20
sahifaBilan(5): ORDER BY sana ASC LIMIT 10 OFFSET 40
Asl obyekt: ORDER BY sana ASC LIMIT 10 OFFSET 20
(o'zgarmadi - yangi obyekt qaytarildi)
readonly class himoyasi: Cannot modify readonly property Sahifalash::$sahifa
| Versiya | Yangilik |
|---|---|
| 8.0 | match, nomli argumentlar, konstruktor xossalari |
| 8.0 | ?->, str_contains, union turlar, JIT |
| 8.1 | enum, readonly xossa, birinchi darajali chaqiriluvchi |
| 8.1 | never qaytish turi, array_is_list() |
| 8.2 | readonly class, DNF turlar, true/false turlari |
| 8.3 | Turli sinf doimiylari, json_validate() |
| 8.4 | Xossa ilgaklari, asimmetrik ko'rinuvchanlik |
Bu darslik 8.2 ga asoslangan - shuning uchun barcha misollar 8.2 va undan yuqori versiyada ishlaydi.
Loyihangizda minimal versiyani composer.json da
ko'rsating:
{
"require": {
"php": ">=8.2"
}
}
Shunda Composer eski PHP da o'rnatishga ruxsat bermaydi va muammo ishga tushirishdan oldin bilinadi.
Qaysi versiya qo'llab-quvvatlanishini php.net/supported-versions
sahifasidan tekshiring - qo'llab-quvvatlanmaydigan
versiyada ishlash xavfsizlik xatosi hisoblanadi.
- Doimiylar bilan holat sinfini yozing va noto'g'ri qiymat bering.
- Uni
enumga aylantiring - noto'g'ri qiymat qabul qilinadimi? enumichigamatni()usulini qo'shing.Holat::cases()bilan barcha variantlarni chiqaring.enumga yangicaseqo'shing vamatchni yangilamang - nima bo'ladi?from()vatryFrom()ni noto'g'ri qiymat bilan solishtiring.?->bilan ixtiyoriy bog'liqlikka murojaat qiling.null * 5natijasini tekshiring.mb_strtoupper(...)sintaksisiniarray_mapda ishlating.readonly classyozing va xossasini o'zgartirishga urining.
Xulosa #
enumdoimiylarni almashtiradi: noto'g'ri qiymatTypeErrorberadi, qo'lda tekshirish kerak emas.enumga yangicaseqo'shilsa, unutilganmatchlarUnhandledMatchErrortashlaydi.- Bazaga saqlash uchun qiymatli
enumkerak; o'qishdatryFromxavfsizroq. enumichida usullar,staticusullar va interfeys amalga oshirish mumkin.?->nulltekshiruvini qisqartiradi, lekinnullni jimgina tarqatadi.- Majburiy qiymat yo'q bo'lsa -
?->bilan yashirmang, istisno tashlang. - Birinchi darajali chaqiriluvchi (
strlen(...)) satr nomidan yaxshiroq - muharrir tekshiradi. - Nomli argumentlar oraliqdagi argumentlarni o'tkazib yuborishga imkon beradi.
readonly class(8.2) barcha xossalarni o'zgarmas qiladi.- O'zgarmas obyektni "o'zgartirish" - yangisini qaytarish.
composer.jsonda minimal PHP versiyasini ko'rsating.
Keyingi, yakuniy bo'limda barcha bilimlarni bitta ishlaydigan loyihada birlashtiramiz.
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.