7-bo‘lim

Prototype

Yangi obyektni noldan qurish o'rniga tayyoridan nusxa olish - va nusxa qanchalik chuqur bo'lishi.

🕑 14 daqiqa o‘qish 📄 749 so‘z 👁 1 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Muammo: sozlangan obyektni takrorlash
  2. clone - sayoz nusxa
  3. __clone() - chuqur nusxa
  4. readonly va clone - PHP 8.2 dagi cheklov
  5. Prototip registri
  6. serialize orqali chuqur nusxa
  7. Xulosa

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 #

PHP
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:

PHP
$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;
Natija
Shartnoma andozasi [Arial 11pt qalin] boblar: Umumiy qoidalar, Toʻlov shartlari
Shartnoma #1 [Arial 11pt qalin] boblar: Umumiy qoidalar, Toʻlov shartlari
Andoza o'zgarsa - hamma joyni tahrirlash kerak

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 #

PHP
$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;
Natija
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:

PHP
$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;
Natija
andoza: Arial 20pt qalin
nusxa:  Arial 20pt qalin
bir xil obyektmi: ha
clone sayoz nusxa oladi

Andozaning 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 turiclone da nima bo'ladi
int, string, boolQiymat ko'chiriladi - mustaqil
array (skalarlardan)Qiymat ko'chiriladi - mustaqil
ObyektHavola ko'chiriladi - bo'lishiladi
Obyektlardan iborat massivMassiv yangi, ichidagilar bo'lishiladi

Bu PHP dagi eng ko'p uchraydigan nozik xatolardan biri.

Massiv ichidagi obyektlar ham xuddi shunday:

PHP
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;
Natija
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.

Sayoz nusxa va chuqur nusxa clone (sayoz) __clone() bilan (chuqur) Hujjat andoza Hujjat nusxa Uslub BITTA obyekt Hujjat andoza Hujjat nusxa Uslub o'z nusxasi Uslub o'z nusxasi Qoida Obyekt maydonlari bo'lgan sinfda __clone() yozing - aks holda nusxa asl bilan bog'liq qoladi. Faqat skalar maydonlar bo'lsa - clone o'zi yetarli.
clone ni faqat skalar maydonli sinflarda xavfsiz deb hisoblang

__clone() - chuqur nusxa #

PHP clone bajarilgach __clone() metodini chaqiradi. Ichki obyektlarni aynan shu yerda nusxalash kerak.

PHP
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());
    }
}
PHP
$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;
Natija
Shartnoma andozasi     [Arial 11pt qalin]
Shartnoma #1           [Arial 20pt qalin]
bir xil obyektmi: yoq

Massiv ichidagi obyektlar uchun halqa kerak:

PHP
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;
Natija
asl[0]:   birinchi
nusxa[0]: OZGARTIRILDI

readonly va clone - PHP 8.2 dagi cheklov #

PHP
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;
Natija
Error: Cannot modify readonly property TashqiRo::$ichki
PHP 8.2.12
PHP 8.2 da readonly maydonni chuqur nusxalab bo'lmaydi

Bu 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:

YechimIzoh
clone o'rniga yangi obyekt qaytaruvchi metodbilanUslub(...)
Ichki obyektni ham readonly qilishNusxalash umuman kerak emas
readonly dan voz kechishEng oson, lekin kafolat yo'qoladi

Birinchisi eng toza:

PHP
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;
Natija
andoza=1
nusxa=42
bilan...() uslubi

Bu 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:

PHP
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;
Natija
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:

PHP
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;
Natija
asl:   1
nusxa: 999
turi:  Tashqi3
Bu usulni ehtiyot bilan ishlating
CheklovIzoh
Yopilmalar (Closure) serializatsiya qilinmaydiFatal xato
Resurslar (fayl, ulanish) yo'qoladinull yoki xato
__wakeup / __sleep chaqiriladiKutilmagan yon ta'sir
SekinButun 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.

Amaliy topshiriq
  1. Ichida obyekt maydoni bo'lgan sinf yozing va clone qiling.
  2. Nusxadagi ichki obyektni o'zgartiring - aslida ham o'zgardimi?
  3. === bilan ikkala ichki obyektni solishtiring.
  4. __clone() qo'shib chuqur nusxa qiling.
  5. Obyektlar massivi uchun array_map bilan nusxalang.
  6. readonly maydonni __clone() da o'zgartirib ko'ring va xatoni yozing.
  7. Xuddi shu sinfni bilan...() uslubiga o'tkazing.
  8. Andoza registrini yozing va undan uchta hujjat yarating.
  9. serialize/unserialize bilan chuqur nusxa oling.
  10. Bu usulning uchta cheklovini sanang.

Xulosa #

  • Prototype - obyektni noldan qurmasdan, nusxadan yaratish naqshi.
  • PHP da clone tayyor - lekin u sayoz ishlaydi.
  • Skalar maydonlar mustaqil ko'chiriladi, obyekt maydonlari bo'lishiladi.
  • Massivning o'zi mustaqil, ichidagi obyektlar esa yo'q.
  • __clone() - clone dan keyin avtomatik chaqiriladigan metod.
  • Chuqur nusxa uchun ichki obyektlarni __clone() da qo'lda nusxalang.
  • Obyektlar massivi uchun array_map bilan har elementni clone qiling.
  • PHP 8.2 da readonly maydonni __clone() da o'zgartirib bo'lmaydi (8.3 da mumkin).
  • readonly sinflar uchun bilan...() metodlari (wither) toza yechim.
  • serialize/unserialize chuqur nusxa beradi, lekin sekin va cheklovlari ko'p.

Keyingi bo'limda strukturaviy naqshlarni boshlaymiz - birinchisi Adapter.

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.