5-bo‘lim
Builder
Ko'p qismli obyektni bosqichma-bosqich qurish - va PHP 8 da bu qachon kerak emas.
Ushbu bo‘lim mundarijasi
Builder - murakkab obyektni bosqichma-bosqich qurish naqshi. Har bosqich ixtiyoriy, oxirida esa tayyor obyekt olinadi.
Muammo: uzun konstruktor #
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;
Husanboy / chegirma: 15%
null, null, null, false - o'qib bo'lmaydiBu teleskopik konstruktor deb ataladi: parametrlar soni o'sib boradi va chaqiruv joyi mazmunsiz qiymatlarga to'lib ketadi.
Muammolar:
| Muammo | Misol |
|---|---|
| Tartibni yodlash kerak | $telefon uchinchimi, to'rtinchimi? |
Mazmunsiz null lar | null, null, null, false |
Ikki string ni almashtirib qo'yish oson | Manzil telefon o'rniga tushadi |
| Tekshiruv qayerda | Konstruktor ichida shishib ketadi |
PHP 8: nomlangan argumentlar #
Naqshga o'tishdan oldin - tilning o'z yechimi:
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);
Husanboy / chegirma: 15%
NULL
bool(false)
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:
| Sabab | Izoh |
|---|---|
| Qurish bosqichma-bosqich boradi | Qismlar turli joyda qo'shiladi |
| Har bosqich oldingisiga bog'liq | Shart qo'shishdan oldin jadval kerak |
| Oxirida tekshiruv kerak | Majburiy qismlar to'ldirilganmi |
| Bir xil qurilmadan turli natija | SQL 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.
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);
}
}
$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;
SELECT id, mijoz, jami FROM buyurtmalar WHERE jami > ? AND holat = ? ORDER BY jami DESC LIMIT 10
[100000,"tolangan"]
Eng oddiy holat ham ishlaydi:
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;
SELECT * FROM mijozlar
(parametrsiz)
SELECT ism FROM mijozlar WHERE shahar = ? AND yosh > ?
["Namangan",18]
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 #
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;
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.
O'zgarmas (immutable) Builder #
Yuqoridagi quruvchi o'zini o'zgartiradi. Bu bir nozik xatoga olib keladi:
$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;
SELECT * FROM mahsulotlar WHERE narx < ?
SELECT * FROM mahsulotlar WHERE narx < ? AND narx > ?
$asos ni ikki marta ishlatib bo'lmadiIkkinchi 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.
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;
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'zgaruvchan | O'zgarmas | |
|---|---|---|
| Xotira | Bitta obyekt | Har qadamda yangi |
| Qayta ishlatish | Xavfli | Xavfsiz |
| Tezlik | Tezroq | Biroz sekinroq |
| Qachon | Bir martalik qurish | Andoza sifatida saqlanadi |
Builder kerak bo'lmagan holat #
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);
(3, 4)
NuqtaQuruvchi()->x(3)->y(4)->qur() - bu new Nuqta(3, 4)
dan uzunroq, sekinroq va tushunarsizroq.
Amaliy chegara taxminan shunday:
| Parametr soni | Tavsiya |
|---|---|
| 1-3 | Oddiy konstruktor |
| 4-8, hammasi ma'lum | Nomlangan argumentlar |
| Soni noma'lum yoki bosqichli | Builder |
Va yodda tuting: ko'p parametr ko'pincha sinf juda ko'p ish qilayotganini bildiradi. Builder yozishdan oldin sinfni bo'lish mumkinmi - shuni tekshiring.
- Sakkiz parametrli konstruktor yozing va uni chaqiring.
- Xuddi shu chaqiruvni nomlangan argumentlar bilan qayta yozing.
SorovQuruvchigaguruhla()metodini qo'shing.qur()ga ikkinchi tekshiruv qo'shing (masalanchegaramanfiy bo'lmasin).- Shartlarni
foreachichida qo'shing - konstruktor bilan buni qilib ko'ring. $asosni ikki marta ishlatib, ifloslanish xatosini takrorlang.OzgarmasQuruvchigatartibla()qo'shing.- O'zgaruvchan va o'zgarmas variantlarni jadval bilan solishtiring.
- Builder kerak emas bo'lgan sinfni kodingizdan toping.
- 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
$thisni 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.
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.