12-bo‘lim

OOP asoslari

Sinf va obyekt, konstruktor xossalarini ko'chirish, kirish darajalari, readonly va statik a'zolar.

🕑 12 daqiqa o‘qish 📄 773 so‘z 👁 1 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Massivdan obyektga
  2. readonly xossalar
  3. Kirish darajalari amalda
  4. Statik a'zolar
  5. Sehrli usullar
  6. Xulosa

Massivlar bilan ishlash oson, lekin ular hech narsani kafolatlamaydi. $mahsulot["narx"] bo'sh bo'lishi, $mahsulot["narxi"] deb xato yozilishi mumkin va kod faqat ish paytida yiqiladi.

Sinf bu muammoni hal qiladi.

Massivdan obyektga #

PHP
// Har bir misol qat'iy turlar rejimida ishlaydi.
PHP
// Massiv bilan - hech qanday kafolat yo'q
$massiv = ["nom" => "Klaviatura", "narx" => 350000];
echo "Massiv: {$massiv['nom']}\n";
echo "Xato yozilgan kalit: " . ($massiv["narxi"] ?? "(yo'q - xato sezilmadi)") . "\n";

echo "\n";

// Sinf bilan - tuzilma majburiy
final class Mahsulot
{
    public function __construct(
        public readonly string $nom,
        public readonly int $narxTiyin,
        public readonly int $soni = 0,
    ) {
        if ($nom === "") {
            throw new InvalidArgumentException("Nom bo'sh bo'lmasin");
        }
        if ($narxTiyin < 0) {
            throw new InvalidArgumentException("Narx manfiy bo'lmasin");
        }
    }

    public function narxMatni(): string
    {
        return number_format($this->narxTiyin / 100, 2, ",", " ") . " so'm";
    }

    public function omborda(): bool
    {
        return $this->soni > 0;
    }

    public function jamiQiymat(): int
    {
        return $this->narxTiyin * $this->soni;
    }
}

$m = new Mahsulot("Klaviatura", 35000000, 12);

echo "Obyekt: {$m->nom}\n";
echo "Narxi:  {$m->narxMatni()}\n";
echo "Omborda: " . ($m->omborda() ? "ha" : "yo'q") . "\n";
echo "Jami:   " . number_format($m->jamiQiymat() / 100, 2, ",", " ") . " so'm\n";

echo "\n";
try {
    new Mahsulot("", 1000);
} catch (InvalidArgumentException $x) {
    echo "Konstruktor rad etdi: " . $x->getMessage() . "\n";
}
Natija
Massiv: Klaviatura
Xato yozilgan kalit: (yo'q - xato sezilmadi)

Obyekt: Klaviatura
Narxi:  350 000,00 so'm
Omborda: ha
Jami:   4 200 000,00 so'm

Konstruktor rad etdi: Nom bo'sh bo'lmasin
Konstruktor xossalarini ko'chirish (PHP 8)

PHP 8 gacha bir xil narsani uch marta yozish kerak edi:

PHP
class Mahsulot {
    private string $nom;          // 1. e'lon
    private int $narx;

    public function __construct(string $nom, int $narx) {   // 2. parametr
        $this->nom = $nom;        // 3. tayinlash
        $this->narx = $narx;
    }
}

PHP 8 da esa bir marta:

PHP
class Mahsulot {
    public function __construct(
        private string $nom,
        private int $narx,
    ) {}
}

Kirish darajasi (public, private, protected) yoki readonly yozilishi bilan parametr avtomatik xossaga aylanadi.

Diqqat: konstruktor tanasi kerak bo'lsa, u xossalar tayinlangandan keyin ishlaydi - shuning uchun yuqoridagi tekshiruvlar to'g'ri joyda turibdi.

Oxirgi parametrdan keyin vergul qo'yish mumkin - bu git tarixini toza saqlaydi.

readonly xossalar #

PHP
final class Buyurtma
{
    public function __construct(
        public readonly int $id,
        public readonly string $mijoz,
        public readonly array $mahsulotlar,
        private string $holat = "yangi",
    ) {}

    public function holat(): string
    {
        return $this->holat;
    }

    // readonly bo'lmagan xossani o'zgartirish mumkin
    public function holatniOzgartir(string $yangi): void
    {
        $ruxsat = ["yangi", "tasdiqlangan", "yuborilgan", "yakunlangan", "bekor"];
        if (!in_array($yangi, $ruxsat, true)) {
            throw new InvalidArgumentException("Noma'lum holat: {$yangi}");
        }
        $this->holat = $yangi;
    }
}

$b = new Buyurtma(101, "Malika", ["Klaviatura", "Sichqoncha"]);

echo "Buyurtma #{$b->id}\n";
echo "Mijoz:    {$b->mijoz}\n";
echo "Holat:    {$b->holat()}\n";

$b->holatniOzgartir("tasdiqlangan");
echo "Yangi holat: {$b->holat()}\n";

echo "\n";
// readonly xossani o'zgartirishga urinish
try {
    $b->id = 999;
} catch (Error $x) {
    echo "readonly himoyasi ishladi:\n";
    echo "  " . $x->getMessage() . "\n";
}

echo "\n";
try {
    $b->holatniOzgartir("qandaydir");
} catch (InvalidArgumentException $x) {
    echo "Noto'g'ri holat rad etildi: " . $x->getMessage() . "\n";
}
Natija
Buyurtma #101
Mijoz:    Malika
Holat:    yangi
Yangi holat: tasdiqlangan

readonly himoyasi ishladi:
  Cannot modify readonly property Buyurtma::$id

Noto'g'ri holat rad etildi: Noma'lum holat: qandaydir
readonly faqat bir marta yozishga ruxsat beradi

readonly xossa konstruktorda (yoki sinf ichida) bir marta qiymat oladi va keyin o'zgarmaydi.

Bu nimani beradi:

FoydaIzoh
Obyekt holati bashorat qilinadiYaratilgandan keyin o'zgarmaydi
Ko'p oqimli xatolar yo'qO'zgarmas qiymat xavfsiz
Nusxa olish shart emasKimdir o'zgartira olmaydi

Ikki muhim cheklov:

1. Faqat tur e'lon qilingan xossaga qo'llanadi. public readonly $x; xato - tur yozilishi shart.

2. Massiv ichini o'zgartirib bo'lmaydi, lekin uning elementlari o'zgarmas bo'lishi shart emas. Ya'ni readonly array $mahsulotlar massivning o'zini almashtirishdan saqlaydi, lekin ichidagi obyektlar o'zgaruvchan bo'lishi mumkin.

PHP 8.2 da readonly class ham qo'shildi - u butun sinfning barcha xossalarini readonly qiladi:

PHP
final readonly class Nuqta {
    public function __construct(
        public int $x,
        public int $y,
    ) {}
}

Qiymat obyektlari (value object) uchun bu ideal.

Kirish darajalari - kim nimani ko'radi public Hamma joydan • Sinf ichidan • Vorislardan • Tashqaridan Bu sizning INTERFEYSINGIZ protected Sinf va vorislar • Sinf ichidan • Vorislardan • Tashqaridan - YO'Q Vorislik uchun mo'ljallangan private Faqat shu sinf • Sinf ichidan • Vorislardan - YO'Q • Tashqaridan - YO'Q STANDART TANLOV Qoida: eng tor darajadan boshlang Hamma narsani private qiling. Kerak bo'lgandagina kengaytiring. Torni kengaytirish oson, kengni toraytirish esa chaqiruvchi kodni buzadi. Zamonaviy yondashuv: public readonly O'qish uchun ochiq, yozish uchun yopiq - getNom() kabi funksiyalar kerak emas. Qiymat obyektlari uchun eng toza yechim.
Kirish darajasi - bu ruxsat emas, niyatni bildirish

Kirish darajalari amalda #

PHP
class Hisob
{
    private int $balansTiyin = 0;
    private array $tarix = [];

    public function __construct(
        public readonly string $egasi,
        int $boshlangichTiyin = 0,
    ) {
        if ($boshlangichTiyin > 0) {
            $this->kirim($boshlangichTiyin, "boshlang'ich");
        }
    }

    public function kirim(int $tiyin, string $izoh = ""): void
    {
        if ($tiyin <= 0) {
            throw new InvalidArgumentException("Summa musbat bo'lsin");
        }
        $this->balansTiyin += $tiyin;
        $this->yoz("+", $tiyin, $izoh);
    }

    public function chiqim(int $tiyin, string $izoh = ""): bool
    {
        if ($tiyin <= 0) {
            throw new InvalidArgumentException("Summa musbat bo'lsin");
        }
        if ($tiyin > $this->balansTiyin) {
            return false;
        }
        $this->balansTiyin -= $tiyin;
        $this->yoz("-", $tiyin, $izoh);
        return true;
    }

    public function balans(): int
    {
        return $this->balansTiyin;
    }

    public function tarix(): array
    {
        return $this->tarix;       // NUSXA qaytariladi
    }

    // Faqat shu sinf ishlata oladi
    private function yoz(string $belgi, int $tiyin, string $izoh): void
    {
        $this->tarix[] = sprintf("%s%s %s", $belgi,
            number_format($tiyin / 100, 2, ",", " "), $izoh);
    }
}

$h = new Hisob("Husanboy Qodirov", 50000000);
$h->kirim(25000000, "oylik");
$ok = $h->chiqim(30000000, "ijara");
$yoq = $h->chiqim(99900000, "avtomobil");

printf("Egasi:  %s\n", $h->egasi);
printf("Balans: %s so'm\n", number_format($h->balans() / 100, 2, ",", " "));
printf("Katta chiqim amalga oshdimi: %s\n", $yoq ? "ha" : "yo'q");

echo "\nTarix:\n";
foreach ($h->tarix() as $q) {
    echo "  {$q}\n";
}

echo "\n";
// private xossaga tashqaridan murojaat
try {
    echo $h->balansTiyin;
} catch (Error $x) {
    echo "private himoyasi: " . substr($x->getMessage(), 0, 52) . "\n";
}
Natija
Egasi:  Husanboy Qodirov
Balans: 450 000,00 so'm
Katta chiqim amalga oshdimi: yo'q

Tarix:
  +500 000,00 boshlang'ich
  +250 000,00 oylik
  -300 000,00 ijara

private himoyasi: Cannot access private property Hisob::$balansTiyin
Massivni qaytarganda nusxa qaytariladi - obyektni esa yo'q

tarix() usuli $this->tarix massivini qaytaradi. PHP da massiv qiymat bo'yicha uzatiladi, ya'ni chaqiruvchi nusxani oladi va uni o'zgartirsa, obyekt ichidagi massiv o'zgarmaydi.

Lekin obyektlar uchun bu ishlamaydi:

PHP
private array $mahsulotlar = [];   // ichida Mahsulot obyektlari

public function mahsulotlar(): array {
    return $this->mahsulotlar;     // massiv nusxa, OBYEKTLAR esa havola
}

Chaqiruvchi massiv nusxasini oladi, lekin undagi obyektlar o'shalar - ularni o'zgartirsa, sizning obyektingiz ichida ham o'zgaradi.

Yechimlar:

UsulIzoh
Obyektlarni readonly qilishEng oddiy va tavsiya etiladi
Chuqur nusxa (__clone)Qimmat, lekin ishonchli
readonly class (PHP 8.2)Butun sinf o'zgarmas

Amaliy tavsiya: ichki holatni qaytaradigan usullar o'zgarmas qiymatlar qaytarsin. Shunda bu muammo umuman paydo bo'lmaydi.

Statik a'zolar #

PHP
final class Hisoblagich
{
    private static int $yaratilgan = 0;
    private static array $royxat = [];

    public const ENG_KOP = 3;

    public function __construct(public readonly string $nom)
    {
        if (self::$yaratilgan >= self::ENG_KOP) {
            throw new RuntimeException(
                "Eng ko'pi " . self::ENG_KOP . " ta yaratish mumkin"
            );
        }
        self::$yaratilgan++;
        self::$royxat[] = $nom;
    }

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

    public static function royxat(): array
    {
        return self::$royxat;
    }

    // Nomli konstruktor - statik usulning eng foydali qo'llanishi
    public static function mehmon(): self
    {
        return new self("mehmon-" . (self::$yaratilgan + 1));
    }
}

echo "Boshlang'ich soni: " . Hisoblagich::soni() . "\n";
echo "Chegara (doimiy):  " . Hisoblagich::ENG_KOP . "\n\n";

$a = new Hisoblagich("birinchi");
$b = Hisoblagich::mehmon();
$c = new Hisoblagich("uchinchi");

echo "Yaratilganlar: " . implode(", ", Hisoblagich::royxat()) . "\n";
echo "Soni: " . Hisoblagich::soni() . "\n\n";

try {
    new Hisoblagich("tortinchi");
} catch (RuntimeException $x) {
    echo "Chegara ishladi: " . $x->getMessage() . "\n";
}
Natija
Boshlang'ich soni: 0
Chegara (doimiy):  3

Yaratilganlar: birinchi, mehmon-2, uchinchi
Soni: 3

Chegara ishladi: Eng ko'pi 3 ta yaratish mumkin
self, static va $this farqi
Kalit so'zNimaga ishora qiladi
$thisJoriy obyekt namunasi
selfYozilgan sinf
staticChaqirilgan sinf (kech bog'lanish)

Farq vorislikda ko'rinadi:

PHP
class Asos {
    public static function yarat(): static {
        return new static();     // VORIS sinfni yaratadi
    }
    public static function yaratSelf(): self {
        return new self();       // har doim Asos yaratadi
    }
}
class Voris extends Asos {}

Voris::yarat();       // Voris obyekti
Voris::yaratSelf();   // Asos obyekti

Nomli konstruktorlar uchun deyarli har doim static kerak - shunda voris sinflarda ham to'g'ri ishlaydi.

Statik holatdan esa ehtiyot bo'ling: u global o'zgaruvchi bilan bir xil muammolarga ega. Sinovlarda bir test ikkinchisiga ta'sir qiladi, chunki statik qiymat so'rovlar orasida saqlanib qoladi.

Statik usullarni asosan nomli konstruktor va toza yordamchi funksiyalar uchun ishlating.

Sehrli usullar #

PHP
final class Sozlama
{
    private array $qiymatlar = [];

    public function __construct(array $boshlangich = [])
    {
        $this->qiymatlar = $boshlangich;
    }

    public function __get(string $nom): mixed
    {
        return $this->qiymatlar[$nom] ?? null;
    }

    public function __set(string $nom, mixed $qiymat): void
    {
        $this->qiymatlar[$nom] = $qiymat;
    }

    public function __isset(string $nom): bool
    {
        return isset($this->qiymatlar[$nom]);
    }

    public function __toString(): string
    {
        $qismlar = [];
        foreach ($this->qiymatlar as $k => $v) {
            $qismlar[] = $k . "=" . (is_bool($v) ? ($v ? "true" : "false") : (string) $v);
        }
        return implode(", ", $qismlar);
    }
}

$s = new Sozlama(["til" => "uz", "mavzu" => "tungi"]);

echo "__get:      " . $s->til . "\n";
echo "Yo'q kalit: " . var_export($s->rang, true) . "\n";

$s->hajm = 14;
echo "__set dan keyin hajm: {$s->hajm}\n";

echo "\n";
echo "__isset('til'):  " . (isset($s->til) ? "ha" : "yo'q") . "\n";
echo "__isset('rang'): " . (isset($s->rang) ? "ha" : "yo'q") . "\n";

echo "\n";
echo "__toString: {$s}\n";
Natija
__get:      uz
Yo'q kalit: NULL
__set dan keyin hajm: 14

__isset('til'):  ha
__isset('rang'): yo'q

__toString: til=uz, mavzu=tungi, hajm=14
Sehrli usullardan kam foydalaning

__get va __set kodni qisqartiradi, lekin uch narsani buzadi:

Nima buziladiSabab
Avtoto'ldirishMuharrir qanday xossalar borligini bilmaydi
Statik tahlilXato yozilgan nom topilmaydi
TezlikOddiy xossaga murojaatdan sekinroq

Eng yomoni birinchisi: $s->tli deb xato yozsangiz, kod xato bermaydi - null qaytaradi va muammo uzoqda ko'rinadi.

Qachon o'rinli:

O'rinliO'rinsiz
Sozlama konteyneriOddiy ma'lumot sinfi
ORM (bazadan kelgan maydonlar)Xossalar oldindan ma'lum bo'lsa
Proksi va dekoratorFaqat "kam yozish" uchun

__toString esa boshqacha - u foydali va xavfsiz. Faqat bitta qoida: u hech qachon istisno tashlamasin va har doim satr qaytarsin.

Amaliy topshiriq
  1. Massiv bilan ishlovchi kodni sinfga aylantiring.
  2. Konstruktorda tekshiruv qo'ying va noto'g'ri qiymat bering.
  3. Konstruktor xossalarini ko'chirish sintaksisini qo'llang.
  4. readonly xossani o'zgartirishga urinib ko'ring.
  5. private xossaga tashqaridan murojaat qiling.
  6. Massiv qaytaruvchi usul yozing va natijani o'zgartiring - asli o'zgardimi?
  7. Statik hisoblagich bilan yaratilgan obyektlar sonini sanang.
  8. Nomli konstruktor (static function yarat()) yozing.
  9. self va static farqini vorislikda sinab ko'ring.
  10. __get bilan xossa yozing va nomini xato yozib ko'ring.

Xulosa #

  • Massiv tuzilmani kafolatlamaydi - xato yozilgan kalit jimgina null beradi.
  • Sinf konstruktorda invariantni tekshiradi: noto'g'ri obyekt umuman yaratilmaydi.
  • Konstruktor xossalarini ko'chirish (PHP 8) uch marta yozishni bir martaga tushiradi.
  • readonly xossa bir marta yoziladi va keyin o'zgarmaydi.
  • readonly faqat tur e'lon qilingan xossaga qo'llanadi.
  • PHP 8.2 da readonly class butun sinfni o'zgarmas qiladi.
  • Kirish darajasini eng tordan boshlang - kengaytirish oson, toraytirish esa buzadi.
  • Massiv qiymat bo'yicha qaytariladi, lekin ichidagi obyektlar havola.
  • self - yozilgan sinf, static - chaqirilgan sinf; nomli konstruktorda static ishlating.
  • Statik holat global o'zgaruvchi bilan bir xil muammolarga ega - ehtiyot bo'ling.
  • __get/__set avtoto'ldirish va statik tahlilni buzadi - kam ishlating.

Keyingi bo'limda vorislik, interfeys va trait ni ko'ramiz.

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.