static, trait, readonly va enum

~20 daq · PHP · OOP — Ob'ektga yo'naltirilgan PHP

Muammodan boshlaylik

Uchta alohida muammo:

  1. Narxni formatlash funksiyasi Narx classiga tegishli, lekin uni chaqirish uchun har safar new Narx() yaratish kerakmi? Unda hech qanday ma'lumot yo'q-ku.
  2. Mahsulot, Maqola, Foydalanuvchi classlarining hammasida bir xil yaratilgan_vaqt va yangilangan_vaqt mantig'i bor. Ular bir-biridan meros olmaydi. Kodni qanday bo'lishamiz?
  3. Buyurtma holati 'new', 'paid', 'shipped' — oddiy string. Kimdir 'payed' deb yozib qo'ydi — hech narsa xato bermadi, buyurtma "yo'qolib" qoldi.

Uchtasining ham PHP da tayyor yechimi bor.


static — ob'ektga emas, classga tegishli

<?php

class Narx
{
    public static function format(int $summa): string
    {
        return number_format($summa, 0, '', ' ') . " so'm";
    }
}

echo Narx::format(3200000);   // 3 200 000 so'm — new kerak emas
  • static metod ob'ektsiz, Klass::metod() bilan chaqiriladi
  • Ichida $this yo'q — chunki ob'ekt yo'q

Static xususiyat — barcha ob'ektlar uchun bitta umumiy qiymat:

<?php

class Foydalanuvchi
{
    private static int $soni = 0;

    public function __construct(public string $ism)
    {
        self::$soni++;
    }

    public static function jamiSoni(): int
    {
        return self::$soni;
    }
}

new Foydalanuvchi("Ali");
new Foydalanuvchi("Vali");
echo Foydalanuvchi::jamiSoni();   // 2

self:: — "shu class" (ichkaridan murojaat).

Named constructor — static ning eng foydali ishlatilishi

<?php

class Pul
{
    private function __construct(private int $tiyin) {}   // private konstruktor!

    public static function somdan(int $som): self
    {
        return new self($som * 100);
    }

    public static function tiyindan(int $tiyin): self
    {
        return new self($tiyin);
    }

    public function som(): float
    {
        return $this->tiyin / 100;
    }
}

$a = Pul::somdan(5000);
$b = Pul::tiyindan(250);

new Pul(5000) — 5000 so'mmi yoki tiyinmi? Noaniq. Pul::somdan(5000) — aniq. DateTimeImmutable::createFromFormat() ham aynan shunday.

[!WARNING] Static ni suiiste'mol qilmang. Hamma narsani static qilsangiz — bu shunchaki funksiyalar to'plami bo'lib qoladi, OOP ning foydalari (almashtirish, test, interface) yo'qoladi. Static — holatsiz yordamchilar va named constructor lar uchun.


Class konstantalari

<?php

class Buyurtma
{
    public const MAKS_MAHSULOT = 50;
    private const SOLIQ = 0.12;

    public function soliq(int $summa): int
    {
        return (int) ($summa * self::SOLIQ);
    }
}

echo Buyurtma::MAKS_MAHSULOT;   // 50

Trait — kodni gorizontal bo'lishish

Trait — bir nechta classga "nusxalab qo'yiladigan" metodlar to'plami:

<?php

trait VaqtBelgilari
{
    private ?DateTimeImmutable $yaratilgan = null;
    private ?DateTimeImmutable $yangilangan = null;

    public function yaratilganDebBelgila(): void
    {
        $this->yaratilgan = new DateTimeImmutable();
        $this->yangilangan = $this->yaratilgan;
    }

    public function yangilanganDebBelgila(): void
    {
        $this->yangilangan = new DateTimeImmutable();
    }

    public function yaratilgan(): ?DateTimeImmutable
    {
        return $this->yaratilgan;
    }
}

class Mahsulot
{
    use VaqtBelgilari;

    public function __construct(public string $nom) {}
}

class Maqola
{
    use VaqtBelgilari;

    public function __construct(public string $sarlavha) {}
}

$m = new Mahsulot("Telefon");
$m->yaratilganDebBelgila();
echo $m->yaratilgan()->format('d.m.Y');

use VaqtBelgilari; — PHP trait ichidagi hamma narsani shu classga ko'chirib qo'yadi. Meros emas — Mahsulot va Maqola bir-biri bilan bog'liq emas.

Bir nechta trait:

class Maqola
{
    use VaqtBelgilari, SoftDelete, Sluggable;
}

Laravel da bu juda ko'p: use HasFactory, SoftDeletes, Notifiable;.

[!INFO] Trait — kuchli, lekin "yashirin" kod: classni o'qiyotganda metodlar qayerdan kelganini ko'rish uchun trait faylini ochish kerak. Haqiqatan ham ko'p bog'liq bo'lmagan classlarda takrorlanadigan kichik mantiq uchun ishlating.


Enum — cheklangan qiymatlar to'plami (PHP 8.1+)

Muammodagi buyurtma holatlari:

<?php

enum BuyurtmaHolati: string
{
    case Yangi = 'new';
    case Tolangan = 'paid';
    case Yolda = 'shipped';
    case Yetkazildi = 'delivered';
    case Bekor = 'cancelled';
}

Endi holat — oddiy string emas, maxsus tur:

<?php

class Buyurtma
{
    public function __construct(
        public readonly int $id,
        public BuyurtmaHolati $holat = BuyurtmaHolati::Yangi,
    ) {}
}

$b = new Buyurtma(1043);
$b->holat = BuyurtmaHolati::Tolangan;   // ✅
$b->holat = 'payed';                    // ❌ TypeError — xato yozish imkonsiz

Bazadan / formadan kelgan qiymatni o'girish

<?php

$holat = BuyurtmaHolati::from('paid');        // BuyurtmaHolati::Tolangan
$holat = BuyurtmaHolati::from('payed');       // ValueError — noto'g'ri qiymat
$holat = BuyurtmaHolati::tryFrom('payed');    // null — xato bermaydi

echo BuyurtmaHolati::Tolangan->value;   // paid — bazaga yozish uchun
echo BuyurtmaHolati::Tolangan->name;    // Tolangan

Enum ichida metodlar

<?php

enum BuyurtmaHolati: string
{
    case Yangi = 'new';
    case Tolangan = 'paid';
    case Yolda = 'shipped';
    case Yetkazildi = 'delivered';
    case Bekor = 'cancelled';

    public function matn(): string
    {
        return match ($this) {
            self::Yangi      => "Yangi",
            self::Tolangan   => "To'langan",
            self::Yolda      => "Yo'lda",
            self::Yetkazildi => "Yetkazildi",
            self::Bekor      => "Bekor qilingan",
        };
    }

    public function rang(): string
    {
        return match ($this) {
            self::Yetkazildi => 'yashil',
            self::Bekor      => 'qizil',
            default          => 'kulrang',
        };
    }

    public function bekorQilishMumkinmi(): bool
    {
        return in_array($this, [self::Yangi, self::Tolangan], true);
    }
}

$h = BuyurtmaHolati::from('shipped');
echo $h->matn();                          // Yo'lda
var_dump($h->bekorQilishMumkinmi());      // bool(false)

// Select uchun barcha variantlar
foreach (BuyurtmaHolati::cases() as $holat) {
    echo "<option value='{$holat->value}'>{$holat->matn()}</option>";
}

match ichida default yozilmasa va yangi holat qo'shilsa — PHP UnhandledMatchError beradi. Biror joyni yangilashni unutmaysiz.


Nima uchun shunday?

Nima uchun enum kerak edi, agar class konstantalari (const YANGI = 'new') bilan ham shunday qilsa bo'lardi?

Konstanta — baribir oddiy string. function holatniOzgartir(string $holat) ga Buyurtma::YANGI ham, 'salom' ham berish mumkin — PHP farqlamaydi. Enum esa alohida tur: function holatniOzgartir(BuyurtmaHolati $holat) faqat beshta ruxsat etilgan qiymatdan birini qabul qiladi. Xato dastur ishga tushganda emas, kod yozilayotganda (muharrirda) ko'rinadi.


Xulosa

Eslab qoling:

  • static — ob'ektga emas, classga tegishli; Klass::metod(), ichida $this yo'q
  • Named constructor: Pul::somdan(5000) — new dan aniqroq
  • const — class konstantasi, self::NOM
  • trait + use — bog'liq bo'lmagan classlar orasida kod bo'lishish
  • enum — cheklangan qiymatlar uchun alohida tur; from, tryFrom, cases(), ->value
  • Enum ichida metod yozish mumkin — matn, rang, qoidalar

Keyingi dars

33-dars: Namespace va autoload