12-bo‘lim

Proxy

Obyekt oldiga o'rin bosar qo'yish - kechiktirish, nazorat va keshlash uchun.

🕑 12 daqiqa o‘qish 📄 770 so‘z 👁 1 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. To'rt xil proksi
  2. Virtual proksi: kechiktirilgan yaratish
  3. Himoya proksisi
  4. Kesh proksisi
  5. PHP ning sehrli metodlari bilan proksi
  6. Proxy, Decorator, Adapter
  7. Xulosa

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 #

TurNima qiladi
VirtualQimmat obyektni kerak bo'lgunicha yaratmaydi
HimoyaHuquqni tekshiradi, ruxsatsizni o'tkazmaydi
Kesh (smart)Natijani eslab qoladi
Uzoq (remote)Tarmoq orqali chaqirishni yashiradi

Birinchi uchtasini kodda ko'ramiz.

Virtual proksi: kechiktirilgan yaratish #

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

PHP
$hisobotlar = [
    new OgirHisobot('yanvar'),
    new OgirHisobot('fevral'),
    new OgirHisobot('mart'),
];

echo '--- royxat', PHP_EOL;
foreach ($hisobotlar as $h) {
    echo $h->sarlavha(), PHP_EOL;
}
Natija
  [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.

PHP
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;
    }
}
PHP
$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);
}
Natija
--- 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
Bu Doctrine va Eloquent da har kuni ishlaydi

ORM dan obyekt olganingizda, uning bog'liq obyektlari odatda darhol yuklanmaydi:

KOD
$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:

KodNechta so'rov
100 ta buyurtmani olish1
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 #

PHP
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();
    }
}
PHP
$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;
    }
}
Natija
--- 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.

Proxy - chaqiruv yo'lidagi nazorat nuqtasi Mijoz interfeysni biladi Proxy tekshiradi / kechiktiradi keshlaydi / rad etadi balki OgirHisobot haqiqiy obyekt baza / tarmoq / fayl tizimi To'rt xil proksi Virtual: haqiqiy obyekt kerak bo'lgunicha yaratilmaydi (ORM ning ghost obyektlari) Himoya: rol tekshiriladi, ruxsatsiz chaqiruv haqiqiy obyektga yetib bormaydi Kesh: birinchi natija eslab qolinadi, keyingilari qaytariladi Uzoq: chaqiruv tarmoq orqali ketadi, mijoz buni sezmaydi Decorator bilan farqi Decorator: haqiqiy obyekt tashqaridan beriladi, chaqiruv doim unga yetadi, xulq qo'shiladi Proxy: haqiqiy obyektni ko'pincha o'zi yaratadi, chaqiruvni to'xtatishi mumkin Tuzilma bir xil - farq maqsadda. Shuning uchun ular ko'pincha chalkashtiriladi.
Bir xil tuzilma, uch xil maqsad

Kesh proksisi #

PHP
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;
Natija
    [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 - eng ko'p xato tug'diradigan proksi

Kesh qo'shish oson, uni to'g'ri qilish qiyin:

SavolNima 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 #

PHP
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;
}
Natija
5
20
--- yozib olingan chaqiruvlar
  qosh(2, 3)
  kopaytir(4, 5)
__call proksisining narxi
Yo'qotishIzoh
Turlar tekshiruvi yo'q$k->qoshhh(1,2) faqat ish vaqtida xato beradi
IDE avtoto'ldirishi ishlamaydiMetodlar ko'rinmaydi
Statik tahlilchilar ogohlantiradiPHPStan, Psalm
instanceof ishlamaydiProksi 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:

PHP
$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]);
}
Natija
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
Amalda farqni ajratish qiyin

Kesh proksisi va kesh dekoratori - deyarli bir xil kod. Ba'zi mualliflar ularni umuman ajratmaydi.

Foydali mezon - haqiqiy obyekt kimga tegishli:

Kim yaratadiChaqiruv doim yetadimi
DecoratorTashqaridan beriladiHa
ProxyKo'pincha o'zi yaratadiYo'q

Nom tanlashda ortiqcha vaqt sarflamang. Muhimi - kod nima qilishi aniq bo'lsin.

Amaliy topshiriq
  1. Konstruktori "qimmat" bo'lgan sinf yozing.
  2. Uning ro'yxatini chiqaring va nechta marta hisoblanganini ko'ring.
  3. Virtual proksi yozib, hisoblashni tana() ga kechiktiring.
  4. yuklanganmi() bilan qaysilari yuklanganini tekshiring.
  5. Rolga qarab ruxsat beruvchi himoya proksisini yozing.
  6. Ikkala proksini birga qo'ying va tartibni sinang.
  7. Kesh proksisini yozing va bazaga necha marta borilganini sanang.
  8. Kesh eskirishi muammosini o'z misolingizda ko'rsating.
  9. __call bilan universal proksi yozing va kamchiliklarini sanang.
  10. 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 __call bilan 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.

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.