5-bo‘lim

Builder

Ko'p qismli obyektni bosqichma-bosqich qurish - va PHP 8 da bu qachon kerak emas.

🕑 12 daqiqa o‘qish 📄 791 so‘z 👁 2 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Muammo: uzun konstruktor
  2. PHP 8: nomlangan argumentlar
  3. Builder haqiqatan kerak bo'lgan joy
  4. Tekshiruv qur() ichida
  5. O'zgarmas (immutable) Builder
  6. Builder kerak bo'lmagan holat
  7. Xulosa

Builder - murakkab obyektni bosqichma-bosqich qurish naqshi. Har bosqich ixtiyoriy, oxirida esa tayyor obyekt olinadi.

Muammo: uzun konstruktor #

PHP
final readonly class Buyurtma
{
    public function __construct(
        public string $mijoz,
        public array $mahsulotlar,
        public ?string $manzil = null,
        public ?string $telefon = null,
        public ?string $izoh = null,
        public bool $shoshilinch = false,
        public int $chegirma = 0,
        public ?string $sovga = null,
    ) {}
}

// Faqat mijoz, mahsulot va chegirma kerak edi
$b = new Buyurtma('Husanboy', ['Klaviatura'], null, null, null, false, 15);

echo $b->mijoz, ' / chegirma: ', $b->chegirma, '%', PHP_EOL;
Natija
Husanboy / chegirma: 15%
null, null, null, false - o'qib bo'lmaydi

Bu teleskopik konstruktor deb ataladi: parametrlar soni o'sib boradi va chaqiruv joyi mazmunsiz qiymatlarga to'lib ketadi.

Muammolar:

MuammoMisol
Tartibni yodlash kerak$telefon uchinchimi, to'rtinchimi?
Mazmunsiz null larnull, null, null, false
Ikki string ni almashtirib qo'yish osonManzil telefon o'rniga tushadi
Tekshiruv qayerdaKonstruktor ichida shishib ketadi

PHP 8: nomlangan argumentlar #

Naqshga o'tishdan oldin - tilning o'z yechimi:

PHP
final readonly class Buyurtma
{
    public function __construct(
        public string $mijoz,
        public array $mahsulotlar,
        public ?string $manzil = null,
        public ?string $telefon = null,
        public ?string $izoh = null,
        public bool $shoshilinch = false,
        public int $chegirma = 0,
        public ?string $sovga = null,
    ) {}
}

$b = new Buyurtma(
    mijoz: 'Husanboy',
    mahsulotlar: ['Klaviatura'],
    chegirma: 15,
);

echo $b->mijoz, ' / chegirma: ', $b->chegirma, '%', PHP_EOL;
var_dump($b->manzil, $b->shoshilinch);
Natija
Husanboy / chegirma: 15%
NULL
bool(false)
Ko'p holatda shu yetarli

Nomlangan argumentlar teleskopik konstruktor muammosining kattasini yechadi: tartib muhim emas, keraksiz null lar yozilmaydi, nom o'zi tushuntiradi.

Shuning uchun PHP 8 da Builder avvalgidan kamroq kerak.

Builder qachon hali ham kerak:

SababIzoh
Qurish bosqichma-bosqich boradiQismlar turli joyda qo'shiladi
Har bosqich oldingisiga bog'liqShart qo'shishdan oldin jadval kerak
Oxirida tekshiruv kerakMajburiy qismlar to'ldirilganmi
Bir xil qurilmadan turli natijaSQL va uning parametrlari

Bizning Buyurtma misolida bularning hech biri yo'q - demak nomlangan argumentlar to'g'ri tanlov.

Builder haqiqatan kerak bo'lgan joy #

SQL so'rovini quramiz. Bu yerda bosqichlar ketma-ket to'planadi va oxirida ikkita natija chiqadi: so'rov matni va parametrlar massivi.

PHP
final readonly class Sorov
{
    public function __construct(
        public string $sql,
        public array $parametrlar,
    ) {}

    public function korsat(): string
    {
        $p = $this->parametrlar === []
            ? '(parametrsiz)'
            : json_encode($this->parametrlar, JSON_UNESCAPED_UNICODE);
        return $this->sql . "\n  " . $p;
    }
}

final class SorovQuruvchi
{
    private string $jadval = '';
    /** @var list<string> */
    private array $ustunlar = [];
    /** @var list<string> */
    private array $shartlar = [];
    /** @var list<mixed> */
    private array $qiymatlar = [];
    private ?string $tartib = null;
    private ?int $chegara = null;

    public function jadvaldan(string $jadval): self
    {
        $this->jadval = $jadval;
        return $this;
    }

    public function ustun(string ...$ustunlar): self
    {
        foreach ($ustunlar as $u) {
            $this->ustunlar[] = $u;
        }
        return $this;
    }

    public function shart(string $ifoda, mixed $qiymat): self
    {
        $this->shartlar[] = $ifoda;
        $this->qiymatlar[] = $qiymat;
        return $this;
    }

    public function tartibla(string $ustun, string $yonalish = 'ASC'): self
    {
        $this->tartib = $ustun . ' ' . strtoupper($yonalish);
        return $this;
    }

    public function chegara(int $soni): self
    {
        $this->chegara = $soni;
        return $this;
    }

    public function qur(): Sorov
    {
        if ($this->jadval === '') {
            throw new LogicException('jadval korsatilmagan');
        }

        $ustun = $this->ustunlar === [] ? '*' : implode(', ', $this->ustunlar);
        $sql = "SELECT {$ustun} FROM {$this->jadval}";

        if ($this->shartlar !== []) {
            $sql .= ' WHERE ' . implode(' AND ', $this->shartlar);
        }
        if ($this->tartib !== null) {
            $sql .= ' ORDER BY ' . $this->tartib;
        }
        if ($this->chegara !== null) {
            $sql .= ' LIMIT ' . $this->chegara;
        }

        return new Sorov($sql, $this->qiymatlar);
    }
}
PHP
$sorov = (new SorovQuruvchi())
    ->jadvaldan('buyurtmalar')
    ->ustun('id', 'mijoz', 'jami')
    ->shart('jami > ?', 100000)
    ->shart('holat = ?', 'tolangan')
    ->tartibla('jami', 'desc')
    ->chegara(10)
    ->qur();

echo $sorov->korsat(), PHP_EOL;
Natija
SELECT id, mijoz, jami FROM buyurtmalar WHERE jami > ? AND holat = ? ORDER BY jami DESC LIMIT 10
  [100000,"tolangan"]

Eng oddiy holat ham ishlaydi:

PHP
echo (new SorovQuruvchi())->jadvaldan('mijozlar')->qur()->korsat(), PHP_EOL;

// shartlar soni oldindan noma'lum bo'lsa
$filtrlar = ['shahar = ?' => 'Namangan', 'yosh > ?' => 18];

$q = (new SorovQuruvchi())->jadvaldan('mijozlar')->ustun('ism');
foreach ($filtrlar as $ifoda => $qiymat) {
    $q->shart($ifoda, $qiymat);
}
echo $q->qur()->korsat(), PHP_EOL;
Natija
SELECT * FROM mijozlar
  (parametrsiz)
SELECT ism FROM mijozlar WHERE shahar = ? AND yosh > ?
  ["Namangan",18]
Bu naqshning asosiy kuchi - halqa ichida qurish

Yuqoridagi foreach ga e'tibor bering: shartlar soni oldindan noma'lum.

Buni konstruktor bilan qilib bo'lmaydi - konstruktor bir marta chaqiriladi va hamma narsani bir vaqtda talab qiladi.

Builder esa obyektni ochiq qoldiradi: kod turli joylarda unga qism qo'shib boradi, qur() chaqirilgandagina obyekt yopiladi.

Bu Builder ni nomlangan argumentlardan ajratib turadigan asosiy farq.

Tekshiruv qur() ichida #

PHP
try {
    (new SorovQuruvchi())->ustun('id')->qur();
} catch (LogicException $x) {
    echo 'Xato: ', $x->getMessage(), PHP_EOL;
}

// jadval qo'shilgach - ishlaydi
echo (new SorovQuruvchi())->ustun('id')
    ->jadvaldan('mijozlar')->qur()->sql, PHP_EOL;
Natija
Xato: jadval korsatilmagan
SELECT id FROM mijozlar

Majburiy qismlarni tekshirish uchun yagona joy bor - qur(). Konstruktorda buni qilishning iloji yo'q, chunki qismlar hali kelmagan.

Builder - obyekt yopilgunicha ochiq turadi Konstruktor new Sorov($sql, $par, $tartib, ...) Hamma qism BIR VAQTDA kerak Halqa ichida qurib bo'lmaydi Builder jadvaldan ustun shart shart Qismlar turli joyda qo'shiladi Soni oldindan noma'lum bo'lishi mumkin qur() tekshiradi + Sorov qaytaradi Qachon Builder, qachon nomlangan argument Qismlar soni oldindan ma'lum bo'lsa - new Sorov(sql: ..., par: ...). Noma'lum bo'lsa - Builder.
Builder ning haqiqiy foydasi - qurish jarayonini bo'laklarga ajratish

O'zgarmas (immutable) Builder #

Yuqoridagi quruvchi o'zini o'zgartiradi. Bu bir nozik xatoga olib keladi:

PHP
$asos = (new SorovQuruvchi())->jadvaldan('mahsulotlar');

$arzon = $asos->shart('narx < ?', 50000)->qur();
$qimmat = $asos->shart('narx > ?', 500000)->qur();

echo $arzon->sql, PHP_EOL;
echo $qimmat->sql, PHP_EOL;
Natija
SELECT * FROM mahsulotlar WHERE narx < ?
SELECT * FROM mahsulotlar WHERE narx < ? AND narx > ?
$asos ni ikki marta ishlatib bo'lmadi

Ikkinchi so'rovga birinchisining sharti ham qo'shilib qoldi - narx < ? AND narx > ?. Bunday so'rov hech qachon natija bermaydi.

Sabab: shart() $this ni o'zgartiradi va o'sha obyektni qaytaradi. $asos ikkala zanjir uchun bitta obyekt.

Agar quruvchini "andoza" sifatida qayta ishlatmoqchi bo'lsangiz - har chaqiruvda nusxa qaytaring.

PHP
final class OzgarmasQuruvchi
{
    /**
     * @param list<string> $shartlar
     * @param list<mixed> $qiymatlar
     */
    public function __construct(
        private readonly string $jadval = '',
        private readonly array $shartlar = [],
        private readonly array $qiymatlar = [],
    ) {}

    public function jadvaldan(string $jadval): self
    {
        return new self($jadval, $this->shartlar, $this->qiymatlar);
    }

    public function shart(string $ifoda, mixed $qiymat): self
    {
        return new self(
            $this->jadval,
            [...$this->shartlar, $ifoda],
            [...$this->qiymatlar, $qiymat],
        );
    }

    public function qur(): string
    {
        $sql = "SELECT * FROM {$this->jadval}";
        if ($this->shartlar !== []) {
            $sql .= ' WHERE ' . implode(' AND ', $this->shartlar);
        }
        return $sql;
    }
}

$asos = (new OzgarmasQuruvchi())->jadvaldan('mahsulotlar');

echo $asos->shart('narx < ?', 50000)->qur(), PHP_EOL;
echo $asos->shart('narx > ?', 500000)->qur(), PHP_EOL;
echo $asos->qur(), PHP_EOL;
Natija
SELECT * FROM mahsulotlar WHERE narx < ?
SELECT * FROM mahsulotlar WHERE narx > ?
SELECT * FROM mahsulotlar

Endi $asos hech qachon o'zgarmaydi va uni cheksiz marta qayta ishlatish mumkin.

O'zgaruvchanO'zgarmas
XotiraBitta obyektHar qadamda yangi
Qayta ishlatishXavfliXavfsiz
TezlikTezroqBiroz sekinroq
QachonBir martalik qurishAndoza sifatida saqlanadi

Builder kerak bo'lmagan holat #

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

$n = new Nuqta(x: 3, y: 4);
printf("(%d, %d)%s", $n->x, $n->y, PHP_EOL);
Natija
(3, 4)
Ikki parametr uchun Builder yozmang

NuqtaQuruvchi()->x(3)->y(4)->qur() - bu new Nuqta(3, 4) dan uzunroq, sekinroq va tushunarsizroq.

Amaliy chegara taxminan shunday:

Parametr soniTavsiya
1-3Oddiy konstruktor
4-8, hammasi ma'lumNomlangan argumentlar
Soni noma'lum yoki bosqichliBuilder

Va yodda tuting: ko'p parametr ko'pincha sinf juda ko'p ish qilayotganini bildiradi. Builder yozishdan oldin sinfni bo'lish mumkinmi - shuni tekshiring.

Amaliy topshiriq
  1. Sakkiz parametrli konstruktor yozing va uni chaqiring.
  2. Xuddi shu chaqiruvni nomlangan argumentlar bilan qayta yozing.
  3. SorovQuruvchi ga guruhla() metodini qo'shing.
  4. qur() ga ikkinchi tekshiruv qo'shing (masalan chegara manfiy bo'lmasin).
  5. Shartlarni foreach ichida qo'shing - konstruktor bilan buni qilib ko'ring.
  6. $asos ni ikki marta ishlatib, ifloslanish xatosini takrorlang.
  7. OzgarmasQuruvchi ga tartibla() qo'shing.
  8. O'zgaruvchan va o'zgarmas variantlarni jadval bilan solishtiring.
  9. Builder kerak emas bo'lgan sinfni kodingizdan toping.
  10. Ko'p parametrli sinfni ikkiga bo'lish mumkinmi - tekshiring.

Xulosa #

  • Builder - obyektni bosqichma-bosqich qurish naqshi.
  • Muammo - teleskopik konstruktor: null, null, null, false.
  • PHP 8 da nomlangan argumentlar bu muammoning kattasini yechadi.
  • Builder qachon hali ham kerak: qismlar soni oldindan noma'lum yoki turli joyda qo'shiladi.
  • qur() - majburiy qismlarni tekshirish uchun yagona joy.
  • Har metod $this ni qaytarsa, zanjir hosil bo'ladi.
  • O'zini o'zgartiruvchi quruvchini qayta ishlatib bo'lmaydi - shartlar to'planib qoladi.
  • O'zgarmas variant har qadamda yangi obyekt qaytaradi va andoza sifatida xavfsiz.
  • 1-3 parametr uchun oddiy konstruktor, 4-8 uchun nomlangan argumentlar yetadi.
  • Ko'p parametr - ko'pincha sinfni bo'lish kerakligining belgisi.

Keyingi bo'limda eng mashhur va eng ko'p tanqid qilinadigan naqsh - Singleton 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.