08 β Reflection, attributes va FFI¶
β¬ οΈ Oldingi: 07 β Property hooks va asymmetric visibility Β· π README Β· Keyingi: 09 β WeakMap, SPL va reference β‘οΈ
Bu bobda: PHP ning eng "sehrli" ko'ringan imkoniyatlarining ichini ochamiz. Avval meta-dasturlash nima ekanini va dasturning ish vaqtida o'z tuzilishini qanday "ko'ra olishini" tushunamiz. So'ng Reflection API ni (
ReflectionClass,ReflectionMethod,ReflectionParameter,ReflectionNamedType/ReflectionUnionType) chuqur o'rganamiz. Keyin PHP 8.0 da kelgan atributlarni β#[Attribute]bilan o'z atributingni e'lon qilish,TARGET_*vaIS_REPEATABLE, hamda Reflection bilan ularni O'QISH. Bularni birlashtirib kichik atribut-asosli tizim quramiz:#[Route]marshrutlovchi,#[Validate]validator va#[Inject]DI konteyner β aynan shu narsa Laravel/Symfony "sehri"ning asosi. Reflection sekin bo'lgani uchun uni qanday keshlash kerakligini ko'ramiz. Nihoyat FFI (Foreign Function Interface) bilan PHP dan to'g'ridan-to'g'ri C funksiyalarini chaqiramiz β qachon kerak, qachon kerak emas va xavfsizlik narxi. Bu bob boshlovchi kitobdagi class va obyekt, kirish darajalari, enum va xatolarni boshqarish boblariga tayanadi.
Meta-dasturlash nima va Reflection nega kerak?¶
Odatdagi dasturda kod ma'lumot ustida ishlaydi: sonlarni qo'shadi, satrlarni birlashtiradi, bazadan qator oladi. Meta-dasturlash esa β kod kodning o'zi ustida ishlashi. Ya'ni dastur ish vaqtida (runtime) o'zining sinflari, metodlari, parametrlari haqida savol bera oladi: "bu sinfda qanday metodlar bor?", "bu metod qanday turdagi argument kutadi?", "bu propertyga qanday atribut yopishtirilgan?".
Aynan shu imkoniyatni PHP da Reflection API beradi. Reflection β "ko'zgu" degani: dastur o'ziga ko'zguda qaraydi va o'z tuzilishini o'qiydi.
Nega bu kerak? Tasavvur qiling, siz framework yozyapsiz. Foydalanuvchi o'z kontroller sinfini yozadi β siz uni oldindan bilmaysiz. Lekin siz uning metodlarini topib, mos so'rovda chaqirishingiz kerak. Yoki DI konteyner yozyapsiz: bir sinfning konstruktori qanday bog'liqliklarni so'rashini avtomatik aniqlab, ularni yetkazib berishingiz kerak. Bularning hammasi Reflection orqali bo'ladi. Qisqasi:
- Routerlar atributlardagi marshrutni o'qiydi (
#[Route('/users')]). - DI konteynerlar konstruktor turlaridan bog'liqlikni aniqlaydi (autowiring).
- ORM lar (Doctrine) entity propertylaridan jadval ustunlarini chiqaradi.
- Validatorlar, serializatorlar, test frameworklar (
@test,#[Test]) β hammasi Reflection ustida.
Tuzoq (oldindan ogohlantirish): Reflection β kuchli, lekin sekin. U sinfni tahlil qilish uchun ichki ishlarni bajaradi. Shuning uchun uni har so'rovda emas, bir marta ishlatib, natijani keshlash kerak. Buni bobning oxirida alohida ko'rib chiqamiz.
ReflectionClass: sinfni ish vaqtida o'qish¶
Boshlanishi oddiy: sinf nomidan ReflectionClass obyektini yaratasiz, so'ng undan savol berasiz. Boshlovchi kitobdagi class va obyekt tushunchasi shu yerda kerak bo'ladi.
<?php
declare(strict_types=1);
final class Hisoblagich
{
public function __construct(private int $boshlang = 0) {}
public function oshir(int $qadam = 1): int
{
return $this->boshlang += $qadam;
}
public function qiymat(): int
{
return $this->boshlang;
}
}
$r = new ReflectionClass(Hisoblagich::class);
echo "Sinf nomi: " . $r->getShortName() . PHP_EOL;
echo "Final mi: " . ($r->isFinal() ? 'ha' : 'yoq') . PHP_EOL;
echo "--- Metodlar ---" . PHP_EOL;
foreach ($r->getMethods() as $m) {
$params = array_map(
fn(ReflectionParameter $p) => $p->getName(),
$m->getParameters()
);
echo " {$m->getName()}(" . implode(', ', $params) . ")" . PHP_EOL;
}
echo "--- Konstruktor parametrlari ---" . PHP_EOL;
$ctor = $r->getConstructor();
foreach ($ctor->getParameters() as $p) {
$type = $p->getType();
$typeName = $type instanceof ReflectionNamedType ? $type->getName() : 'aralash';
$default = $p->isDefaultValueAvailable() ? var_export($p->getDefaultValue(), true) : 'yoq';
echo " \${$p->getName()}: {$typeName} (default: {$default})" . PHP_EOL;
}
// newInstanceArgs: konstruktorga argument massivi bilan obyekt yaratamiz
$obj = $r->newInstanceArgs([10]);
echo "Yangi obyekt qiymati: " . $obj->qiymat() . PHP_EOL;
$obj->oshir(5);
echo "Oshirilgandan keyin: " . $obj->qiymat() . PHP_EOL;
Chiqishi (tekshirilgan):
Sinf nomi: Hisoblagich
Final mi: ha
--- Metodlar ---
__construct(boshlang)
oshir(qadam)
qiymat()
--- Konstruktor parametrlari ---
$boshlang: int (default: 0)
Yangi obyekt qiymati: 10
Oshirilgandan keyin: 15
E'tibor bering: newInstanceArgs([10]) private int $boshlang ni konstruktor orqali to'ldirdi β ya'ni biz qaysi propertylar borligini va konstruktor nima kutishini bilmasdan ham obyekt yarata oldik. Mana shu β frameworkning obyektlarni siz uchun yaratishining (instantiation) asosi.
Reflection oilasidagi asosiy sinflar¶
| Sinf | Nimani aks ettiradi | Eng muhim metodlar |
|---|---|---|
ReflectionClass |
sinfning o'zi | getMethods(), getProperties(), getAttributes(), getConstructor(), newInstanceArgs(), isFinal(), getInterfaceNames() |
ReflectionMethod |
bitta metod | getParameters(), getReturnType(), invoke(), isPublic(), isStatic() |
ReflectionProperty |
bitta property | getType(), getValue($obj), setValue(), getAttributes() |
ReflectionParameter |
metod/funksiya parametri | getType(), getName(), isDefaultValueAvailable(), getDefaultValue(), allowsNull() |
ReflectionNamedType |
bitta nomli tur (int, App\User) |
getName(), isBuiltin(), allowsNull() |
ReflectionUnionType |
union tur (int\|string) |
getTypes() (ichida ReflectionNamedType lar) |
ReflectionEnum |
enum (22-bob) | getCases(), isBacked() |
Tur tizimini o'qish: NamedType va UnionType¶
PHP 8 ning boy tip tizimi (union, nullable, mixed) Reflection orqali ham o'qiladi. Bu DI konteyner uchun hayotiy muhim β chunki konstruktor parametri turi bo'yicha bog'liqlikni yechadi.
<?php
declare(strict_types=1);
final class Misol
{
public function f(int|string $id, ?DateTimeInterface $vaqt, mixed $har): void {}
}
$m = new ReflectionMethod(Misol::class, 'f');
foreach ($m->getParameters() as $p) {
$t = $p->getType();
echo "\${$p->getName()}: ";
if ($t instanceof ReflectionUnionType) {
// int|string kabi union tur β tarkibiy turlarni ajratamiz.
$names = array_map(
fn(ReflectionNamedType $nt) => $nt->getName(),
$t->getTypes()
);
echo 'union(' . implode('|', $names) . ')';
} elseif ($t instanceof ReflectionNamedType) {
// Oddiy tur. ?DateTimeInterface bo'lsa allowsNull() true bo'ladi.
echo $t->getName();
if ($t->allowsNull() && $t->getName() !== 'mixed') {
echo ' (nullable)';
}
} else {
echo 'turi belgilanmagan';
}
echo PHP_EOL;
}
Chiqishi:
Diqqat:
getType()nullqaytarishi mumkin β agar parametr turi umuman belgilanmagan bo'lsa. Shuning uchun har doiminstanceofbilan tekshiring, to'g'ridan-to'g'ri$t->getName()chaqirmang.isBuiltin()esa turning ichki (int,string) yoki sinf (App\User) ekanini ajratadi β autowiring da faqat sinflarni yechamiz, ichki turlarni emas.
Atributlar (PHP 8.0): kodga "yorliq" yopishtirish¶
Reflection sinf tuzilishini o'qiydi. Lekin ko'pincha bizga ko'proq ma'lumot kerak: "bu metod qaysi URL ga bog'lansin?", "bu propertyni qanday tekshirish kerak?". Bu qo'shimcha metadata ni kodning o'ziga yozish uchun atributlar ishlatiladi.
Atribut β bu metod, property, sinf, parametr yoki konstantaga yopishtiriladigan strukturalangan yorliq. Sintaksisi #[...]:
PHP 8.0 dan oldin buni doc-comment larda (/** @Route(...) */ β satr ichidagi izoh) qilishardi. Doctrine va Symfony shunday qilardi, lekin bu xato keltirib chiqarardi: izohlar oddiy matn, IDE ularni tekshirmaydi, yozilishida xato bo'lsa bilinmaydi. Atributlar esa β tilning haqiqiy qismi: noto'g'ri nom yozsangiz, PHP xato beradi.
Eng muhim tushuncha: atribut β passiv metadata. PHP atributni o'zi ishga solmaydi, hech narsa qilmaydi. U shunchaki kodda "yotadi". Faqat siz uni Reflection bilan o'qiganingizda va
newInstance()chaqirganingizda u haqiqiy obyektga aylanadi. "Sehr" β bu o'qish bosqichida sodir bo'ladi.
O'z atributingni e'lon qilish¶
Atribut β bu shunchaki oddiy sinf, ustiga #[Attribute] qo'yilgan. #[Attribute] ning o'zi ham atribut β bu PHP ga "bu sinf atribut sifatida ishlatilishi mumkin" deb aytadi.
<?php
declare(strict_types=1);
// 1) O'z atributimizni e'lon qilamiz. #[Attribute] ning o'zi ham atribut!
#[Attribute(Attribute::TARGET_METHOD)]
final class Route
{
public function __construct(
public string $path,
public string $method = 'GET',
) {}
}
#[Route('/users', 'GET')] yozganingizda β bu aynan new Route('/users', 'GET') chaqiruvi (lekin faqat o'qilganda yaratiladi). Konstruktor argumentlari atributning argumentlariga to'g'ri keladi. Shuning uchun atribut sinfining konstruktorida public promoted property (16-bob) ishlatish qulay β o'qiganda to'g'ridan-to'g'ri $route->path ga kirasiz.
TARGET_* β atribut qayerga qo'yilishi mumkin¶
#[Attribute(...)] ga bayroq (flag) beriladi: bu atribut qayerga qo'yilishi mumkin. Bayroqlarni | bilan birlashtirasiz.
| Bayroq | Qayerga |
|---|---|
Attribute::TARGET_CLASS |
sinf, interfeys, trait, enum |
Attribute::TARGET_METHOD |
metod |
Attribute::TARGET_PROPERTY |
property |
Attribute::TARGET_PARAMETER |
funksiya/metod parametri |
Attribute::TARGET_CLASS_CONSTANT |
sinf konstantasi |
Attribute::TARGET_FUNCTION |
global funksiya |
Attribute::TARGET_ALL |
hammasi (standart qiymat) |
Attribute::IS_REPEATABLE |
bitta joyga BIR NECHTA marta qo'yilishi mumkin |
TARGET cheklovi majburlanadi. Agar TARGET_PROPERTY atributini metodga qo'ysangiz, newInstance() chaqirilganda PHP Error otadi:
<?php
declare(strict_types=1);
#[Attribute(Attribute::TARGET_PROPERTY)]
final class OnlyProp {}
final class X {
// β TARGET_PROPERTY atributini METODGA qo'ydik
#[OnlyProp]
public function f(): void {}
}
$r = new ReflectionMethod(X::class, 'f');
// newInstance() chaqirilganda PHP target mosligini tekshiradi va Error otadi:
$r->getAttributes(OnlyProp::class)[0]->newInstance();
// Error: Attribute "OnlyProp" cannot target method (allowed targets: property)
Nega muhim: TARGET cheklovi xatoni erta ushlaydi. Foydalanuvchi atributni noto'g'ri joyga qo'ysa, tushunarli xato oladi. O'z atributingizni yozayotganda har doim aniq TARGET bering β
TARGET_ALLni faqat haqiqatan kerak bo'lganda ishlating.
Amaliy tizim 1: atribut-asosli router (#[Route])¶
Endi nazariyani birlashtiramiz. Mana frameworklarning "sehri"ning yuragi: kontroller metodlariga #[Route] qo'yamiz, so'ng Reflection bilan o'qib, marshrut jadvali quramiz va so'rovni mos metodga yo'naltiramiz. Bu β 01-bobdagi REST router ning atribut-asosli, "kattalashgan" versiyasi.
<?php
declare(strict_types=1);
// 1) O'z atributimizni e'lon qilamiz. #[Attribute] ning o'zi ham atribut!
#[Attribute(Attribute::TARGET_METHOD)]
final class Route
{
public function __construct(
public string $path,
public string $method = 'GET',
) {}
}
// 2) Atributlarni metodlarga "yopishtiramiz" β bu shunchaki metadata,
// PHP ularni o'zi ishga solmaydi.
final class UserController
{
#[Route('/users', 'GET')]
public function index(): string
{
return 'Barcha foydalanuvchilar';
}
#[Route('/users', 'POST')]
public function create(): string
{
return 'Foydalanuvchi yaratildi';
}
#[Route('/users/{id}', 'GET')]
public function show(): string
{
return 'Bitta foydalanuvchi';
}
// Atributsiz metod β marshrutga qo'shilmaydi.
public function ichkiYordamchi(): void {}
}
// 3) Reflection bilan O'QIYMIZ va marshrut jadvalini quramiz.
function marshrutlarniYig(string $controllerClass): array
{
$jadval = [];
$r = new ReflectionClass($controllerClass);
foreach ($r->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
// Faqat #[Route] atributi bor metodlarni olamiz.
$attrs = $method->getAttributes(Route::class);
foreach ($attrs as $attr) {
$route = $attr->newInstance(); // <-- bu yerda Route obyekti yaratiladi
$jadval[] = [
'method' => $route->method,
'path' => $route->path,
'controller' => $controllerClass,
'action' => $method->getName(),
];
}
}
return $jadval;
}
$jadval = marshrutlarniYig(UserController::class);
echo "=== Marshrut jadvali ===" . PHP_EOL;
foreach ($jadval as $row) {
printf("%-5s %-14s -> %s::%s%s",
$row['method'], $row['path'], $row['controller'], $row['action'], PHP_EOL);
}
// 4) Soddalashtirilgan dispatcher: kelgan so'rovni topib, chaqiramiz.
function dispatch(array $jadval, string $method, string $path): string
{
foreach ($jadval as $route) {
if ($route['method'] === $method && $route['path'] === $path) {
$obj = new $route['controller']();
return $obj->{$route['action']}();
}
}
return '404 Not Found';
}
echo PHP_EOL . "=== Dispatch sinovi ===" . PHP_EOL;
echo "GET /users => " . dispatch($jadval, 'GET', '/users') . PHP_EOL;
echo "POST /users => " . dispatch($jadval, 'POST', '/users') . PHP_EOL;
echo "GET /yoq => " . dispatch($jadval, 'GET', '/yoq') . PHP_EOL;
Chiqishi (tekshirilgan):
=== Marshrut jadvali ===
GET /users -> UserController::index
POST /users -> UserController::create
GET /users/{id} -> UserController::show
=== Dispatch sinovi ===
GET /users => Barcha foydalanuvchilar
POST /users => Foydalanuvchi yaratildi
GET /yoq => 404 Not Found
Mana β Symfony/Laravel routerining qalbi. Endi #[Route] qaerda "sehrli" emas: bu shunchaki Reflection + getAttributes() + newInstance(). Siz buni o'zingiz yozdingiz. Kelajakdagi L4 mini-framework boblarida shu naqshni to'liq routerga aylantiramiz.
getAttributes(Route::class)muhim: argumentsizgetAttributes()HAMMA atributlarni qaytaradi. Sinf nomini bersangiz, faqat o'sha turdagilarni oladi β bu sizning kodingizni boshqa atributlardan (masalan#[Deprecated]) himoya qiladi.
Amaliy tizim 2: validator (#[Validate], IS_REPEATABLE)¶
Bitta propertyga bir nechta qoida qo'yish kerak bo'ladi (masalan "majburiy" VA "kamida 3 belgi"). Buning uchun IS_REPEATABLE bayrog'i kerak β usiz bitta joyga atributni faqat bir marta qo'ya olasiz.
<?php
declare(strict_types=1);
// TARGET_PROPERTY + IS_REPEATABLE: bitta propertyga bir nechta qoida qo'yamiz.
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
final class Validate
{
public function __construct(
public string $rule,
public ?int $param = null,
) {}
public function tekshir(mixed $value): ?string
{
return match ($this->rule) {
'required' => ($value === null || $value === '')
? 'majburiy maydon' : null,
'email' => (is_string($value) && filter_var($value, FILTER_VALIDATE_EMAIL))
? null : 'email formati notogri',
'min' => (is_string($value) && mb_strlen($value) >= ($this->param ?? 0))
? null : "kamida {$this->param} ta belgi kerak",
default => null,
};
}
}
final class RoyxatdanOtish
{
#[Validate('required')]
#[Validate('min', 3)]
public string $ism = '';
#[Validate('required')]
#[Validate('email')]
public string $email = '';
}
// Validator: obyekt propertylarini Reflection orqali tekshiradi.
function validatsiya(object $obj): array
{
$xatolar = [];
$r = new ReflectionClass($obj);
foreach ($r->getProperties() as $prop) {
$qiymat = $prop->getValue($obj);
foreach ($prop->getAttributes(Validate::class) as $attr) {
$rule = $attr->newInstance();
if ($xato = $rule->tekshir($qiymat)) {
$xatolar[$prop->getName()][] = $xato;
}
}
}
return $xatolar;
}
$form = new RoyxatdanOtish();
$form->ism = 'Al'; // juda qisqa
$form->email = 'notogri-email'; // notogri format
$xatolar = validatsiya($form);
echo "=== Validatsiya natijasi ===" . PHP_EOL;
echo json_encode($xatolar, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . PHP_EOL;
// To'g'ri to'ldirilgan forma:
$ok = new RoyxatdanOtish();
$ok->ism = 'Oqil';
$ok->email = 'oqil@misol.uz';
echo PHP_EOL . "Tugri forma xatolari: ";
echo json_encode(validatsiya($ok)) . PHP_EOL;
Chiqishi:
=== Validatsiya natijasi ===
{
"ism": [
"kamida 3 ta belgi kerak"
],
"email": [
"email formati notogri"
]
}
Tugri forma xatolari: []
E'tibor bering: #[Validate('required')] va #[Validate('min', 3)] β bitta propertyga ikkita atribut. IS_REPEATABLE siz bu xato bo'lardi. getAttributes() ikkalasini ham qaytaradi, biz har birini alohida newInstance() qilamiz.
Real loyihada: symfony/validator aynan shunday ishlaydi β
#[Assert\NotBlank],#[Assert\Email],#[Assert\Length(min: 3)]. Endi siz uning ichida nima borligini bilasiz.
Amaliy tizim 3: DI konteyner va autowiring¶
Eng kuchli misol. DI konteyner β sinflarni siz uchun yaratuvchi, bog'liqliklarini avtomatik to'ldiruvchi obyekt. "Autowiring" β konstruktor turlariga qarab kerakli obyektlarni avtomatik yaratish. Bu yerda atribut shart emas, faqat Reflection + tur tizimi kifoya.
<?php
declare(strict_types=1);
// Soddalashtirilgan DI konteyner: konstruktor turlariga qarab
// bog'liqliklarni avtomatik yaratadi (autowiring).
final class Logger
{
public function yoz(string $msg): void
{
echo "[LOG] {$msg}" . PHP_EOL;
}
}
final class UserRepository
{
public function __construct(private Logger $logger) {}
public function topish(int $id): string
{
$this->logger->yoz("User #{$id} qidirilmoqda");
return "User#{$id}";
}
}
final class UserService
{
public function __construct(
private UserRepository $repo,
private Logger $logger,
) {}
public function profil(int $id): string
{
$this->logger->yoz('Profil tayyorlanmoqda');
return 'Profil: ' . $this->repo->topish($id);
}
}
final class Container
{
/** @var array<string, object> */
private array $kesh = [];
public function get(string $class): object
{
// Allaqachon yaratilgan bo'lsa, keshdan qaytaramiz (singleton).
if (isset($this->kesh[$class])) {
return $this->kesh[$class];
}
$r = new ReflectionClass($class);
$ctor = $r->getConstructor();
// Konstruktorsiz sinf β to'g'ridan-to'g'ri yaratamiz.
if ($ctor === null) {
return $this->kesh[$class] = new $class();
}
// Har bir parametr turini aniqlab, rekursiv ravishda yechamiz.
$args = [];
foreach ($ctor->getParameters() as $param) {
$type = $param->getType();
if (!$type instanceof ReflectionNamedType || $type->isBuiltin()) {
throw new RuntimeException(
"Avtomatik yechib bolmaydi: \${$param->getName()}"
);
}
$args[] = $this->get($type->getName()); // rekursiya
}
return $this->kesh[$class] = $r->newInstanceArgs($args);
}
}
$container = new Container();
$service = $container->get(UserService::class);
echo "=== DI konteyner ishlamoqda ===" . PHP_EOL;
echo $service->profil(42) . PHP_EOL;
// Singleton tekshiruvi: ikkinchi marta so'ralsa, AYNAN o'sha obyekt.
$a = $container->get(Logger::class);
$b = $container->get(Logger::class);
echo PHP_EOL . "Bir xil Logger obyektimi? " . ($a === $b ? 'ha' : 'yoq') . PHP_EOL;
Chiqishi:
=== DI konteyner ishlamoqda ===
[LOG] Profil tayyorlanmoqda
[LOG] User #42 qidirilmoqda
Profil: User#42
Bir xil Logger obyektimi? ha
Bu yerda nima sodir bo'ldi: get(UserService::class) chaqirildi -> konstruktorda UserRepository va Logger kerak ekan -> ularni rekursiv yaratdi -> UserRepository esa Logger ni so'radi -> kesh tufayli ayni Logger qayta ishlatildi. Siz hech qachon new UserService(new UserRepository(new Logger()), ...) yozmadingiz β konteyner buni o'zi qildi. Mana shu β Laravel app() va Symfony konteynerining mohiyati.
isBuiltin()cheklovi: agar konstruktorint $portso'rasa, konteyner uni yecha olmaydi (qaysi qiymat?) vaRuntimeExceptionotadi. Real konteynerlarda buni#[Inject]atributi yoki aniq sozlama (binding) bilan hal qilishadi β masalan$container->bind('port', 8080). Bu esa keyingi tizimning vazifasi:#[Inject]ni Qiyin mashqda ko'ramiz.
Reflection narxi va keshlash (production naqshi)¶
Reflection β qulay, lekin bepul emas. Har new ReflectionClass(), har getAttributes()->newInstance() qo'shimcha ish. Bir-ikki sinf uchun bu sezilmaydi, lekin yuzlab marshrut/entity bo'lgan ilovada har so'rovda qayta tahlil qilish β sezilarli sekinlik.
Yechim β bir marta o'qib, natijani keshlash. Frameworklar aynan shuni qiladi: php artisan route:cache yoki Symfony ning kompilyatsiya qilingan konteyneri β bularning hammasi "Reflection ni bir marta ishlatib, natijani oddiy PHP massiv/fayl sifatida saqlash" degani.
<?php
declare(strict_types=1);
#[Attribute(Attribute::TARGET_METHOD)]
final class Route
{
public function __construct(public string $path, public string $method = 'GET') {}
}
final class Demo
{
#[Route('/a')] public function a(): void {}
#[Route('/b', 'POST')] public function b(): void {}
}
// "Qimmat" qism: Reflection bilan o'qish.
function ogish(string $class): array
{
$jadval = [];
$r = new ReflectionClass($class);
foreach ($r->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
foreach ($method->getAttributes(Route::class) as $attr) {
$route = $attr->newInstance();
$jadval[] = [$route->method, $route->path, $method->getName()];
}
}
return $jadval;
}
// Production naqshi: bir marta o'qib, oddiy massiv (PHP fayl) sifatida saqlaymiz.
$cacheFile = sys_get_temp_dir() . '/routes.cache.php';
if (is_file($cacheFile)) {
$jadval = require $cacheFile; // ARZON: faqat massivni include qilamiz
$manba = 'kesh (Reflection ishlamadi)';
} else {
$jadval = ogish(Demo::class); // QIMMAT: faqat birinchi marta
file_put_contents(
$cacheFile,
'<?php return ' . var_export($jadval, true) . ';'
);
$manba = 'Reflection (birinchi marta)';
}
echo "Manba: {$manba}" . PHP_EOL;
echo "Marshrutlar soni: " . count($jadval) . PHP_EOL;
echo json_encode($jadval) . PHP_EOL;
@unlink($cacheFile); // demo uchun tozalab qo'yamiz
Birinchi ishga tushirishda Reflection (birinchi marta), keyingilarida kesh chiqadi (agar oxiridagi unlink ni olib tashlasangiz). Kesh fayli β oddiy <?php return [...] β uni require qilish Reflection dan o'nlab marta tezroq, chunki OPcache uni xotirada saqlaydi.
Oltin qoida: development da Reflection ni to'g'ridan-to'g'ri ishlating (kod o'zgaradi, kesh halaqit beradi). Production da esa deploy paytida bir marta kesh quring va shuni o'qing. Hech qachon production da har so'rovda atributlarni qaytadan o'qimang.
FFI: PHP dan to'g'ridan-to'g'ri C funksiyalarini chaqirish¶
Hozirgacha biz PHP ning ichidagi dunyoni o'qidik. FFI (Foreign Function Interface) β butunlay boshqa narsa: PHP dan tashqaridagi, C tilida yozilgan kompilyatsiya qilingan kutubxonalarni (.so/.dll/.dylib) to'g'ridan-to'g'ri chaqirish. Ya'ni C kutubxonasi uchun PHP kengaytmasi (extension) yozmasdan, uni PHP dan ishlatish.
FFI::cdef bilan C funksiyasini e'lon qilish¶
FFI::cdef() ga ikkita narsa berasiz: C funksiya imzosi (faqat e'lon, tanasi emas) va kutubxona nomi. Quyidagi misol Windows da C ish vaqti kutubxonasi msvcrt.dll dan funksiyalar chaqiradi (Linux/macOS da libc.so.6 yoki libm.so bo'ladi).
<?php
declare(strict_types=1);
// FFI::cdef() β C funksiya imzosini e'lon qilamiz.
// Windows da standart C kutubxonasi: msvcrt.dll
// (Linux: "libc.so.6", macOS: odatda nomsiz / "libc.dylib").
$ffi = FFI::cdef(<<<'C'
int abs(int n);
double sqrt(double x);
C, "msvcrt.dll");
// Endi C funksiyalarini xuddi PHP metodidek chaqiramiz.
echo "abs(-42) = " . $ffi->abs(-42) . PHP_EOL;
echo "sqrt(144) = " . $ffi->sqrt(144.0) . PHP_EOL;
Chiqishi (Windows da, tekshirilgan):
$ffi->abs(-42) β bu PHP funksiyasi emas! Bu to'g'ridan-to'g'ri msvcrt.dll ichidagi mashina kodidagi abs funksiyasiga chaqiruv. PHP soni C int ga aylandi, natija qaytib PHP soniga aylandi.
Portativlik tuzog'i (darhol e'tibor bering): kutubxona nomi platformaga bog'liq.
"msvcrt.dll"faqat Windows da,"libc.so.6"Linux da ishlaydi. Bu β FFI ning birinchi katta narxi: kod ko'chmas (non-portable) bo'lib qoladi. Standartabs/sqrtuchun esa FFI umuman kerak emas β PHP daabs()vasqrt()allaqachon bor va tezroq. Bu faqat o'rgatuvchi misol.
String, struct va massivlar bilan ishlash¶
FFI faqat sonlar emas β string, C struct va massivlar bilan ham ishlaydi. PHP turlari C turlariga avtomatik moslanadi (marshalling).
<?php
declare(strict_types=1);
// strlen: C funksiyasi PHP string ni qabul qiladi (const char*).
$ffi = FFI::cdef(<<<'C'
typedef struct { int x; int y; } Point;
size_t strlen(const char *s);
int strcmp(const char *a, const char *b);
C, "msvcrt.dll");
$matn = "Salom, FFI!";
echo "C strlen('{$matn}') = " . $ffi->strlen($matn) . PHP_EOL;
echo "PHP strlen = " . strlen($matn) . PHP_EOL;
echo "strcmp('a','b') = " . $ffi->strcmp('a', 'b') . PHP_EOL;
// --- Struct: C tuzilmasini PHP da yaratish va to'ldirish ---
// MUHIM: new() ni $ffi INSTANSIYASIDAN chaqiramiz (statik FFI::new() 8.4 da deprecated).
$p = $ffi->new("Point"); // C struct uchun xotira ajratiladi
$p->x = 3;
$p->y = 4;
echo PHP_EOL . "Point({$p->x}, {$p->y})" . PHP_EOL;
// FFI massivi: C tipidagi 5 ta int.
$arr = $ffi->new("int[5]");
for ($i = 0; $i < 5; $i++) {
$arr[$i] = $i * $i;
}
echo "arr[3] = {$arr[3]}, soni = " . count($arr) . PHP_EOL;
Chiqishi:
8.4 tuzog'i:
FFI::new("int[5]")β statik chaqiruv β PHP 8.4 da deprecated. Yuqorida ko'rganingizdek,$ffi->new(...)β instansiya metodi sifatida chaqiring. C struct (Point) ham, massiv (int[5]) hamFFI\CDataobyekti β uni xuddi oddiy PHP obyekti/massivi kabi ishlatasiz, lekin orqada C xotirasi yotadi.
Qachon FFI KERAK va qachon KERAK EMAS¶
Bu β bobning eng muhim amaliy xulosasi. FFI kuchli, lekin deyarli har doim kerak emas.
| Qachon FFI mantiqiy | Qachon FFI KERAK EMAS (deyarli har doim) |
|---|---|
| Mavjud, katta C kutubxonasiga ulanish kerak, lekin uni PHP extension sifatida yozish juda qimmat (masalan ixtisoslashgan ilmiy/grafik kutubxona) | Funksiya allaqachon PHP da bor (abs, sqrt, mb_strlen ...) β toza PHP tezroq va xavfsiz |
| Hot-loop: juda ko'p marta takrorlanadigan og'ir hisob, C da sezilarli tez | Oddiy CRUD/web ilova β FFI chaqiruvi overhead, foydasi yo'q |
| Prototip: extension yozishdan oldin C API ni sinab ko'rish | Tayyor PHP extension yoki kutubxona (Composer) mavjud β uni ishlating |
| Tizim API si (masalan maxsus drayver) bilan to'g'ridan-to'g'ri muloqot | Portativlik muhim β FFI kodi platformaga bog'lanib qoladi |
Narxlar: (1) xavfsizlik β siz PHP ning xavfsiz qobig'idan tashqariga chiqasiz; C dagi xato segfault / butun jarayonning qulashiga (crash) olib keladi, PHP ning chiroyli Exception i emas; (2) portativlik β kutubxona nomi/ABI platformaga bog'liq; (3) xotira β C xotirasini ba'zan o'zingiz boshqarishingiz kerak (GC yordam bermaydi); (4) murakkablik β C turlari, pointerlar, ABI bilan tanish bo'lish shart.
Amaliy maslahat: FFI ga murojaat qilishdan oldin o'zingizga uch savol bering: "Buni toza PHP da qila olamanmi?" (odatda β ha), "Tayyor Composer paketi yoki PHP extension bormi?" (ko'pincha β bor), "Bu haqiqatan hot-loop yoki noyob C kutubxonami?" (kamdan-kam β ha). Uch javob ham FFI ni qo'llab-quvvatlamasa, FFI dan voz keching.
FFI xavfsizligi: preload va ishonchli kod¶
FFI β kuch, demak xavf. Agar tajovuzkor sizning ilovangizda ixtiyoriy FFI kodini ishga tushira olsa, u butun serverni egallashi mumkin (chunki FFI mashina darajasida ishlaydi). Shuning uchun:
ffi.enable=preload(tavsiya etilgan production sozlamasi): FFI faqat oldindan yuklangan (preloaded) fayllardagina ishlaydi. Ya'ni FFI e'lonlariphp.inidaopcache.preloadorqali ko'rsatilgan ishonchli fayllarda bo'lishi shart β runtime da ixtiyoriyFFI::cdef()chaqirib bo'lmaydi. Bu eng xavfsiz rejim.ffi.enable=trueβ har joyda FFI ga ruxsat. Faqat ishonchli, izolyatsiyalangan muhitda (masalan CLI skript) ishlating.ffi.enable=falseβ butunlay o'chiq (web kontekst uchun standart va xavfsiz).- Hech qachon foydalanuvchi kiritmasini (
$_GET,$_POST) FFI imzosiga, kutubxona nomiga yoki funksiya argumentlariga ishonmasdan bermang. - FFI kodini faqat siz yozgan, ko'rib chiqilgan fayllarda saqlang β uchinchi tomon kodiga FFI ishlatishga ruxsat bermang.
Xulosa: FFI β "kuch + mas'uliyat" vositasi. Boshlovchi yoki o'rta darajadagi web ilovada deyarli hech qachon kerak bo'lmaydi. Uni bilish β ekspertlikning bir qismi, lekin ishlatmaslikni bilish undan ham muhim. Xatolarni boshqarish bobida ko'rganingizdek, PHP ning xavfsiz xato-modeli FFI da ishlamaydi β C darajasidagi xato butun jarayonni o'ldiradi.
Mashqlar¶
Oson¶
ReflectionClassyordamida ixtiyoriy sinfning interfeyslari (getInterfaceNames()) va ota-sinfi (getParentClass()) nomlarini chop etuvchi funksiya yozing.#[Attribute(Attribute::TARGET_CLASS)]bilan#[Entity(table: 'users')]atributini e'lon qiling. Bir sinfga qo'yib, Reflection bilan o'qibtableqiymatini chop eting.- FFI bilan
msvcrt.dlldanint rand(void)funksiyasini e'lon qiling va uni chaqirib bir tasodifiy son chop eting. (Eslatma: bu illustrativ β PHP darandom_int()bor va yaxshiroq.)
O'rta¶
#[Validate]validatorini kengaytiring: yangi'max'qoidasini qo'shing (#[Validate('max', 20)]) β satrparamdan uzun bo'lsa xato qaytarsin. Test forma bilan tekshiring.- Router tizimiga
getAttributes()argumentsiz versiyasidan foydalanib, bir metodda bir nechta turdagi atribut (#[Route]va yangi#[Auth]) bo'lganda ikkalasini ham o'qiydigan kod yozing.#[Auth]bo'lsa, marshrut "himoyalangan" deb belgilansin. - Reflection keshlash misolini o'zgartiring: kesh faylini o'chirmang va skriptni ikki marta ishga tushiring. Birinchi va ikkinchi ishga tushishda
Manba:qatori farq qilishini kuzating va nega shundayligini izohlang.
Qiyin¶
#[Inject]bilan DI konteynerni kuchaytiring.Container::get()ni shunday o'zgartiring: agar konstruktor parametri ichki tur (int,string) bo'lsa va unga#[Inject('kalit')]atributi qo'yilgan bo'lsa, qiymatni konteynerning sozlamalar jadvalidan (bind('kalit', qiymat)) olsin. Atributsiz ichki tur uchun esa standart qiymat (isDefaultValueAvailable()) ishlatilsin, u ham bo'lmasaRuntimeExceptionotsin.- Atribut-asosli serializator yozing.
#[Column(name)]va#[Ignore]atributlari bilan obyektni DB qatori shaklidagi massivga aylantiruvchitoRow(object): arrayfunksiyasini quring:#[Column]bor propertylar massivga kirsin (nameberilmasa, property nomi ishlatilsin),#[Ignore]bor yoki umuman atributsiz propertylar tashlab ketilsin.
Yechim β 7 (Inject bilan DI konteyner)
<?php
declare(strict_types=1);
#[Attribute(Attribute::TARGET_PARAMETER)]
final class Inject
{
public function __construct(public string $kalit) {}
}
final class Sozlama
{
public function __construct(public string $dsn, public int $timeout) {}
}
final class Container
{
/** @var array<string, mixed> */
private array $bindings = [];
/** @var array<string, object> */
private array $kesh = [];
public function bind(string $kalit, mixed $qiymat): void
{
$this->bindings[$kalit] = $qiymat;
}
public function get(string $class): object
{
if (isset($this->kesh[$class])) {
return $this->kesh[$class];
}
$r = new ReflectionClass($class);
$ctor = $r->getConstructor();
if ($ctor === null) {
return $this->kesh[$class] = new $class();
}
$args = [];
foreach ($ctor->getParameters() as $param) {
$type = $param->getType();
// 1) Sinf turi -> rekursiv yechamiz (autowiring)
if ($type instanceof ReflectionNamedType && !$type->isBuiltin()) {
$args[] = $this->get($type->getName());
continue;
}
// 2) Ichki tur + #[Inject('kalit')] -> bindings dan olamiz
$injectAttrs = $param->getAttributes(Inject::class);
if ($injectAttrs !== []) {
$kalit = $injectAttrs[0]->newInstance()->kalit;
if (!array_key_exists($kalit, $this->bindings)) {
throw new RuntimeException("Bog'lanmagan kalit: {$kalit}");
}
$args[] = $this->bindings[$kalit];
continue;
}
// 3) Standart qiymat bo'lsa -> shuni olamiz
if ($param->isDefaultValueAvailable()) {
$args[] = $param->getDefaultValue();
continue;
}
// 4) Hech narsa yo'q -> xato
throw new RuntimeException(
"Yechib bolmaydi: \${$param->getName()} (sinfda {$class})"
);
}
return $this->kesh[$class] = $r->newInstanceArgs($args);
}
}
// Konstruktorga #[Inject] qo'yilgan sinf:
final class Database
{
public function __construct(
#[Inject('db.dsn')] public string $dsn,
#[Inject('db.timeout')] public int $timeout,
) {}
}
$c = new Container();
$c->bind('db.dsn', 'mysql:host=localhost;dbname=app');
$c->bind('db.timeout', 30);
$db = $c->get(Database::class);
echo "DSN: {$db->dsn}" . PHP_EOL;
echo "Timeout: {$db->timeout}" . PHP_EOL;
Chiqishi:
Tushuntirish: kalit g'oya β har bir konstruktor parametri uchun tartib bilan uch holatni tekshirish: (1) sinf turi bo'lsa avtomatik yaratish, (2) #[Inject] atributi bo'lsa sozlamalar jadvalidan olish, (3) standart qiymat bo'lsa shuni olish, aks holda xato. Bu β Laravel/PHP-DI konteynerlarining haqiqiy ish printsipi.
Yechim β 8 (atribut-asosli serializator)
<?php
declare(strict_types=1);
#[Attribute(Attribute::TARGET_PROPERTY)]
final class Column
{
public function __construct(public ?string $name = null) {}
}
#[Attribute(Attribute::TARGET_PROPERTY)]
final class Ignore {}
final class Mahsulot
{
#[Column('id')]
public int $id = 0;
#[Column('mahsulot_nomi')]
public string $nom = '';
#[Column] // nom berilmasa, property nomidan foydalanamiz
public float $narx = 0.0;
#[Ignore]
public string $ichkiHolat = 'maxfiy';
// Atributsiz property β e'tiborga olinmaydi.
public string $vaqtinchalik = '';
}
function toRow(object $obj): array
{
$row = [];
$r = new ReflectionObject($obj);
foreach ($r->getProperties() as $prop) {
// Ignore bo'lsa β tashlab ketamiz.
if ($prop->getAttributes(Ignore::class) !== []) {
continue;
}
$colAttrs = $prop->getAttributes(Column::class);
if ($colAttrs === []) {
continue; // Column yo'q β DB ustuni emas
}
$col = $colAttrs[0]->newInstance();
$name = $col->name ?? $prop->getName();
$row[$name] = $prop->getValue($obj);
}
return $row;
}
$m = new Mahsulot();
$m->id = 7;
$m->nom = 'Klaviatura';
$m->narx = 249.99;
$m->ichkiHolat = 'BU CHIQMASLIGI KERAK';
$m->vaqtinchalik = 'BU HAM CHIQMASLIGI KERAK';
echo json_encode(toRow($m), JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . PHP_EOL;
Chiqishi:
Tushuntirish: uchta nazorat: (1) #[Ignore] bor bo'lsa β to'liq tashlab ketamiz; (2) #[Column] umuman yo'q bo'lsa β bu DB ustuni emas, tashlab ketamiz; (3) #[Column] bor β name ni atributdan, bo'lmasa property nomidan olamiz. ReflectionObject β ReflectionClass ga o'xshash, lekin obyektdan to'g'ridan-to'g'ri yaratiladi; instance (non-static) propertyni o'qishda getValue() ga obyektni uzatish SHART (ReflectionObject yoki ReflectionClass farqi yo'q β usiz TypeError otiladi). Bu β Doctrine ORM va symfony/serializer ning soddalashtirilgan modeli.
Yechim β 4 (max validatsiya qoidasi)
Validate::tekshir() ning match ifodasiga yangi tarmoq qo'shing:
'max' => (is_string($value) && mb_strlen($value) <= ($this->param ?? PHP_INT_MAX))
? null : "ko'pi bilan {$this->param} ta belgi",
So'ng #[Validate('max', 20)] ni propertyga qo'yib, 20 belgidan uzun qiymat bilan sinab ko'ring β match qolgan barcha qoidalar bilan birga ishlaydi, chunki IS_REPEATABLE har bir atributni alohida newInstance() qiladi.
Reflection va atributlar β zamonaviy PHP frameworklarining poydevori: router, DI, ORM, validator β hammasi shu ikki ustunga tayanadi. Endi siz ularning "sehrini" ko'rasiz: bu shunchaki getAttributes()->newInstance(). FFI esa β kamdan-kam, lekin kuchli vosita; uni bilish ekspertlik belgisi, ishlatmaslikni bilish esa undan ham muhim. Keyingi bobda PHP ning xotira va kolleksiya vositalariga β WeakMap, SPL va referencelarga β o'tamiz.
β¬ οΈ Oldingi: 07 β Property hooks va asymmetric visibility Β· π README Β· Keyingi: 09 β WeakMap, SPL va reference β‘οΈ