2.11 Magic metodlar¶
β¬ οΈ Oldingi: 2.10 Xatolarni boshqarish (try / catch) Β· π README Β· Keyingi: 2.12 Namespace va autoloading β‘οΈ
Tasavvur qiling: Laravel'da $user->name deb yozasiz va ism o'z-o'zidan bazadan keladi β garchi User classida public $name umuman e'lon qilinmagan bo'lsa ham. Yoki $collection->where(...)->map(...)->filter(...) deb metodlarni zanjir qilib chaqirasiz, lekin classda bunday metodlarning ko'pi yo'q. Bu "sehr" qayerdan? Javob β magic metodlar (magic method β PHP avtomatik chaqiradigan maxsus metod).
Magic metod β nomi ikkita pastki chiziq bilan boshlanadigan (__) maxsus metod. Siz uni hech qachon $obj->__get(...) deb qo'lda chaqirmaysiz β PHP uni ma'lum bir vaziyat yuzaga kelganda o'zi chaqiradi. Masalan, mavjud bo'lmagan xususiyatga murojaat qilsangiz β __get ishga tushadi; obyektni echo qilsangiz β __toString ishga tushadi.
Bitta __constructni siz allaqachon bilasiz (2.2): u obyekt new bilan yaratilganda avtomatik chaqiriladi. Aslida __construct ham magic metod β shunchaki eng ko'p ishlatiladigani. Bu bobda qolgan magic metodlarni o'rganamiz va ular bilan haqiqiy professional kutubxonalar (Laravel, Doctrine) qanday "sehr" yaratishini ko'ramiz.
Diqqat: PHP'da
__bilan boshlanadigan metod nomlari faqat PHP'ning o'zi uchun zahiralangan. Shuning uchun o'z metodlaringizni__bilan boshlamang β kelajakda PHP yangi magic metod qo'shsa, to'qnashuv bo'ladi.
__construct va __destruct β boshlanish va tugash¶
__constructni bilamiz: obyekt tug'ilganda chaqiriladi. Uning juftligi β __destruct: obyekt "o'lganda" (xotiradan o'chirilganda yoki skript tugaganda) chaqiriladi. Bu, masalan, ochilgan faylni yopish yoki ulanishni uzish uchun ishlatiladi:
<?php
class FaylYozuvchi
{
private $resurs;
public function __construct(private readonly string $yol)
{
$this->resurs = fopen($this->yol, 'w');
echo "Fayl ochildi: {$this->yol}\n";
}
public function yoz(string $matn): void
{
fwrite($this->resurs, $matn);
}
// Obyekt yo'q qilinganda avtomatik chaqiriladi
public function __destruct()
{
fclose($this->resurs);
echo "Fayl yopildi: {$this->yol}\n";
}
}
$yozuvchi = new FaylYozuvchi('hisobot.txt');
$yozuvchi->yoz("Salom dunyo");
unset($yozuvchi); // bu yerda __destruct ishga tushadi
echo "Dastur davom etmoqda...\n";
Natija:
__destructni qo'lda yozish kamdan-kam kerak bo'ladi, lekin "resurs" (fayl, tarmoq ulanishi) bilan ishlaganda foydali. Diqqat: bu yerda constructor property promotion (2.2'da o'rgangan qisqa yozuv: private readonly string $yol) ishlatildi β bu zamonaviy PHP 8 uslubi.
__toString β obyektni matnga aylantirish¶
Oddiy obyektni echo qilsangiz, xato chiqadi: PHP obyektni matnga qanday aylantirishni bilmaydi. __toString aynan shuni hal qiladi β u obyekt matn sifatida ishlatilganda (echo, satr ulash, (string)) chaqiriladi va matn qaytaradi:
<?php
class Pul
{
public function __construct(
private readonly int $miqdor,
private readonly string $valyuta = 'UZS'
) {}
public function __toString(): string
{
return number_format($this->miqdor, 0, '.', ' ') . ' ' . $this->valyuta;
}
}
$narx = new Pul(1500000);
echo $narx; // 1 500 000 UZS
echo "\n";
echo "Jami: " . $narx . "\n"; // Jami: 1 500 000 UZS
$matn = (string) $narx; // matnga aylantirish
echo strlen($matn) . "\n"; // 13
__toString har doim string qaytarishi shart (PHP 8'da : string return-tipini yozsangiz, buni ta'minlaysiz). Bu metod log yozish, xabarlarni chiqarish, DTO'ni (Data Transfer Object β ma'lumot tashuvchi obyekt) ko'rsatishda juda qulay.
__get va __set β mavjud bo'lmagan xususiyatga murojaat¶
Mana eng "sehrli" juftlik. __get($nom) β mavjud bo'lmagan (yoki private/protected bo'lib, tashqaridan ko'rinmaydigan) xususiyatni o'qishga urinilganda chaqiriladi. __set($nom, $qiymat) β bunday xususiyatga yozishga urinilganda chaqiriladi.
Bu Laravel modellaridagi ($user->name) "sehr"ning asosi. Quyida ichki massivda ma'lumot saqlaydigan, lekin tashqaridan oddiy xususiyat kabi ko'rinadigan class:
<?php
class Sozlama
{
private array $malumot = [];
// $obj->nimadir = ... bo'lganda chaqiriladi
public function __set(string $nom, mixed $qiymat): void
{
echo "[set] {$nom} = " . var_export($qiymat, true) . "\n";
$this->malumot[$nom] = $qiymat;
}
// $obj->nimadir o'qilganda chaqiriladi
public function __get(string $nom): mixed
{
echo "[get] {$nom}\n";
return $this->malumot[$nom] ?? null;
}
}
$s = new Sozlama();
$s->til = 'uz'; // __set chaqirildi
$s->mavzu = 'tungi'; // __set chaqirildi
echo $s->til . "\n"; // __get chaqirildi -> uz
echo $s->mavzu . "\n"; // __get chaqirildi -> tungi
Natija:
Diqqat qiling: til va mavzu degan xususiyatlar classda umuman e'lon qilinmagan β ular dinamik (dynamic β ish vaqtida hosil bo'ladigan) tarzda yaratildi. Bu β "dinamik xususiyat" (dynamic property) deb ataladi.
Muhim qoidalar: -
__get/__setfaqat xususiyat mavjud bo'lmaganda yoki ko'rinmas (private/protected) bo'lganda ishlaydi. Agarpublic $tile'lon qilingan bo'lsa, PHP to'g'ridan-to'g'ri unga murojaat qiladi va__getni chaqirmaydi. - Bu metodlar sekinroq ishlaydi (oddiy xususiyatga qaraganda) va IDE ularni "ko'rmaydi" (avto-to'ldirish ishlamaydi). Shuning uchun ortiqcha ishlatmang.
__isset va __unset β isset() va unset() bilan ishlash¶
__get/__set bilan dinamik xususiyat yaratganda, bitta muammo chiqadi: isset($obj->til) to'g'ri ishlamaydi, chunki PHP haqiqiy xususiyatni ko'rmaydi. Buni __isset hal qiladi β u dinamik xususiyatga isset() yoki empty() qo'llanilganda chaqiriladi. __unset esa unset() qo'llanilganda:
<?php
class Sozlama
{
private array $malumot = [];
public function __set(string $nom, mixed $qiymat): void
{
$this->malumot[$nom] = $qiymat;
}
public function __get(string $nom): mixed
{
return $this->malumot[$nom] ?? null;
}
// isset($obj->nom) / empty($obj->nom) bo'lganda
public function __isset(string $nom): bool
{
return isset($this->malumot[$nom]);
}
// unset($obj->nom) bo'lganda
public function __unset(string $nom): void
{
unset($this->malumot[$nom]);
}
}
$s = new Sozlama();
$s->til = 'uz';
var_dump(isset($s->til)); // true (__isset ishladi)
var_dump(isset($s->mavzu)); // false (yo'q)
unset($s->til); // __unset ishladi
var_dump(isset($s->til)); // false
To'liq "dinamik xususiyatli" class qilish uchun bu to'rtlik (__get, __set, __isset, __unset) birga ishlatiladi β shunda obyekt har tomondan oddiy xususiyatli obyekt kabi tuyuladi.
__call va __callStatic β mavjud bo'lmagan metod chaqirilganda¶
__call($nom, $argumentlar) β obyektda mavjud bo'lmagan metod chaqirilganda ishga tushadi. $nom β chaqirilgan metod nomi, $argumentlar β berilgan argumentlar massivi. __callStatic esa xuddi shu, lekin static metod uchun (Class::metod()).
Bu "proxy" (vositachi) va "fluent interface" (zanjirli, ravon interfeys) yaratishda asosiy vosita. Avval oddiy misol β har qanday metod chaqiruvini "ushlab qoladigan" class:
<?php
class Logger
{
// Mavjud bo'lmagan metod chaqirilganda
public function __call(string $nom, array $argumentlar): void
{
$matn = $argumentlar[0] ?? '';
echo "[" . strtoupper($nom) . "] {$matn}\n";
}
// Mavjud bo'lmagan STATIC metod chaqirilganda
public static function __callStatic(string $nom, array $argumentlar): void
{
$matn = $argumentlar[0] ?? '';
echo "[STATIC " . strtoupper($nom) . "] {$matn}\n";
}
}
$log = new Logger();
$log->info("Foydalanuvchi kirdi"); // [INFO] Foydalanuvchi kirdi
$log->error("Baza ulanmadi"); // [ERROR] Baza ulanmadi
$log->debug("x = 42"); // [DEBUG] x = 42
Logger::warning("Eski API"); // [STATIC WARNING] Eski API
info, error, debug, warning metodlari classda yozilmagan β lekin barchasi ishlaydi, chunki __call/__callStatic ularni ushlab qoladi. Aynan shu usul bilan Laravel'ning Log::info(...), Cache::get(...) kabi "facade"lari (yuzaki interfeyslar) ishlaydi.
__invoke β obyektni funksiya kabi chaqirish¶
__invoke eng qiziq magic metodlardan biri: u obyektni xuddi funksiya kabi $obj(...) ko'rinishida chaqirilganda ishga tushadi. Bunday obyekt "chaqiriladigan obyekt" (invokable object) deb ataladi β u holat (xususiyat) saqlaydigan funksiya kabi:
<?php
class Kopaytiruvchi
{
public function __construct(private readonly int $koeffitsient) {}
// $obj(...) deb chaqirilganda ishlaydi
public function __invoke(int $son): int
{
return $son * $this->koeffitsient;
}
}
$ikkilantir = new Kopaytiruvchi(2);
$uchlantir = new Kopaytiruvchi(3);
echo $ikkilantir(10) . "\n"; // 20 ($ikkilantir obyektini funksiya kabi chaqirdik)
echo $uchlantir(10) . "\n"; // 30
// Eng foydali tomoni: array_map kabi funksiyalarga uzatish mumkin
$sonlar = [1, 2, 3, 4];
$natija = array_map($ikkilantir, $sonlar);
print_r($natija); // [2, 4, 6, 8]
__invokening kuchi shundaki, oddiy funksiya holatni (bu yerda β $koeffitsient) saqlay olmaydi, ammo obyekt saqlaydi. Laravel'da "single action controller" (bitta vazifali kontroller) aynan shu prinsipga asoslangan.
__clone β obyektni nusxalashda chuqur nusxa¶
PHP'da obyektni clone bilan nusxalasangiz, "yuza nusxa" (shallow copy) hosil bo'ladi: oddiy qiymatlar ko'chiriladi, lekin ichki obyektlar ikkala nusxada ham bir xil (umumiy) qoladi. Demak, nusxadagi ichki obyektni o'zgartirsangiz, asl obyektdagi ham o'zgaradi β bu ko'pincha xato keltiradi.
__clone β obyekt clone qilingandan keyin yangi nusxada avtomatik chaqiriladi. Uning vazifasi β ichki obyektlarni ham nusxalab, "chuqur nusxa" (deep copy) qilish:
<?php
class Manzil
{
public function __construct(public string $shahar) {}
}
class Foydalanuvchi
{
public function __construct(
public string $ism,
public Manzil $manzil
) {}
// clone qilinganda yangi nusxada chaqiriladi
public function __clone(): void
{
// Ichki obyektni ham nusxalaymiz (aks holda umumiy bo'lib qoladi)
$this->manzil = clone $this->manzil;
}
}
$ali = new Foydalanuvchi('Ali', new Manzil('Toshkent'));
$vali = clone $ali;
$vali->ism = 'Vali';
$vali->manzil->shahar = 'Samarqand'; // faqat $vali ning manzilini o'zgartiramiz
echo $ali->manzil->shahar . "\n"; // Toshkent (o'zgarmadi!)
echo $vali->manzil->shahar . "\n"; // Samarqand
__clone ichida $this->manzil = clone $this->manzil; qatorini olib tashlasangiz, ikkalasi ham Samarqand bo'lib qoladi β chunki ikkala obyekt bitta Manzilni baham ko'radi. Mana shuning uchun ichki obyektli classlarda __clone deyarli har doim kerak.
Real misol: dinamik query-builder¶
Endi bir nechta magic metodni birga ishlatib, haqiqiy kutubxonalardagidek "query builder" (so'rov quruvchi β SQL'ni metodlar bilan yig'adigan vosita) yasaymiz. Bu Laravel'ning DB::table('users')->where('age', '>', 18)->orderBy('name') uslubining soddalashtirilgan ko'rinishi:
<?php
class QuerySorovi
{
private array $shartlar = [];
private array $tartib = [];
public function __construct(private readonly string $jadval) {}
// where(...), orWhere(...) kabi metodlar uchun
public function where(string $maydon, string $amal, mixed $qiymat): static
{
$this->shartlar[] = "{$maydon} {$amal} '{$qiymat}'";
return $this; // zanjir uchun o'zini qaytaradi (fluent interface)
}
public function orderBy(string $maydon, string $yonalish = 'ASC'): static
{
$this->tartib[] = "{$maydon} {$yonalish}";
return $this;
}
// Yakuniy SQL matnini yasash
public function __toString(): string
{
$sql = "SELECT * FROM {$this->jadval}";
if ($this->shartlar !== []) {
$sql .= " WHERE " . implode(' AND ', $this->shartlar);
}
if ($this->tartib !== []) {
$sql .= " ORDER BY " . implode(', ', $this->tartib);
}
return $sql . ";";
}
}
$sorov = (new QuerySorovi('foydalanuvchilar'))
->where('yosh', '>', 18)
->where('shahar', '=', 'Toshkent')
->orderBy('ism');
echo $sorov . "\n";
// SELECT * FROM foydalanuvchilar WHERE yosh > '18' AND shahar = 'Toshkent' ORDER BY ism ASC;
Bu yerda ikkita g'oya birlashdi: har bir metod return $this qaytarib zanjir (fluent) imkonini berdi, __toString esa yig'ilgan so'rovni echo qilinganda SQL matniga aylantirdi.
Real misol: tiplangan konteyner (ActiveRecord namunasi)¶
Endi __get/__set/__isset + __callni birga ishlatib, soddalashtirilgan "ActiveRecord" (ma'lumotni obyekt kabi ifodalovchi andoza β Laravel Eloquent shunga asoslangan) yasaymiz. Bunda ma'lumotlar ichki massivda saqlanadi, lekin tashqaridan oddiy obyekt kabi ko'rinadi, ustiga getIsm() kabi metodlar ham "sehrli" ravishda ishlaydi:
<?php
class Model
{
public function __construct(private array $atributlar = []) {}
public function __get(string $nom): mixed
{
return $this->atributlar[$nom] ?? null;
}
public function __set(string $nom, mixed $qiymat): void
{
$this->atributlar[$nom] = $qiymat;
}
public function __isset(string $nom): bool
{
return isset($this->atributlar[$nom]);
}
// getIsm(), getYosh() kabi chaqiruvlarni avtomatik hosil qilamiz
public function __call(string $nom, array $argumentlar): mixed
{
if (str_starts_with($nom, 'get')) {
// "getIsm" -> "ism"
$maydon = strtolower(substr($nom, 3));
return $this->atributlar[$maydon] ?? null;
}
throw new BadMethodCallException("Metod topilmadi: {$nom}");
}
public function toArray(): array
{
return $this->atributlar;
}
}
$user = new Model(['ism' => 'Ali', 'yosh' => 25]);
// Dinamik xususiyat (__get / __set)
echo $user->ism . "\n"; // Ali
$user->shahar = 'Toshkent'; // __set
echo $user->shahar . "\n"; // Toshkent
// Sehrli getter (__call)
echo $user->getIsm() . "\n"; // Ali
echo $user->getYosh() . "\n"; // 25
// __isset
var_dump(isset($user->ism)); // true
var_dump(isset($user->yoq)); // false
print_r($user->toArray());
Natija:
Bu β Eloquent va Doctrine kabi ORM (Object-Relational Mapping β obyekt va baza jadvali orasidagi ko'prik) kutubxonalarining yuragida turgan g'oya. Albatta, haqiqiy ORM bundan ancha murakkab, lekin "sehr" aynan shu magic metodlardan boshlanadi.
__serialize va __unserialize β obyektni saqlashga tayyorlash¶
Obyektni faylga saqlash, sessiyaga yozish yoki tarmoq orqali yuborish uchun uni matn ko'rinishiga o'tkazish kerak β bu serializatsiya (serialize()). Ba'zan saqlashdan oldin nimanidir tashlab yuborish kerak (masalan, parol yoki ulanish resursi). __serialize aynan qaysi ma'lumot saqlanishini, __unserialize esa tiklashda nima qilishni belgilaydi (PHP 7.4+; eski __sleep/__wakeup o'rnini bosadi):
<?php
class Foydalanuvchi
{
public ?string $parol = null; // bu saqlanmasin
public function __construct(public string $ism) {}
public function __serialize(): array
{
return ['ism' => $this->ism]; // faqat ism saqlanadi
}
public function __unserialize(array $data): void
{
$this->ism = $data['ism'];
}
}
$u = new Foydalanuvchi("Ali");
$u->parol = "maxfiy";
$saqlangan = serialize($u); // parol ICHIDA yo'q
$tiklangan = unserialize($saqlangan);
echo $tiklangan->ism; // Ali
π Eski __sleep() (saqlanadigan xususiyat nomlarini qaytaradi) va __wakeup() (tiklashda chaqiriladi) ham mavjud, lekin zamonaviy kodda __serialize/__unserialize juftligi afzal.
__debugInfo β var_dump nimani ko'rsatishini boshqarish¶
var_dump($obj) obyektning BARCHA xususiyatlarini chiqaradi β ba'zan bu ortiqcha yoki maxfiy (parol, PIN). __debugInfo var_dump nimani ko'rsatishini belgilaydi:
<?php
class Hisob
{
public function __construct(
public string $egasi,
private string $pinKod,
) {}
public function __debugInfo(): array
{
return ['egasi' => $this->egasi, 'pinKod' => '***']; // PIN yashirildi
}
}
var_dump(new Hisob("Ali", "1234"));
// object(Hisob)#1 (2) { ["egasi"]=> string(3) "Ali" ["pinKod"]=> string(3) "***" }
Magic metodlar β to'liq ro'yxat (eslatma jadvali)¶
| Metod | Qachon chaqiriladi |
|---|---|
__construct |
Obyekt new bilan yaratilganda |
__destruct |
Obyekt yo'q qilinganda / skript tugaganda |
__toString |
Obyekt matn sifatida ishlatilganda (echo, (string)) |
__get |
Mavjud/ko'rinmas xususiyat o'qilganda |
__set |
Mavjud/ko'rinmas xususiyatga yozilganda |
__isset |
isset()/empty() qo'llanilganda |
__unset |
unset() qo'llanilganda |
__call |
Mavjud bo'lmagan obyekt metodi chaqirilganda |
__callStatic |
Mavjud bo'lmagan static metod chaqirilganda |
__invoke |
Obyekt funksiya kabi $obj(...) chaqirilganda |
__clone |
Obyekt clone qilingandan keyin |
__serialize |
serialize() da β qaysi ma'lumot saqlanishi (PHP 7.4+) |
__unserialize |
unserialize() da β obyektni qayta tiklash (PHP 7.4+) |
__sleep / __wakeup |
Eski serializatsiya juftligi (__serialize afzal) |
__debugInfo |
var_dump() obyektni qanday ko'rsatishi |
__set_state |
var_export() chaqirilganda |
Ehtiyot bo'ling: magic metodlarni ortiqcha ishlatmang¶
Magic metodlar kuchli, lekin "sehr" har doim yaxshi emas. Quyidagilarni yodda tuting:
- IDE va avto-to'ldirish "ko'rmaydi".
__getorqali yaratilgan$user->ismxususiyatini muharrir tanimaydi β xato yozsangiz ham ogohlantirmaydi. Buni yumshatish uchun class ustiga@propertyizohi yoziladi (Laravel modellarida shuni ko'rasiz). - Sekinroq. Magic metod chaqiruvi oddiy xususiyat/metoddan bir necha barobar sekin. Ko'p marta takrorlanadigan tsiklda buni sezasiz.
- O'qishni qiyinlashtiradi. Kodni o'qigan kishi
$user->getIsm()qayerda yozilganini izlab topa olmaydi β chunki u__callichida yashiringan. - Qachon ishlatish kerak? Haqiqatan dinamik xulq kerak bo'lganda (ORM, konfiguratsiya konteyneri, proxy, DTO). Oddiy class uchun esa odatiy xususiyat va metodlardan foydalaning β ular aniqroq va tezroq.
Qoida sodda: agar oddiy usul bilan yozish mumkin bo'lsa, magic metoddan foydalanmang. Magic metod β maxsus vaziyatlar uchun maxsus vosita.
Mashqlar¶
Oson
Vaqtnomli class yarating: u$soatva$daqiqaqabul qilsin,__toStringorqali"14:05"ko'rinishida qaytarsin (bir xonali daqiqa oldiga nol qo'shing β maslahat:sprintf("%02d", ...)).Haroratclassini yozing:$qiymat(gradus) saqlasin,echoqilinganda"23Β°C"chiqarsin.Foydalanuvchiclassida$ismva$familiyabo'lsin;__toStringto'liq ismni ("Ali Valiyev") qaytarsin.Kvadratclassini yozing:__invoke($son)orqali chaqirilganda sonning kvadratini ($son * $son) qaytarsin.$k = new Kvadrat(); echo $k(5);-> 25.
O'rta
__getva__setishlatib, ichki massivda ma'lumot saqlaydiganKonteynerclassini yozing.$c->til = 'uz'vaecho $c->til;ishlasin. Mavjud bo'lmagan xususiyat o'qilsanullqaytsin.__issetham qo'shing (5-mashqdagiKonteynerga):isset($c->til)to'g'ri ishlasin.Salomlovchiclassini yozing:__invoke($ism)orqali"Salom, Ali!"qaytarsin. Keyin uniarray_mapbilan["Ali", "Vali"]massiviga qo'llang.__setichida tekshiruv qo'shing: agar$nom === 'yosh'bo'lsa va qiymat manfiy bo'lsa,InvalidArgumentExceptiontashlang (throw). Aks holda saqlang.
Qiyin
Sozlamaclassini yozing:set('til', 'uz')o'rnigasetTil('uz'),setMavzu('tungi')kabi metodlar__callorqali ishlasin (fluent β har biri$thisqaytarsin, zanjir qilinsin).__toStringorqali barcha sozlamalarni"til=uz; mavzu=tungi"ko'rinishida chiqaring.Savatchaclassini__clonebilan yozing: ichidaMahsulotobyektlari massivi bo'lsin. Savatchanicloneqilganda, ichidagi har birMahsulotham yangidan nusxalansin (chuqur nusxa), shunda nusxadagi mahsulot narxini o'zgartirsangiz asl savatchaga ta'sir qilmasin.- Soddalashtirilgan
Record(ActiveRecord) classini yozing: konstruktorda massiv qabul qilsin;__get/__setbilan maydonlarga murojaat;__callbilangetX()(o'qish) vasetX($q)(yozish) metodlari ishlasin;toArray()barcha maydonlarni qaytarsin.
Yechim β 5 (__get / __set)
<?php
class Konteyner
{
private array $malumot = [];
public function __get(string $nom): mixed
{
return $this->malumot[$nom] ?? null;
}
public function __set(string $nom, mixed $qiymat): void
{
$this->malumot[$nom] = $qiymat;
}
}
$c = new Konteyner();
$c->til = 'uz';
echo $c->til . "\n"; // uz
var_dump($c->mavzu); // NULL (mavjud emas)
?? null β agar kalit massivda bo'lmasa, null qaytaradi (xato o'rniga).
Yechim β 7 (__invoke + array_map)
<?php
class Salomlovchi
{
public function __invoke(string $ism): string
{
return "Salom, {$ism}!";
}
}
$salom = new Salomlovchi();
echo $salom('Ali') . "\n"; // Salom, Ali!
$natija = array_map($salom, ['Ali', 'Vali']);
print_r($natija);
// Array ( [0] => Salom, Ali! [1] => Salom, Vali! )
__invoke tufayli oddiy funksiya kabi ishlatildi va array_mapga uzatildi.
Yechim β 9 (__call bilan fluent setter)
<?php
class Sozlama
{
private array $malumot = [];
public function __call(string $nom, array $argumentlar): static
{
if (str_starts_with($nom, 'set')) {
// "setTil" -> "til"
$maydon = strtolower(substr($nom, 3));
$this->malumot[$maydon] = $argumentlar[0] ?? null;
return $this; // zanjir uchun
}
throw new BadMethodCallException("Noma'lum metod: {$nom}");
}
public function __toString(): string
{
$juftliklar = [];
foreach ($this->malumot as $kalit => $qiymat) {
$juftliklar[] = "{$kalit}={$qiymat}";
}
return implode('; ', $juftliklar);
}
}
$s = (new Sozlama())
->setTil('uz')
->setMavzu('tungi')
->setTil('en'); // qayta yozish ham ishlaydi
echo $s . "\n"; // til=en; mavzu=tungi
setX __call ichida ushlanib, $this qaytargani uchun zanjir hosil bo'ldi.
Yechim β 10 (__clone chuqur nusxa)
<?php
class Mahsulot
{
public function __construct(
public string $nom,
public int $narx
) {}
}
class Savatcha
{
/** @var Mahsulot[] */
private array $mahsulotlar = [];
public function qoshish(Mahsulot $m): void
{
$this->mahsulotlar[] = $m;
}
public function jami(): int
{
return array_sum(array_map(fn(Mahsulot $m) => $m->narx, $this->mahsulotlar));
}
// Har bir ichki Mahsulotni ham nusxalaymiz (chuqur nusxa)
public function __clone(): void
{
$this->mahsulotlar = array_map(
fn(Mahsulot $m) => clone $m,
$this->mahsulotlar
);
}
}
$asl = new Savatcha();
$asl->qoshish(new Mahsulot('Kitob', 50000));
$asl->qoshish(new Mahsulot('Ruchka', 5000));
$nusxa = clone $asl;
// Nusxadagi birinchi mahsulot narxini o'zgartiramiz... lekin buning uchun
// ichki obyektga murojaat kerak, shuning uchun jamilarni solishtiramiz:
echo "Asl jami: " . $asl->jami() . "\n"; // 55000
echo "Nusxa jami: " . $nusxa->jami() . "\n"; // 55000
// Endi nusxadagi ichki obyektni o'zgartirsak, __clone tufayli
// asl savatchaga ta'sir qilmaydi β chunki ular alohida obyektlar.
__clone ichida array_map(fn($m) => clone $m, ...) har bir mahsulotni yangidan nusxaladi. Bu qatorsiz, ikkala savatcha bir xil Mahsulot obyektlarini baham ko'rardi.
Yechim β 11 (oddiy ActiveRecord)
<?php
class Record
{
public function __construct(private array $maydonlar = []) {}
public function __get(string $nom): mixed
{
return $this->maydonlar[$nom] ?? null;
}
public function __set(string $nom, mixed $qiymat): void
{
$this->maydonlar[$nom] = $qiymat;
}
public function __call(string $nom, array $argumentlar): mixed
{
// getIsm() -> o'qish
if (str_starts_with($nom, 'get')) {
$maydon = strtolower(substr($nom, 3));
return $this->maydonlar[$maydon] ?? null;
}
// setIsm('Ali') -> yozish
if (str_starts_with($nom, 'set')) {
$maydon = strtolower(substr($nom, 3));
$this->maydonlar[$maydon] = $argumentlar[0] ?? null;
return $this; // fluent
}
throw new BadMethodCallException("Noma'lum metod: {$nom}");
}
public function toArray(): array
{
return $this->maydonlar;
}
}
$u = new Record(['ism' => 'Ali']);
// __get / __set
$u->yosh = 25;
echo $u->ism . "\n"; // Ali
// __call: getter / setter
$u->setShahar('Toshkent');
echo $u->getShahar() . "\n"; // Toshkent
echo $u->getYosh() . "\n"; // 25
print_r($u->toArray());
// Array ( [ism] => Ali [yosh] => 25 [shahar] => Toshkent )