19-bo‘lim

Zamonaviy PHP 8

enum, readonly, nullsafe, birinchi darajali chaqiriluvchilar, match va PHP 8 ning boshqa yangiliklari.

🕑 9 daqiqa o‘qish 📄 665 so‘z 👁 1 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. enum - cheklangan qiymatlar to'plami
  2. enum bazaga saqlash
  3. Nullsafe va birinchi darajali chaqiriluvchilar
  4. Nomli argumentlar va readonly amalda
  5. Xulosa

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 #

PHP
// Har bir misol qat'iy turlar rejimida ishlaydi.
PHP
// 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";
Natija
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
Doimiylarenum
Tur xavfsizligiYo'q - oddiy satrHa
Noto'g'ri qiymatQo'lda tekshirilsa topiladiTypeError - o'zi rad etadi
Usul qo'shishMumkin emasMumkin
Barcha variantlarni olishQo'lda ro'yxat::cases()
match to'liqligiTekshirilmaydiTekshiriladi

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:

TurYozilishiQachon
Tozaenum Holat { case Yangi; }Qiymat kerak emas
Qiymatlienum Holat: string { case Yangi = "yangi"; }Bazaga saqlanadi

Bazaga saqlash uchun qiymatli enum kerak:

PHP
$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 #

PHP
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";
Natija
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
PHP 8 nima almashtirdi ESKI YANGI const HOLAT_YANGI = "yangi"; enum Holat: string { case Yangi = "yangi"; } $this->nom = $nom; (3 marta) __construct(private string $nom) konstruktorda takror xossalarni ko'chirish if ($o !== null) { $o->usul(); } $o?->usul() null tekshiruvi nullsafe operatori switch + break + default match ($x) { 1 => "a", default => "b" } qiymat qaytarmaydi ifoda, qat'iy solishtirish array_map("strtoupper", $m) array_map(strtoupper(...), $m) satr - xato topilmaydi birinchi darajali chaqiriluvchi strpos($a, $b) !== false str_contains($a, $b) 0 tuzog'i aniq bool Har bir yangilik bir xil maqsadda: xatoni ishga tushirishdan OLDIN topish.
PHP 8 - kamroq yozish va ko'proq kafolat

Nullsafe va birinchi darajali chaqiriluvchilar #

PHP
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";
Natija
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.

PHP
$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'rinliO'rinsiz
Ixtiyoriy bog'liqlik (?Manzil)Majburiy bo'lishi kerak bo'lgan narsa
Chiqarish uchun (?? "-")Hisob-kitobda
Jurnalga yozishdaBiznes 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 #

PHP
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";
}
Natija
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
Qaysi versiyada nima paydo bo'lgan
VersiyaYangilik
8.0match, nomli argumentlar, konstruktor xossalari
8.0?->, str_contains, union turlar, JIT
8.1enum, readonly xossa, birinchi darajali chaqiriluvchi
8.1never qaytish turi, array_is_list()
8.2readonly class, DNF turlar, true/false turlari
8.3Turli sinf doimiylari, json_validate()
8.4Xossa 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:

JSON
{
    "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.

Amaliy topshiriq
  1. Doimiylar bilan holat sinfini yozing va noto'g'ri qiymat bering.
  2. Uni enum ga aylantiring - noto'g'ri qiymat qabul qilinadimi?
  3. enum ichiga matni() usulini qo'shing.
  4. Holat::cases() bilan barcha variantlarni chiqaring.
  5. enum ga yangi case qo'shing va match ni yangilamang - nima bo'ladi?
  6. from() va tryFrom() ni noto'g'ri qiymat bilan solishtiring.
  7. ?-> bilan ixtiyoriy bog'liqlikka murojaat qiling.
  8. null * 5 natijasini tekshiring.
  9. mb_strtoupper(...) sintaksisini array_map da ishlating.
  10. readonly class yozing va xossasini o'zgartirishga urining.

Xulosa #

  • enum doimiylarni almashtiradi: noto'g'ri qiymat TypeError beradi, qo'lda tekshirish kerak emas.
  • enum ga yangi case qo'shilsa, unutilgan match lar UnhandledMatchError tashlaydi.
  • Bazaga saqlash uchun qiymatli enum kerak; o'qishda tryFrom xavfsizroq.
  • enum ichida usullar, static usullar va interfeys amalga oshirish mumkin.
  • ?-> null tekshiruvini qisqartiradi, lekin null ni 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.json da minimal PHP versiyasini ko'rsating.

Keyingi, yakuniy bo'limda barcha bilimlarni bitta ishlaydigan loyihada birlashtiramiz.

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.