7-bo‘lim
Prototype
Yangi obyektni noldan qurish o'rniga tayyoridan nusxa olish - va nusxa qanchalik chuqur bo'lishi.
Ushbu bo‘lim mundarijasi
Prototype - yangi obyektni noldan yaratish o'rniga mavjudidan nusxa olish naqshi.
PHP da bunga tayyor kalit so'z bor: clone. Naqshning
qiyinligi esa clone ning qanchalik chuqur ishlashida.
Muammo: sozlangan obyektni takrorlash #
final class Uslub
{
public function __construct(
public string $shrift = 'Times',
public int $olcham = 12,
public bool $qalin = false,
) {}
public function korsat(): string
{
return sprintf('%s %dpt%s', $this->shrift, $this->olcham,
$this->qalin ? ' qalin' : '');
}
}
final class Hujjat
{
/** @var list<string> */
public array $boblar = [];
public function __construct(
public string $sarlavha,
public Uslub $uslub,
) {}
public function korsat(): string
{
return sprintf("%s [%s] boblar: %s",
$this->sarlavha,
$this->uslub->korsat(),
$this->boblar === [] ? '(yoq)' : implode(', ', $this->boblar));
}
}
Kompaniyaning shartnoma andozasi bor. Har yangi shartnoma undan boshlanadi:
$andoza = new Hujjat('Shartnoma andozasi', new Uslub('Arial', 11, true));
$andoza->boblar = ['Umumiy qoidalar', 'Toʻlov shartlari'];
// Har safar qo'lda qayta qurish - takrorlanish
$birinchi = new Hujjat('Shartnoma #1', new Uslub('Arial', 11, true));
$birinchi->boblar = ['Umumiy qoidalar', 'Toʻlov shartlari'];
echo $andoza->korsat(), PHP_EOL;
echo $birinchi->korsat(), PHP_EOL;
Shartnoma andozasi [Arial 11pt qalin] boblar: Umumiy qoidalar, Toʻlov shartlari
Shartnoma #1 [Arial 11pt qalin] boblar: Umumiy qoidalar, Toʻlov shartlari
Yuqoridagi kodda uslub va boblar ikki marta yozilgan. Real loyihada ular o'ntalab joyda takrorlanadi.
Andozaga yangi bob qo'shilsa, ularning hammasini topish kerak. Bittasi eskicha qolib ketadi.
Prototype naqshi shuni taklif qiladi: andozani bir marta qur, keyin nusxa ol.
clone - sayoz nusxa #
$andoza = new Hujjat('Shartnoma andozasi', new Uslub('Arial', 11, true));
$andoza->boblar = ['Umumiy qoidalar'];
$nusxa = clone $andoza;
$nusxa->sarlavha = 'Shartnoma #1';
$nusxa->boblar[] = 'Maxfiylik';
echo $andoza->korsat(), PHP_EOL;
echo $nusxa->korsat(), PHP_EOL;
Shartnoma andozasi [Arial 11pt qalin] boblar: Umumiy qoidalar
Shartnoma #1 [Arial 11pt qalin] boblar: Umumiy qoidalar, Maxfiylik
Sarlavha va boblar mustaqil o'zgardi. Endi uslubga tegamiz:
$andoza = new Hujjat('Shartnoma andozasi', new Uslub('Arial', 11, true));
$nusxa = clone $andoza;
$nusxa->uslub->olcham = 20; // faqat nusxaga tegdik
echo 'andoza: ', $andoza->uslub->korsat(), PHP_EOL;
echo 'nusxa: ', $nusxa->uslub->korsat(), PHP_EOL;
echo 'bir xil obyektmi: ',
$andoza->uslub === $nusxa->uslub ? 'ha' : 'yoq', PHP_EOL;
andoza: Arial 20pt qalin
nusxa: Arial 20pt qalin
bir xil obyektmi: ha
clone sayoz nusxa oladiAndozaning uslubi ham 20pt bo'lib qoldi - biz unga tegmaganimiz holda.
Sabab: clone obyekt maydonlarini ko'chiradi, lekin maydon
obyektga havola bo'lsa - havolaning o'zi ko'chiriladi,
obyekt emas. Natijada ikkala hujjat bitta Uslub
obyektiga qaraydi.
| Maydon turi | clone da nima bo'ladi |
|---|---|
int, string, bool | Qiymat ko'chiriladi - mustaqil |
array (skalarlardan) | Qiymat ko'chiriladi - mustaqil |
| Obyekt | Havola ko'chiriladi - bo'lishiladi |
| Obyektlardan iborat massiv | Massiv yangi, ichidagilar bo'lishiladi |
Bu PHP dagi eng ko'p uchraydigan nozik xatolardan biri.
Massiv ichidagi obyektlar ham xuddi shunday:
final class Qator
{
public function __construct(public string $matn) {}
}
final class Jadval
{
/** @var list<Qator> */
public array $qatorlar = [];
}
$asl = new Jadval();
$asl->qatorlar = [new Qator('birinchi'), new Qator('ikkinchi')];
$nusxa = clone $asl;
$nusxa->qatorlar[0]->matn = 'OZGARTIRILDI';
$nusxa->qatorlar[] = new Qator('uchinchi');
echo 'asl qatorlar soni: ', count($asl->qatorlar), PHP_EOL;
echo 'nusxa qatorlar soni: ', count($nusxa->qatorlar), PHP_EOL;
echo 'asl[0]: ', $asl->qatorlar[0]->matn, PHP_EOL;
echo 'nusxa[0]: ', $nusxa->qatorlar[0]->matn, PHP_EOL;
asl qatorlar soni: 2
nusxa qatorlar soni: 3
asl[0]: OZGARTIRILDI
nusxa[0]: OZGARTIRILDI
Massivning o'zi mustaqil (uchinchi qator faqat nusxada), lekin ichidagi obyektlar bo'lishiladi.
__clone() - chuqur nusxa #
PHP clone bajarilgach __clone() metodini chaqiradi. Ichki
obyektlarni aynan shu yerda nusxalash kerak.
final class Hujjat2
{
/** @var list<string> */
public array $boblar = [];
public function __construct(
public string $sarlavha,
public Uslub $uslub,
) {}
// clone dan keyin avtomatik chaqiriladi
public function __clone(): void
{
$this->uslub = clone $this->uslub;
}
public function korsat(): string
{
return sprintf('%-22s [%s]', $this->sarlavha, $this->uslub->korsat());
}
}
$andoza = new Hujjat2('Shartnoma andozasi', new Uslub('Arial', 11, true));
$nusxa = clone $andoza;
$nusxa->sarlavha = 'Shartnoma #1';
$nusxa->uslub->olcham = 20;
echo $andoza->korsat(), PHP_EOL;
echo $nusxa->korsat(), PHP_EOL;
echo 'bir xil obyektmi: ',
$andoza->uslub === $nusxa->uslub ? 'ha' : 'yoq', PHP_EOL;
Shartnoma andozasi [Arial 11pt qalin]
Shartnoma #1 [Arial 20pt qalin]
bir xil obyektmi: yoq
Massiv ichidagi obyektlar uchun halqa kerak:
final class Qator
{
public function __construct(public string $matn) {}
}
final class Jadval2
{
/** @var list<Qator> */
public array $qatorlar = [];
public function __clone(): void
{
// har bir elementni alohida nusxalaymiz
$this->qatorlar = array_map(
static fn(Qator $q): Qator => clone $q,
$this->qatorlar,
);
}
}
$asl = new Jadval2();
$asl->qatorlar = [new Qator('birinchi'), new Qator('ikkinchi')];
$nusxa = clone $asl;
$nusxa->qatorlar[0]->matn = 'OZGARTIRILDI';
echo 'asl[0]: ', $asl->qatorlar[0]->matn, PHP_EOL;
echo 'nusxa[0]: ', $nusxa->qatorlar[0]->matn, PHP_EOL;
asl[0]: birinchi
nusxa[0]: OZGARTIRILDI
readonly va clone - PHP 8.2 dagi cheklov #
final class IchkiRo
{
public function __construct(public int $son) {}
}
final class TashqiRo
{
public function __construct(
public readonly IchkiRo $ichki,
public readonly string $nom,
) {}
public function __clone(): void
{
// PHP 8.2 da bu ISHLAMAYDI
$this->ichki = clone $this->ichki;
}
}
try {
$b = clone new TashqiRo(new IchkiRo(1), 'a');
} catch (Error $e) {
echo get_class($e), ': ', $e->getMessage(), PHP_EOL;
}
echo 'PHP ', PHP_VERSION, PHP_EOL;
Error: Cannot modify readonly property TashqiRo::$ichki
PHP 8.2.12
readonly maydonni chuqur nusxalab bo'lmaydiBu bilib turib qo'yilgan cheklov edi: readonly maydon
faqat e'lon qilingan sohada, faqat bir marta yozilishi
mumkin - __clone() ichida ham yozib bo'lmaydi.
PHP 8.3 da bu o'zgardi: __clone() ichida readonly
maydonni qayta ishga tushirish mumkin bo'ldi.
PHP 8.2 da ishlaydigan yechimlar:
| Yechim | Izoh |
|---|---|
clone o'rniga yangi obyekt qaytaruvchi metod | bilanUslub(...) |
Ichki obyektni ham readonly qilish | Nusxalash umuman kerak emas |
readonly dan voz kechish | Eng oson, lekin kafolat yo'qoladi |
Birinchisi eng toza:
final class TashqiToza
{
public function __construct(
public readonly int $son,
public readonly string $nom,
) {}
// clone o'rniga - yangi obyekt qaytaruvchi metod
public function bilanSon(int $son): self
{
return new self($son, $this->nom);
}
public function bilanNom(string $nom): self
{
return new self($this->son, $nom);
}
public function korsat(): string
{
return "{$this->nom}={$this->son}";
}
}
$asl = new TashqiToza(1, 'andoza');
$yangi = $asl->bilanSon(42)->bilanNom('nusxa');
echo $asl->korsat(), PHP_EOL;
echo $yangi->korsat(), PHP_EOL;
andoza=1
nusxa=42
bilan...() uslubiBu wither deb ataladi (with... dan). U readonly
obyektlar bilan juda yaxshi ishlaydi:
- Asl obyekt hech qachon o'zgarmaydi
- Har chaqiruv yangi obyekt qaytaradi
- Zanjir qilib yozish mumkin
__clone()va uning nozikliklari kerak emas
5-bo'limdagi OzgarmasQuruvchi ham xuddi shu g'oyaga
asoslangan.
Prototip registri #
Ko'p andoza bo'lsa, ularni bir joyda saqlash qulay:
final class AndozaRegistri
{
/** @var array<string, Hujjat2> */
private array $andozalar = [];
public function qoy(string $kalit, Hujjat2 $andoza): void
{
$this->andozalar[$kalit] = $andoza;
}
public function yarat(string $kalit): Hujjat2
{
if (!isset($this->andozalar[$kalit])) {
throw new InvalidArgumentException("andoza yoq: {$kalit}");
}
// registrdagi asl nusxa hech qachon berilmaydi
return clone $this->andozalar[$kalit];
}
/** @return list<string> */
public function kalitlar(): array
{
return array_keys($this->andozalar);
}
}
$registr = new AndozaRegistri();
$registr->qoy('shartnoma',
new Hujjat2('Shartnoma', new Uslub('Arial', 11, true)));
$registr->qoy('hisobot',
new Hujjat2('Hisobot', new Uslub('Times', 12)));
echo 'andozalar: ', implode(', ', $registr->kalitlar()), PHP_EOL;
$h1 = $registr->yarat('shartnoma');
$h1->sarlavha = 'Shartnoma #7';
$h1->uslub->olcham = 14;
// registrdagi asl nusxa o'zgarmadi
$h2 = $registr->yarat('shartnoma');
echo $h1->korsat(), PHP_EOL;
echo $h2->korsat(), PHP_EOL;
andozalar: shartnoma, hisobot
Shartnoma #7 [Arial 14pt qalin]
Shartnoma [Arial 11pt qalin]
serialize orqali chuqur nusxa #
Qo'lda __clone() yozish zerikarli bo'lsa, qisqa yo'l bor:
final class Ichki3
{
public function __construct(public int $son) {}
}
final class Tashqi3
{
/** @var list<Ichki3> */
public array $bolalar = [];
}
$asl = new Tashqi3();
$asl->bolalar = [new Ichki3(1), new Ichki3(2)];
// butun grafni chuqur nusxalaydi
$nusxa = unserialize(serialize($asl));
$nusxa->bolalar[0]->son = 999;
echo 'asl: ', $asl->bolalar[0]->son, PHP_EOL;
echo 'nusxa: ', $nusxa->bolalar[0]->son, PHP_EOL;
echo 'turi: ', $nusxa::class, PHP_EOL;
asl: 1
nusxa: 999
turi: Tashqi3
| Cheklov | Izoh |
|---|---|
Yopilmalar (Closure) serializatsiya qilinmaydi | Fatal xato |
| Resurslar (fayl, ulanish) yo'qoladi | null yoki xato |
__wakeup / __sleep chaqiriladi | Kutilmagan yon ta'sir |
| Sekin | Butun graf matnga aylanadi va qaytadi |
Va eng muhimi: hech qachon ishonchsiz manbadan kelgan
matnni unserialize qilmang. Bu obyekt injeksiyasi
zaifligi - hujumchi sizning sinflaringizdan zanjir yig'ib
kod bajarishga erishishi mumkin.
Bu yerdagi misolda serialize natijasi bizning o'z
kodimizdan chiqqan, shuning uchun xavfsiz.
- Ichida obyekt maydoni bo'lgan sinf yozing va
cloneqiling. - Nusxadagi ichki obyektni o'zgartiring - aslida ham o'zgardimi?
===bilan ikkala ichki obyektni solishtiring.__clone()qo'shib chuqur nusxa qiling.- Obyektlar massivi uchun
array_mapbilan nusxalang. readonlymaydonni__clone()da o'zgartirib ko'ring va xatoni yozing.- Xuddi shu sinfni
bilan...()uslubiga o'tkazing. - Andoza registrini yozing va undan uchta hujjat yarating.
serialize/unserializebilan chuqur nusxa oling.- Bu usulning uchta cheklovini sanang.
Xulosa #
- Prototype - obyektni noldan qurmasdan, nusxadan yaratish naqshi.
- PHP da
clonetayyor - lekin u sayoz ishlaydi. - Skalar maydonlar mustaqil ko'chiriladi, obyekt maydonlari bo'lishiladi.
- Massivning o'zi mustaqil, ichidagi obyektlar esa yo'q.
__clone()-clonedan keyin avtomatik chaqiriladigan metod.- Chuqur nusxa uchun ichki obyektlarni
__clone()da qo'lda nusxalang. - Obyektlar massivi uchun
array_mapbilan har elementnicloneqiling. - PHP 8.2 da
readonlymaydonni__clone()da o'zgartirib bo'lmaydi (8.3 da mumkin). readonlysinflar uchunbilan...()metodlari (wither) toza yechim.serialize/unserializechuqur nusxa beradi, lekin sekin va cheklovlari ko'p.
Keyingi bo'limda strukturaviy naqshlarni boshlaymiz - birinchisi Adapter.
O‘qish tarixini saqlamoqchimisiz?
Tizimga kirsangiz, tugatgan bo‘limlaringiz saqlanadi va qoldirgan joyingizdan davom etasiz.
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.