12-bo‘lim
Proxy
Obyekt oldiga o'rin bosar qo'yish - kechiktirish, nazorat va keshlash uchun.
Ushbu bo‘lim mundarijasi
Proxy - haqiqiy obyekt bilan mijoz orasida turadigan o'rin bosar. U bir xil interfeysni bajaradi, lekin chaqiruvni nazorat qiladi: kechiktiradi, tekshiradi, keshlaydi yoki umuman o'tkazmaydi.
To'rt xil proksi #
| Tur | Nima qiladi |
|---|---|
| Virtual | Qimmat obyektni kerak bo'lgunicha yaratmaydi |
| Himoya | Huquqni tekshiradi, ruxsatsizni o'tkazmaydi |
| Kesh (smart) | Natijani eslab qoladi |
| Uzoq (remote) | Tarmoq orqali chaqirishni yashiradi |
Birinchi uchtasini kodda ko'ramiz.
Virtual proksi: kechiktirilgan yaratish #
interface Hisobot
{
public function sarlavha(): string;
public function tana(): string;
}
final class OgirHisobot implements Hisobot
{
private string $malumot;
public function __construct(private readonly string $nom)
{
// Konstruktor QIMMAT - bazadan o'qiydi, hisoblaydi
echo " [ogir] {$nom} tayyorlanmoqda...", PHP_EOL;
$qatorlar = [];
for ($i = 1; $i <= 3; $i++) {
$qatorlar[] = sprintf(' %d-qator: %d', $i, $i * 1000);
}
$this->malumot = implode("\n", $qatorlar);
}
public function sarlavha(): string
{
return 'Hisobot: ' . $this->nom;
}
public function tana(): string
{
return $this->malumot;
}
}
Muammo: sarlavhani ko'rsatish uchun ham butun hisobot tayyorlanadi.
$hisobotlar = [
new OgirHisobot('yanvar'),
new OgirHisobot('fevral'),
new OgirHisobot('mart'),
];
echo '--- royxat', PHP_EOL;
foreach ($hisobotlar as $h) {
echo $h->sarlavha(), PHP_EOL;
}
[ogir] yanvar tayyorlanmoqda...
[ogir] fevral tayyorlanmoqda...
[ogir] mart tayyorlanmoqda...
--- royxat
Hisobot: yanvar
Hisobot: fevral
Hisobot: mart
Uchala hisobot ham to'liq hisoblandi, lekin faqat sarlavha kerak edi.
final class KechikkanHisobot implements Hisobot
{
private ?OgirHisobot $haqiqiy = null;
public function __construct(private readonly string $nom) {}
// sarlavha uchun haqiqiy obyekt KERAK EMAS
public function sarlavha(): string
{
return 'Hisobot: ' . $this->nom;
}
// tana so'ralgandagina yaratamiz
public function tana(): string
{
$this->haqiqiy ??= new OgirHisobot($this->nom);
return $this->haqiqiy->tana();
}
public function yuklanganmi(): bool
{
return $this->haqiqiy !== null;
}
}
$hisobotlar = [
new KechikkanHisobot('yanvar'),
new KechikkanHisobot('fevral'),
new KechikkanHisobot('mart'),
];
echo '--- royxat', PHP_EOL;
foreach ($hisobotlar as $h) {
echo $h->sarlavha(), PHP_EOL;
}
echo '--- faqat bittasini ochamiz', PHP_EOL;
echo $hisobotlar[1]->tana(), PHP_EOL;
echo '--- yuklanganlar', PHP_EOL;
foreach ($hisobotlar as $h) {
printf('%-20s %s%s', $h->sarlavha(),
$h->yuklanganmi() ? 'yuklangan' : 'yuklanmagan', PHP_EOL);
}
--- royxat
Hisobot: yanvar
Hisobot: fevral
Hisobot: mart
--- faqat bittasini ochamiz
[ogir] fevral tayyorlanmoqda...
1-qator: 1000
2-qator: 2000
3-qator: 3000
--- yuklanganlar
Hisobot: yanvar yuklanmagan
Hisobot: fevral yuklangan
Hisobot: mart yuklanmagan
ORM dan obyekt olganingizda, uning bog'liq obyektlari odatda darhol yuklanmaydi:
$buyurtma = $ombor->top(42);
echo $buyurtma->mijoz->ism; // SO'ROV AYNAN SHU YERDA ketadi
$buyurtma->mijoz - haqiqiy Mijoz emas, uning proksisi
(ghost object). Faqat maydoniga murojaat qilinganda baza
so'rovi yuboriladi.
Buning teskari tomoni ham bor - mashhur N+1 muammosi:
| Kod | Nechta so'rov |
|---|---|
| 100 ta buyurtmani olish | 1 |
Har birida ->mijoz->ism | +100 |
Shuning uchun ORM lar with('mijoz') kabi "oldindan yuklash"
imkonini beradi. Virtual proksi qulaylik beradi, lekin
so'rovlarni ko'zdan yashiradi.
Himoya proksisi #
final readonly class Foydalanuvchi
{
public function __construct(
public string $ism,
public string $rol,
) {}
}
final class HimoyalanganHisobot implements Hisobot
{
public function __construct(
private readonly Hisobot $ichki,
private readonly Foydalanuvchi $kim,
) {}
public function sarlavha(): string
{
return $this->ichki->sarlavha();
}
public function tana(): string
{
if (!in_array($this->kim->rol, ['admin', 'buxgalter'], true)) {
throw new RuntimeException(
"{$this->kim->ism} ({$this->kim->rol}) uchun ruxsat yoq"
);
}
return $this->ichki->tana();
}
}
$asos = new KechikkanHisobot('yanvar');
$kimlar = [
new Foydalanuvchi('Husanboy', 'admin'),
new Foydalanuvchi('Malika', 'buxgalter'),
new Foydalanuvchi('Nodira', 'mehmon'),
];
foreach ($kimlar as $kim) {
$proxy = new HimoyalanganHisobot($asos, $kim);
echo '--- ', $kim->ism, ' (', $kim->rol, ')', PHP_EOL;
echo $proxy->sarlavha(), PHP_EOL;
try {
echo $proxy->tana(), PHP_EOL;
} catch (RuntimeException $x) {
echo 'RAD ETILDI: ', $x->getMessage(), PHP_EOL;
}
}
--- Husanboy (admin)
Hisobot: yanvar
[ogir] yanvar tayyorlanmoqda...
1-qator: 1000
2-qator: 2000
3-qator: 3000
--- Malika (buxgalter)
Hisobot: yanvar
1-qator: 1000
2-qator: 2000
3-qator: 3000
--- Nodira (mehmon)
Hisobot: yanvar
RAD ETILDI: Nodira (mehmon) uchun ruxsat yoq
Diqqat: "og'ir" hisobot bir marta tayyorlandi - ikkinchi
foydalanuvchi uchun KechikkanHisobot uni qayta ishlatdi.
Ikki proksi birga ishladi.
Kesh proksisi #
final class SekinBaza
{
private int $sorovlar = 0;
public function mahsulot(int $id): string
{
$this->sorovlar++;
echo " [baza] SELECT ... WHERE id={$id}", PHP_EOL;
return match ($id) {
1 => 'Klaviatura',
2 => 'Sichqoncha',
default => 'Nomalum',
};
}
public function sorovlar(): int
{
return $this->sorovlar;
}
}
final class KeshProksi
{
/** @var array<int, string> */
private array $kesh = [];
private int $tegdi = 0;
public function __construct(private readonly SekinBaza $baza) {}
public function mahsulot(int $id): string
{
if (isset($this->kesh[$id])) {
$this->tegdi++;
echo " [kesh] {$id} keshdan", PHP_EOL;
return $this->kesh[$id];
}
return $this->kesh[$id] = $this->baza->mahsulot($id);
}
public function statistika(): string
{
return sprintf('baza sorovlari: %d, keshdan: %d',
$this->baza->sorovlar(), $this->tegdi);
}
}
$baza = new SekinBaza();
$proxy = new KeshProksi($baza);
foreach ([1, 2, 1, 1, 2, 3] as $id) {
$nom = $proxy->mahsulot($id);
echo $id, ' -> ', $nom, PHP_EOL;
}
echo $proxy->statistika(), PHP_EOL;
[baza] SELECT ... WHERE id=1
1 -> Klaviatura
[baza] SELECT ... WHERE id=2
2 -> Sichqoncha
[kesh] 1 keshdan
1 -> Klaviatura
[kesh] 1 keshdan
1 -> Klaviatura
[kesh] 2 keshdan
2 -> Sichqoncha
[baza] SELECT ... WHERE id=3
3 -> Nomalum
baza sorovlari: 3, keshdan: 3
Kesh qo'shish oson, uni to'g'ri qilish qiyin:
| Savol | Nima bo'ladi javob noto'g'ri bo'lsa |
|---|---|
| Kesh qachon eskiradi? | Foydalanuvchi eski narxni ko'radi |
| Ma'lumot o'zgarsa kim tozalaydi? | Nomuvofiqlik |
| Xotira chegarasi bormi? | Xotira tugaydi |
null natija keshlanadimi? | Har safar bazaga borish |
Yuqoridagi misolda id=99 uchun 'Nomalum' keshlanadi -
va agar keyin bu mahsulot bazaga qo'shilsa, proksi hali ham
eski javobni beradi.
Bu kesh eskirishi muammosi va u dasturlashdagi eng mashhur qiyin masalalardan biri.
PHP ning sehrli metodlari bilan proksi #
final class UmumiyProksi
{
/** @var list<string> */
private array $chaqiruvlar = [];
public function __construct(private readonly object $ichki) {}
// istalgan metod chaqiruvi shu yerga tushadi
public function __call(string $metod, array $argumentlar): mixed
{
$this->chaqiruvlar[] = $metod . '(' . implode(', ',
array_map(static fn(mixed $a): string => var_export($a, true),
$argumentlar)) . ')';
return $this->ichki->$metod(...$argumentlar);
}
/** @return list<string> */
public function chaqiruvlar(): array
{
return $this->chaqiruvlar;
}
}
final class Kalkulyator
{
public function qosh(int $a, int $b): int { return $a + $b; }
public function kopaytir(int $a, int $b): int { return $a * $b; }
}
$k = new UmumiyProksi(new Kalkulyator());
echo $k->qosh(2, 3), PHP_EOL;
echo $k->kopaytir(4, 5), PHP_EOL;
echo '--- yozib olingan chaqiruvlar', PHP_EOL;
foreach ($k->chaqiruvlar() as $c) {
echo ' ', $c, PHP_EOL;
}
5
20
--- yozib olingan chaqiruvlar
qosh(2, 3)
kopaytir(4, 5)
__call proksisining narxi| Yo'qotish | Izoh |
|---|---|
| Turlar tekshiruvi yo'q | $k->qoshhh(1,2) faqat ish vaqtida xato beradi |
| IDE avtoto'ldirishi ishlamaydi | Metodlar ko'rinmaydi |
| Statik tahlilchilar ogohlantiradi | PHPStan, Psalm |
instanceof ishlamaydi | Proksi Kalkulyator emas |
Shu sababli jiddiy loyihalarda __call o'rniga interfeysni
bajaruvchi aniq proksi yoziladi - biz yuqorida qilganimizdek.
__call faqat test dublyorlari va nosozlik qidirish
vositalari uchun qulay.
Proxy, Decorator, Adapter #
Uchalasining tuzilmasi bir xil: interfeysni bajaradi va ichida obyekt saqlaydi. Farq niyatda:
$jadval = [
['Adapter', 'interfeys mos emas', 'tarjima qiladi', 'ha'],
['Decorator', 'xulq yetarli emas', 'qoshadi', 'yoq'],
['Proxy', 'kirishni nazorat', 'toxtatishi mumkin', 'yoq'],
];
printf("%-11s %-22s %-20s %s%s", 'Naqsh', 'Muammo', 'Nima qiladi',
'Interfeys ozgaradimi', PHP_EOL);
echo str_repeat('-', 76), PHP_EOL;
foreach ($jadval as $q) {
printf("%-11s %-22s %-20s %s%s", ...[...$q, PHP_EOL]);
}
Naqsh Muammo Nima qiladi Interfeys ozgaradimi
----------------------------------------------------------------------------
Adapter interfeys mos emas tarjima qiladi ha
Decorator xulq yetarli emas qoshadi yoq
Proxy kirishni nazorat toxtatishi mumkin yoq
Kesh proksisi va kesh dekoratori - deyarli bir xil kod. Ba'zi mualliflar ularni umuman ajratmaydi.
Foydali mezon - haqiqiy obyekt kimga tegishli:
| Kim yaratadi | Chaqiruv doim yetadimi | |
|---|---|---|
| Decorator | Tashqaridan beriladi | Ha |
| Proxy | Ko'pincha o'zi yaratadi | Yo'q |
Nom tanlashda ortiqcha vaqt sarflamang. Muhimi - kod nima qilishi aniq bo'lsin.
- Konstruktori "qimmat" bo'lgan sinf yozing.
- Uning ro'yxatini chiqaring va nechta marta hisoblanganini ko'ring.
- Virtual proksi yozib, hisoblashni
tana()ga kechiktiring. yuklanganmi()bilan qaysilari yuklanganini tekshiring.- Rolga qarab ruxsat beruvchi himoya proksisini yozing.
- Ikkala proksini birga qo'ying va tartibni sinang.
- Kesh proksisini yozing va bazaga necha marta borilganini sanang.
- Kesh eskirishi muammosini o'z misolingizda ko'rsating.
__callbilan universal proksi yozing va kamchiliklarini sanang.- Proxy, Decorator va Adapter farqini o'z so'zlaringiz bilan yozing.
Xulosa #
- Proxy - haqiqiy obyekt oldida turuvchi, bir xil interfeysdagi o'rin bosar.
- Virtual proksi qimmat obyektni kerak bo'lgunicha yaratmaydi.
- ORM larning kechiktirilgan yuklashi - aynan shu naqsh, va u N+1 muammosini tug'diradi.
- Himoya proksisi huquqni tekshiradi va chaqiruvni to'xtatishi mumkin.
- Kesh proksisi natijani eslab qoladi - lekin kesh eskirishi jiddiy masala.
- Proksilarni bir-birining ustiga qo'yish mumkin.
- PHP da
__callbilan universal proksi yozish mumkin, lekin turlar tekshiruvi yo'qoladi. - Jiddiy kodda interfeysni bajaruvchi aniq proksi yoziladi.
- Proxy, Decorator va Adapter tuzilmasi bir xil - farq niyatda.
- Decorator ga obyekt beriladi va chaqiruv doim yetadi; Proxy uni o'zi yaratadi va to'xtatishi mumkin.
Keyingi bo'limda xulq-atvor naqshlarini boshlaymiz - birinchisi Strategy.
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.