07 β Property hooks va asymmetric visibility (8.4)¶
β¬ οΈ Oldingi: 06 β readonly, Value Object va variance Β· π README Β· Keyingi: 08 β Reflection, attributes va FFI β‘οΈ
Bu bobda: PHP 8.4 ning ikkita eng yangi va eng kutilgan imkoniyatini β property hooks va asymmetric visibility ni chuqur o'rganamiz. Property hook nima:
getvasetmantiqini propertyning o'ziga osib qo'yish β endigetName()/setName()boilerplate kerak emas. Virtual property β backing-field'siz, faqat hisoblanadigan xususiyat (masalan$fullName,$domen). Asymmetric visibility (public private(set)) β tashqaridan o'qiladi, lekin faqat sinf ichidan yoziladi. Bularning hammasi ayni shu kompyuterda PHP 8.4.0 da haqiqatan ishlaydi β har bir misolphpbilan ishga tushirilib tasdiqlangan, taqiqlangan urinishlar esa// βbilan belgilangan. Bu bob boshlovchi kitobdagi kirish darajalari va class va obyekt boblariga, hamda 06 β readonly va Value Object ga tayanadi.
Muammo: getter/setter boilerplate¶
Boshlovchi kitobda kirish darajalari bilan tanishgansiz: propertyni private qilamiz, tashqi kod unga to'g'ridan-to'g'ri tegmasligi uchun, va public getter/setter metodlar orqali boshqaramiz. Bu klassik inkapsulyatsiya. Lekin bahosi bor β boilerplate (qayta-qayta yoziladigan, mazmunsiz kod):
<?php
declare(strict_types=1);
// ESKI uslub: har property uchun getter + setter
final class EskiUser
{
private string $name = '';
public function getName(): string
{
return $this->name;
}
public function setName(string $name): void
{
$this->name = ucfirst(trim($name)); // mantiq shu yerda
}
}
$e = new EskiUser();
$e->setName(' oqil ');
echo $e->getName(), PHP_EOL; // Oqil
Bitta name property uchun 8 qator. O'nta property bo'lsa β 80 qator deyarli bir xil kod. Va eng yomoni: chaqirayotgan tomon $e->getName() va $e->setName(...) deb yozishga majbur, garchi konseptual jihatdan bu shunchaki "name" xususiyati.
PHP 8.4 gacha yagona muqobil β __get/__set sehrli (magic) metodlar edi. Lekin ular butun obyekt uchun bitta "qora quti": IDE ularni avtot'ldira olmaydi, Reflection ko'rmaydi, va ular sekin. Bu kamchiliklarni pastda batafsil ko'ramiz.
Property hooks aynan shu muammoni hal qiladi: mantiqni metodga emas, propertyning o'ziga osasiz.
<?php
declare(strict_types=1);
// YANGI uslub: hook propertyga "osilgan"
final class YangiUser
{
public string $name = '' {
set(string $value) {
$this->name = ucfirst(trim($value));
}
}
}
$y = new YangiUser();
$y->name = ' oqil '; // setName() EMAS - oddiy tayinlash, lekin hook ishlaydi
echo $y->name, PHP_EOL; // Oqil - getName() EMAS - oddiy o'qish
Chaqiruvchi tomon uchun $y->name β oddiy public property kabi ko'rinadi. Lekin orqada set hook har bir tayinlashda ucfirst(trim(...)) ni bajaradi. Inkapsulyatsiya saqlandi, boilerplate yo'qoldi.
Eslatma: bu bob mantiqni qayta o'rgatmaydi β
private/publicfarqini boshlovchi kitobdan (16-bob) bilasiz deb taxmin qiladi. Bu yerda PHP 8.4 ning yangi mexanizmlari ga e'tibor qaratamiz.
Property hook nima va u qanday ishlaydi¶
Property hook β bu propertyga biriktirilgan get va/yoki set "ilmoq"lari. Tashqi kod propertyni o'qiganda get hook chaqiriladi, yozganda set hook chaqiriladi. Quyidagi diagramma oqimni ko'rsatadi:
E'tibor bering: set hook $this->property ga yozadi β bu backing field (haqiqiy saqlash joyi). Hook ichida $this->email = $value deganda biz cheksiz rekursiyaga tushmaymiz: PHP backing fieldga to'g'ridan-to'g'ri yozayotganini biladi, hookni qayta chaqirmaydi.
Qisqa sintaksis (arrow) va blok sintaksis¶
get hook ikki ko'rinishda yoziladi. Qisqa (arrow) sintaksis β bitta ifoda qaytaradigan oddiy holatlar uchun:
<?php
declare(strict_types=1);
class Doira
{
public function __construct(public float $radius) {}
// Qisqa sintaksis: get => <ifoda>
public float $yuza {
get => M_PI * $this->radius ** 2;
}
}
$d = new Doira(2.0);
printf("Yuza: %.4f%s", $d->yuza, PHP_EOL); // Yuza: 12.5664
Blok sintaksis β bir nechta amal yoki set uchun (validatsiya, normalizatsiya):
<?php
declare(strict_types=1);
class Temperatura
{
// Blok sintaksis: set { ... }
public float $selsiy = 0.0 {
set(float $value) {
if ($value < -273.15) {
throw new \InvalidArgumentException('Absolyut noldan past');
}
$this->selsiy = $value; // backing fieldga yozish
}
}
public float $farengeyt {
get => $this->selsiy * 9 / 5 + 32; // qisqa get
}
}
$t = new Temperatura();
$t->selsiy = 25.0;
echo $t->farengeyt, PHP_EOL; // 77
// β set hook validatsiyasi: mumkin emas
try {
$t->selsiy = -300;
} catch (\InvalidArgumentException $e) {
echo "Xato: ", $e->getMessage(), PHP_EOL; // Xato: Absolyut noldan past
}
Ishga tushirsangiz aynan shu chiqadi:
$valueqayerdan keldi?sethookdaset(float $value)deb parametr e'lon qilasiz β bu propertyga tayinlanayotgan yangi qiymat. Nomi ixtiyoriy ($value,$v,$narxβ farqi yo'q), turi esa propertyning turiga mos kelishi shart. Agarset { ... }deb parametrsiz yozsangiz, PHP avtomatik$valuenomini beradi.
get va set ni birga ishlatish¶
Bitta property ikkala hookga ham ega bo'lishi mumkin. Bu β private property + getter + setter ning eng zich ko'rinishi:
<?php
declare(strict_types=1);
class Pul
{
// Ichkarida tiyin sa, tashqarida so'm ko'rinadi
private int $tiyin = 0;
public float $som {
get => $this->tiyin / 100;
set(float $value) {
$this->tiyin = (int) round($value * 100);
}
}
}
$p = new Pul();
$p->som = 19.99;
echo $p->som, PHP_EOL; // 19.99 (ichkarida 1999 tiyin saqlanadi)
Bu yerda $som β alohida backing field emas: u $tiyin ustidan ishlaydi. O'qiganda tiyindan so'mga, yozganda so'mdan tiyinga aylantiradi. Tashqi kod faqat so'mni ko'radi, ichki aniqlik (butun tiyin) yashirin.
Virtual property: backing-field'siz hisoblanadigan xususiyat¶
Yuqoridagi $farengeyt va $yuza β bular virtual property. Ularning o'z saqlash joyi yo'q: ular har safar boshqa propertydan hisoblanadi. Faqat get hooki bor, set hooki yo'q.
Klassik misol β $fullName:
<?php
declare(strict_types=1);
class Person
{
public function __construct(
public string $first,
public string $last,
) {}
// Virtual: hech qaerda saqlanmaydi, har o'qishda hisoblanadi
public string $fullName {
get => trim("$this->first $this->last");
}
}
$p = new Person('Oqil', 'Imomnazarov');
echo $p->fullName, PHP_EOL; // Oqil Imomnazarov
$p->first = 'Laylo';
echo $p->fullName, PHP_EOL; // Laylo Imomnazarov - avtomatik yangilandi
$fullName hech qachon "eskirmaydi": $first o'zgarsa, keyingi o'qishda yangi qiymat chiqadi. Agar buni oddiy property qilsangiz (public string $fullName = '...'), uni qo'lda sinxron saqlashga majbur bo'lardingiz β xato manbai.
Virtual ekanini qanday biladi PHP? Agar hook ichida $this->propertyNomi ga to'g'ridan-to'g'ri murojaat bo'lmasa (ya'ni backing field ishlatilmasa), PHP propertyni virtual deb hisoblaydi va unga xotira ajratmaydi. Buni Reflection bilan tekshirsa bo'ladi (8-bobda batafsil):
<?php
declare(strict_types=1);
class Doira
{
public function __construct(public float $radius) {}
public float $yuza {
get => M_PI * $this->radius ** 2;
}
}
$rp = new \ReflectionProperty(Doira::class, 'yuza');
var_dump($rp->isVirtual()); // bool(true)
var_dump($rp->hasHooks()); // bool(true)
β Virtual propertyga yozib bo'lmaydi.
$fullNamefaqatgetga ega β unga tayinlash urinishi Error tashlaydi:Agar virtual propertyga ham yozish kerak bo'lsa (masalan
$fullName = "Ali Vali"ni$first/$lastga ajratish), ungasethook qo'shasiz β quyida "ko'p birlik" mashqida shuni quramiz.
set hookda validatsiya va normalizatsiya¶
set hookning eng kuchli foydasi β yaroqsiz holatni obyektga umuman kirita olmaslik. Quyida email normalizatsiya (kichik harf, bo'shliqlarni olib tashlash) va validatsiya birga:
<?php
declare(strict_types=1);
class Foydalanuvchi
{
public string $email = '' {
set(string $value) {
$value = strtolower(trim($value)); // normalizatsiya
if (! str_contains($value, '@')) { // validatsiya
throw new \InvalidArgumentException("Yaroqsiz email: {$value}");
}
$this->email = $value;
}
}
}
$u = new Foydalanuvchi();
$u->email = ' OQIL@Misol.UZ ';
echo $u->email, PHP_EOL; // oqil@misol.uz - tozalangan
// β Yaroqsiz email obyektga kira olmaydi
try {
$u->email = 'xato';
} catch (\InvalidArgumentException $e) {
echo $e->getMessage(), PHP_EOL; // Yaroqsiz email: xato
}
Endi $u->email ni har kim o'qiy oladi, lekin u doim to'g'ri formatda β chunki yagona kirish nuqtasi set hookdan o'tadi. Bu β inkapsulyatsiyaning asl maqsadi: obyekt hech qachon yaroqsiz holatga tushmaydi (xatolarni boshqarish haqida ko'proq: 23-bob).
Hook konstruktor promotion bilan¶
Hooklar konstruktor promotion bilan ham ishlaydi β set validatsiyasi obyekt yaratilganda ham qo'llanadi:
<?php
declare(strict_types=1);
class Mahsulot
{
public function __construct(
public string $nom,
public int $narx = 0 {
set(int $value) {
$this->narx = max(0, $value); // manfiy narx -> 0
}
},
) {}
}
$m = new Mahsulot('Kitob', -50);
echo $m->narx, PHP_EOL; // 0 - konstruktordagi qiymat ham hookdan o'tdi
Hooks interfeysda ham e'lon qilinishi mumkin¶
Interfeys odatda metodlarni talab qiladi. PHP 8.4 da interfeys property hook ham talab qila oladi β ya'ni "bu interfeysni amalga oshirgan har bir sinfda o'qiladigan fullName property bo'lsin" deyish mumkin:
<?php
declare(strict_types=1);
interface HasFullName
{
// Interfeys: "get qilinadigan fullName property bo'lishi shart"
public string $fullName { get; }
}
class Person implements HasFullName
{
public function __construct(
public string $first,
public string $last,
) {}
// Shartni virtual property bilan bajaramiz
public string $fullName {
get => trim("$this->first $this->last");
}
}
$p = new Person('Oqil', 'Imomnazarov');
echo $p->fullName, PHP_EOL; // Oqil Imomnazarov
Interfeysda { get; } β "o'qiladigan property kerak" demak; { get; set; } β "o'qiladigan va yoziladigan property kerak". Bu kontrakt sinfni qanday amalga oshirishga majburlamaydi: kimdir oddiy public property, kimdir virtual hook, kimdir backing field bilan bajarishi mumkin. Muhimi β tashqi shartnoma bajariladi. Bu enum va interfeyslar bilan birga (22-bob) kuchli mavhumlashtirish (abstraction) imkonini beradi.
Nega bu muhim? Avval interfeys faqat
getFullName(): stringmetodini talab qila olardi β ya'ni siz amalga oshirish detalini (metod ekanini) shartnomaga tiqib qo'yardingiz. Endi shartnoma "o'qiladiganstring $fullNameqiymati" deydi β qanday hosil bo'lishi sinfning ichki ishi. Bu toza dizayn.
Asymmetric visibility: o'qish ochiq, yozish yopiq¶
Ikkinchi katta yangilik β asimmetrik ko'rinish (asymmetric visibility). Property uchun o'qish va yozish kirish darajalarini alohida belgilash mumkin:
public private(set) int $balans; // o'qish: public, yozish: private
public protected(set) string $holat; // o'qish: public, yozish: protected
public private(set) ni o'qing: "o'qish uchun public, yozish (set) uchun private". Ya'ni tashqi kod propertyni ko'ra oladi, lekin o'zgartira olmaydi β o'zgartirish faqat sinf ichidan mumkin.
<?php
declare(strict_types=1);
final class Hisob
{
// Tashqaridan o'qiladi, faqat ichkaridan yoziladi
public private(set) int $balans = 0;
public function depozit(int $miqdor): void
{
$this->balans += $miqdor; // ICHKARIDA yozish - OK
}
}
$h = new Hisob();
$h->depozit(100);
echo $h->balans, PHP_EOL; // 100 - tashqaridan o'qish OK
// β Tashqaridan yozish - Error
try {
$h->balans = 999;
} catch (\Error $e) {
echo "Error: ", $e->getMessage(), PHP_EOL;
}
Chiqishi:
Avval bunga erishish uchun: property private qilib, qo'lga getBalans() getter yozardingiz va setterni umuman bermasdingiz. Endi bitta public private(set) shu maqsadni ifodalaydi β ortiqcha kod yo'q.
protected(set) esa subklassga ham yozishga ruxsat beradi:
<?php
declare(strict_types=1);
class Asos
{
public protected(set) string $holat = 'yangi';
}
class Buyurtma extends Asos
{
public function yubor(): void
{
$this->holat = 'yuborildi'; // subklass yoza oladi (protected)
}
}
$b = new Buyurtma();
$b->yubor();
echo $b->holat, PHP_EOL; // yuborildi
readonly bilan farqi: bir marta emas, ko'p marta β lekin faqat ichkaridan¶
06-bob da readonly property bilan ishlagansiz. readonly va private(set) ni adashtirmaslik muhim β ular boshqa cheklovlarni qo'yadi:
| Xususiyat | readonly |
public private(set) |
|---|---|---|
| Tashqaridan o'qish | β ochiq | β ochiq |
| Tashqaridan yozish | β yo'q | β yo'q |
| Ichkaridan yozish | faqat bir marta (initsializatsiya) | ko'p marta |
| Maqsad | o'zgarmas (immutable) qiymat | tashqaridan himoyalangan, ichkaridan boshqariladigan holat |
<?php
declare(strict_types=1);
// readonly: faqat BIR marta, hatto sinf ichidan ham
class RO
{
public function __construct(public readonly int $x) {}
public function ozgartir(): void
{
$this->x = 5; // β ikkinchi marta yozib bo'lmaydi
}
}
$r = new RO(1);
try {
$r->ozgartir();
} catch (\Error $e) {
echo "readonly: ", $e->getMessage(), PHP_EOL;
}
// private(set): KO'P marta, lekin faqat ichkaridan
class Counter
{
public private(set) int $soni = 0;
public function inc(): void { $this->soni++; } // ko'p marta - OK
}
$c = new Counter();
$c->inc();
$c->inc();
$c->inc();
echo "private(set) soni: ", $c->soni, PHP_EOL; // 3
Chiqishi:
Xulosa: balans, hisoblagich, status kabi ichkaridan o'zgaradigan, lekin tashqaridan himoyalangan holat uchun private(set). Yaratilgandan keyin umuman o'zgarmaydigan qiymat (ID, koordinata, vaqt belgisi) uchun readonly.
Asymmetric + hook birga¶
Eng kuchli kombinatsiya β asimmetrik ko'rinish va hookni birga ishlatish. O'qish ochiq, yozish faqat ichkaridan va har yozishda validatsiya:
<?php
declare(strict_types=1);
final class Foydalanuvchi
{
// Tashqaridan o'qiladi; ichkaridan, validatsiya bilan yoziladi
public private(set) string $email = '' {
set(string $value) {
$value = strtolower(trim($value));
if (! str_contains($value, '@')) {
throw new \InvalidArgumentException("Yaroqsiz email: {$value}");
}
$this->email = $value;
}
}
public private(set) int $login_soni = 0;
// Virtual: emaildan domen qismi
public string $domen {
get => substr($this->email, strpos($this->email, '@') + 1);
}
public function __construct(string $email)
{
$this->email = $email; // set hook ichkarida ishlaydi
}
public function kirdi(): void
{
$this->login_soni++;
}
}
$u = new Foydalanuvchi(' OQIL@Misol.UZ ');
echo $u->email, PHP_EOL; // oqil@misol.uz
echo $u->domen, PHP_EOL; // misol.uz
$u->kirdi();
$u->kirdi();
echo $u->login_soni, PHP_EOL; // 2
// β tashqaridan email yozish - asymmetric
try { $u->email = 'boshqa@x.uz'; }
catch (\Error $e) { echo "1) ", $e->getMessage(), PHP_EOL; }
// β virtual domenga yozish - set hook yo'q
try { $u->domen = 'x'; }
catch (\Error $e) { echo "2) ", $e->getMessage(), PHP_EOL; }
Chiqishi:
oqil@misol.uz
misol.uz
2
1) Cannot modify private(set) property Foydalanuvchi::$email from global scope
2) Property Foydalanuvchi::$domen is read-only
Bu kichik sinf uch xil himoyani birlashtiradi: email tashqaridan himoyalangan (asymmetric) va doim toza (set hook), login_soni faqat kirdi() orqali oshadi, domen esa har doim emailga mos virtual qiymat. Bularning hammasi β getter/setter metodlarsiz.
Hooks vs sehrli __get/__set: nega hook afzal¶
PHP 8.4 gacha "propertyga mantiq osish" ning yagona usuli β __get/__set sehrli metodlar edi. Ular hali ham ishlaydi, lekin property hook deyarli har jihatdan ustun. Farqni tushunaylik.
<?php
declare(strict_types=1);
// Sehrli __get - butun obyekt uchun bitta "qora quti"
class Sehrli
{
private array $data = ['x' => 10];
public function __get(string $name): mixed
{
return $this->data[$name] ?? null;
}
}
$s = new Sehrli();
echo $s->x, PHP_EOL; // 10 - ishlaydi, LEKIN...
// Reflection / IDE 'x' propertyni KO'RMAYDI
var_dump(property_exists(Sehrli::class, 'x')); // bool(false)
Chiqishi:
__get butun obyekt darajasida ishlaydi β mavjud bo'lmagan har qanday propertyga murojaatni tutib oladi. Bu uchta jiddiy muammoni keltiradi:
-
Aniqlik yo'q.
__get"har qanday property" ni qaytaradi β IDE qaysi propertylar borligini bilmaydi (avtoto'ldirish ishlamaydi), Reflectionxni propertylar ro'yxatida ko'rmaydi (property_existsfalse). Property hook esa aniq e'lon qilingan propertyga osiladi: IDE va Reflection uni to'liq ko'radi. -
Performance.
__get/__sethar murojaatda sehrli metodni chaqiradi β bu oddiy property kirishidan sezilarli sekin. Property hook esa faqat o'sha bitta propertyga ta'sir qiladi; hooksiz propertylar hech qanday qo'shimcha narxsiz, oddiy property tezligida ishlaydi. -
Faqat kerakli propertyga.
__setbutun obyektni "qamrab oladi": agar bitta propertyga validatsiya kerak bo'lsa ham,__sethar bir noma'lum propertyga tushadi va siz ichidaswitch ($name)yozishga majbursiz. Hook esa nuqta bilan, faqat kerakli propertyga qo'llanadi.
| Mezon | Property hook (8.4) | __get/__set (eski) |
|---|---|---|
| IDE avtoto'ldirish | β ko'radi | β ko'rmaydi |
| Reflection | β
to'liq (getHooks, isVirtual) |
β ko'rmaydi |
| Tezlik | hooksiz property = oddiy; hookli = arzon | har murojaatda sehrli chaqiruv (sekin) |
| Qamrov | faqat e'lon qilingan property | har qanday noma'lum property |
| Tur xavfsizligi | property turi tekshiriladi | qo'lda tekshirasiz |
Qachon
__gethali kerak? Faqat propertylar nomi oldindan noma'lum bo'lganda β masalan dinamik ORM yokistdClass-kabi "ochiq" konteyner. Aniq, ma'lum propertylar uchun esa doim hook ishlating.
Qachon hook kerak, qachon oddiy public property yetarli (over-engineering tanqidi)¶
Yangi imkoniyat paydo bo'lganda uni hamma joyda ishlatish vasvasasi bor. Bu β over-engineering. Hook mantiq kerak bo'lganda kerak, shunchaki "zamonaviy ko'rinish" uchun emas.
<?php
declare(strict_types=1);
// β KERAKSIZ: hook hech narsa qo'shmaydi - shunchaki o'qiydi/yozadi
final class YomonNuqta
{
public int $x = 0 {
get => $this->x; // foydasiz - oddiy o'qish
set(int $v) { $this->x = $v; } // foydasiz - oddiy yozish
}
}
// β
TO'G'RI: mantiq yo'q bo'lsa - oddiy public property
final class Nuqta
{
public function __construct(
public int $x = 0,
public int $y = 0,
) {}
}
$n = new Nuqta(3, 4);
echo "$n->x,$n->y", PHP_EOL; // 3,4
YomonNuqta dagi hooklar hech qanday qiymat qo'shmaydi β ular shunchaki qiymatni o'tkazib yuboradi, ortiqcha kod va ozgina sekinlik bilan. Bu β getter/setter boilerplate ning yangi qiyofadagi takrori.
Hook (yoki asymmetric) qachon o'rinli:
sethook β validatsiya (yaroqsiz qiymatni rad etish) yoki normalizatsiya (trim, lower, round) kerak bo'lganda.gethook (virtual) β qiymat boshqa propertylardan hisoblanadigan bo'lsa ($fullName,$yuza,$domen).private(set)β property tashqaridan ko'rinishi, lekin faqat sinf ichidan boshqarilishi kerak bo'lganda (balans, status, hisoblagich).
Oddiy public property qachon yetarli:
- Hech qanday validatsiya/hisob/cheklov yo'q (DTO maydonlari, koordinatalar, oddiy konfiguratsiya qiymatlari).
- Bunda hook qo'shsangiz β faqat shovqin qo'shasiz.
Qoida: hookni mantiq talab qilganda qo'shing, odat tariqasida emas. "Balki kelajakda kerak bo'lar" β bu over-engineering ning klassik bahonasi. Kerak bo'lganda qo'shasiz: hookni keyinroq qo'shish chaqiruvchi kodni umuman buzmaydi, chunki sintaksis bir xil (
$obj->prop). Bu β hookning yana bir afzalligi: oddiy public propertydan hookli propertyga o'tish qayta moslashuvsiz (backward-compatible).
Yana bir tuzoq: rekursiya va backing field¶
Bitta nozik tuzoqni alohida ta'kidlaymiz. set hook ichida $this->property = $value deyish backing fieldga yozadi β bu xavfsiz, hookni qayta chaqirmaydi. Lekin get hook ichida $this->property ni o'qisangiz, virtual propertyda bu cheksiz rekursiyaga olib keladi (chunki o'qish o'zini o'zi chaqiradi). Shu sababli virtual get hook boshqa propertylarni o'qishi kerak, o'zini emas:
<?php
declare(strict_types=1);
class ToGri
{
public function __construct(private float $kvadrat_tomon) {}
public float $yuza {
// β
TO'G'RI: boshqa property ($kvadrat_tomon) ni o'qiydi
get => $this->kvadrat_tomon ** 2;
}
}
$o = new ToGri(5.0);
echo $o->yuza, PHP_EOL; // 25
Agar backing fieldli (virtual emas) property kerak bo'lsa, get ichida $this->property ni o'qisangiz PHP buni backing fieldga murojaat deb tushunadi va rekursiya bo'lmaydi β lekin bunda property virtual emas, xotira oladi. Tafovutni Reflection (isVirtual()) bilan tekshirib ko'ring.
Xulosa¶
- Property hook (8.4) β
get/setmantiqini propertyning o'ziga osadi:get => ...(qisqa) yoki{ get { ... } set { ... } }(blok). Bu getter/setter boilerplateni o'ldiradi. - Virtual property β backing-field'siz, faqat
getβ qiymat har o'qishda hisoblanadi ($fullName,$domen). Unga yozib bo'lmaydi. sethook$valueparametrini oladi β validatsiya va normalizatsiya uchun yagona kirish nuqtasi; obyekt hech qachon yaroqsiz holatga tushmaydi.- Hooklar interfeysda (
{ get; }) e'lon qilinishi mumkin β toza shartnoma. - Asymmetric visibility (
public private(set)/protected(set)) β tashqaridan o'qiladi, faqat ichkaridan yoziladi.readonlydan farqi: bir marta emas, ko'p marta lekin faqat sinf ichidan. - Hook
__get/__setdan afzal: aniqlik (IDE/Reflection ko'radi), tezlik (sehrli emas), nuqtali qamrov (faqat kerakli property). - Hookni mantiq talab qilganda qo'shing β odat tariqasida emas. Mantiqsiz holatda oddiy public property yetarli (over-engineering dan saqlaning).
Keyingi bobda Reflection bilan bu hooklarning ich-ichiga kiramiz, attributes va FFI ni o'rganamiz.
Mashqlar¶
Oson¶
-
Doira virtual.
Doirasinfini yarating:public float $radiuskonstruktorda. Ikkita virtual property qo'shing β$yuza(pi * r^2) va$perimetr(2 * pi * r). Ularga yozib bo'lmasligini tekshiring. -
Foiz cheklovi.
Progresssinfidapublic int $foizproperty bo'lsin;sethook qiymatni doim0..100oralig'iga qisib qo'ysin (150->100,-5->0).
O'rta¶
-
Slug.
Maqolasinfida oddiypublic string $sarlavhava virtualpublic string $slugbo'lsin.$slugsarlavhani kichik harf qilib, harf/raqamdan tashqari belgilarni-ga almashtirsin ("Salom Dunyo PHP 8.4"->"salom-dunyo-php-8-4"). -
Himoyalangan stok.
Mahsulotsinfidapublic private(set) int $stokbo'lsin.qoshish(int $n)vasotish(int $n)metodlari stokni o'zgartirsin;sotishda stok yetmasa istisno tashlansin. Tashqaridan$m->stok = 999mumkin emasligini tasdiqlang.
Qiyin¶
- Ko'p birlik harorat.
Haroratsinfini yarating: ichki backing field βprivate float $kelvin. Uchta property β$selsiy,$farengeyt,$kelvinlarβ har birigetvasetga ega bo'lsin (ya'ni har birini o'qish ham, yozish ham mumkin), lekin hammasi bitta$kelvinustidan ishlasin.setda fizik chegaralar tekshirilsin (selsiy>= -273.15, kelvin>= 0). Birini o'zgartirsangiz, qolganlar avtomatik mos kelishi kerak.
Yechim β 1
<?php
declare(strict_types=1);
final class Doira
{
public function __construct(public float $radius) {}
public float $yuza {
get => M_PI * $this->radius ** 2;
}
public float $perimetr {
get => 2 * M_PI * $this->radius;
}
}
$d = new Doira(2.0);
printf("yuza=%.4f perimetr=%.4f%s", $d->yuza, $d->perimetr, PHP_EOL);
// β virtual propertyga yozib bo'lmaydi
try {
$d->yuza = 10;
} catch (\Error $e) {
echo $e->getMessage(), PHP_EOL; // Property Doira::$yuza is read-only
}
Ikkala property ham virtual β radius ga to'liq bog'liq, o'z xotirasi yo'q. radius o'zgarsa, keyingi o'qishda ikkalasi ham yangilanadi.
Yechim β 2
<?php
declare(strict_types=1);
final class Progress
{
public int $foiz = 0 {
set(int $v) {
$this->foiz = max(0, min(100, $v)); // 0..100 ga qisish
}
}
}
$p = new Progress();
$p->foiz = 150;
echo $p->foiz, PHP_EOL; // 100
$p->foiz = -5;
echo $p->foiz, PHP_EOL; // 0
$p->foiz = 73;
echo $p->foiz, PHP_EOL; // 73
max(0, min(100, $v)) β qiymatni pastdan 0, tepadan 100 bilan cheklaydi. Bu β "clamp" naqshi.
Yechim β 3
<?php
declare(strict_types=1);
final class Maqola
{
public string $sarlavha = '';
public string $slug {
get => preg_replace('/[^a-z0-9]+/', '-', strtolower(trim($this->sarlavha)));
}
}
$m = new Maqola();
$m->sarlavha = 'Salom Dunyo PHP 8.4';
echo $m->slug, PHP_EOL; // salom-dunyo-php-8-4
$slug β virtual property: u $sarlavha dan har o'qishda yangidan hisoblanadi. Avval strtolower(trim(...)), so'ng preg_replace harf/raqamdan tashqari ketma-ket belgilarni bitta - ga almashtiradi. Ishlab chiqarishda chekka - larni ham olib tashlash (trim($slug, '-')) tavsiya etiladi.
Yechim β 4
<?php
declare(strict_types=1);
final class Mahsulot
{
public private(set) int $stok = 0;
public function qoshish(int $n): void
{
$this->stok += max(0, $n); // manfiy qo'shishni e'tiborsiz qoldirish
}
public function sotish(int $n): void
{
if ($n > $this->stok) {
throw new \RuntimeException('Yetarli stok yo\'q');
}
$this->stok -= $n;
}
}
$x = new Mahsulot();
$x->qoshish(10);
$x->sotish(3);
echo $x->stok, PHP_EOL; // 7
// β tashqaridan yozish mumkin emas
try {
$x->stok = 999;
} catch (\Error $e) {
echo "Himoyalangan: ", $e->getMessage(), PHP_EOL;
}
// β stok yetmasa istisno
try {
$x->sotish(100);
} catch (\RuntimeException $e) {
echo "Sotuv xatosi: ", $e->getMessage(), PHP_EOL;
}
public private(set) stokni tashqaridan o'qish mumkin (echo $x->stok), lekin yozishni faqat sinf metodlari (qoshish/sotish) orqali β va ular o'z qoidalarini (manfiy qo'shmaslik, yetmaganda rad etish) qo'llaydi. Bu β inkapsulyatsiyaning toza ifodasi.
Yechim β 5 (to'liq)
<?php
declare(strict_types=1);
/**
* Bitta haqiqat manbai - $kelvin. Uchala property ($selsiy, $farengeyt,
* $kelvinlar) shu ustidan ishlaydi: get hisoblab beradi, set esa
* fizik chegarani tekshirib $kelvin ni yangilaydi. Bir birlikni
* o'zgartirsangiz, qolganlar avtomatik mos keladi - chunki manba bitta.
*/
final class Harorat
{
private float $kelvin = 273.15; // standart: 0 C
public float $selsiy {
get => $this->kelvin - 273.15;
set(float $v) {
if ($v < -273.15) {
throw new \InvalidArgumentException('Absolyut noldan past');
}
$this->kelvin = $v + 273.15;
}
}
public float $farengeyt {
get => ($this->kelvin - 273.15) * 9 / 5 + 32;
set(float $v) {
// Farengeytni selsiyga o'tkazib, $selsiy set hookidan o'tkazamiz
$this->selsiy = ($v - 32) * 5 / 9;
}
}
public float $kelvinlar {
get => $this->kelvin;
set(float $v) {
if ($v < 0) {
throw new \InvalidArgumentException('Kelvin manfiy bo\'lmaydi');
}
$this->kelvin = $v;
}
}
}
$h = new Harorat();
$h->selsiy = 100; // qaynash nuqtasi
printf("C=%.2f F=%.2f K=%.2f%s", $h->selsiy, $h->farengeyt, $h->kelvinlar, PHP_EOL);
// C=100.00 F=212.00 K=373.15
$h->farengeyt = 32; // muzlash nuqtasi - boshqa birlikni o'zgartirdik
printf("C=%.2f K=%.2f%s", $h->selsiy, $h->kelvinlar, PHP_EOL);
// C=0.00 K=273.15
// β fizik chegara: manfiy kelvin mumkin emas
try {
$h->kelvinlar = -1;
} catch (\InvalidArgumentException $e) {
echo "Xato: ", $e->getMessage(), PHP_EOL; // Xato: Kelvin manfiy bo'lmaydi
}
// β absolyut noldan past selsiy
try {
$h->selsiy = -300;
} catch (\InvalidArgumentException $e) {
echo "Xato: ", $e->getMessage(), PHP_EOL; // Xato: Absolyut noldan past
}
Nega bu dizayn to'g'ri?
-
Yagona haqiqat manbai (single source of truth): faqat
$kelvinsaqlanadi. Uchala property β uning ustidagi ko'rinishlar (view). Shuning uchun ular hech qachon bir-biriga zid bo'lmaydi:$selsiyni o'zgartirsangiz,$farengeytkeyingi o'qishda avtomatik yangi qiymatni hisoblaydi. Agar uchchalasini alohida backing field qilsangiz β qo'lda sinxron saqlashga majbur bo'lardingiz, bu esa xatoga sabab. -
Validatsiya qatlami: har bir
setfizik chegarani tekshiradi. E'tibor bering,$farengeytningseti o'zi tekshirmaydi β u qiymatni$selsiyga o'tkazadi, va$selsiyningsethooki tekshiradi. Shunday qilib validatsiya bir joyda jamlangan (DRY). -
Virtual: uchchala property ham virtual β backing fieldsiz. Ular faqat
$kelvinni o'qiydi/yozadi, o'zlariga xotira olmaydi.
Bu naqsh β birlik aylantirish, valyuta, masofa (km/mil) kabi har qanday "bir necha ko'rinishli bitta qiymat" uchun ideal: ichda bitta kanonik birlikni saqlang, qolganlarni hook orqali ko'rsating.
β¬ οΈ Oldingi: 06 β readonly, Value Object va variance Β· π README Β· Keyingi: 08 β Reflection, attributes va FFI β‘οΈ