05 β Qat'iy tiplash va PHP 8.4 tip tizimi¶
β¬ οΈ Oldingi: 04 β JWT va stateless auth Β· π README Β· Keyingi: 06 β readonly, Value Object va variance β‘οΈ
Bu bobda: tip tizimini shunchaki "xato ushlagich" emas, balki dizayn vositasi sifatida ishlatishni o'rganamiz. Avval har faylda
declare(strict_types=1)nega zarurligini va u o'chirilganda PHP "5" satrini jimginaintga aylantirib qanday xavf tug'dirishini ko'ramiz. So'ng PHP 8.4 ning to'liq tip arsenalini ochamiz: skalyar turlar va?T,A|Bunion,A&Bintersection,(A&B)|CDNF, hamdanever/void/mixedvafalse/trueliteral turlari. Keyin type narrowing (instanceof,is_*,match (true)) bilan kompilyatorga "endi bu aniq shu tur" deb ishontiramiz, va nihoyat==vs===hamda son-satr solishtirish tuzoqlarini chetlab o'tamiz. Bu bob boshlovchi kitobdagi class va obyekt, kirish darajalari, enum va xatolarni boshqarish boblariga tayanadi β ularni qayta o'rgatmaymiz, balki ekspert nuqtayi nazaridan chuqurlashtiramiz.
Nega tip β bu dizayn, xato ushlagich emas¶
Boshlovchi sifatida tipni odatda "qiymat int mi yoki string mi" degan texnik tafsilot deb qabul qilamiz. Ekspert nuqtayi nazaridan esa tur β bu shartnoma (contract). function add(int $a, int $b): int deb yozganingizda siz uchta narsani bir vaqtda e'lon qilasiz:
- Hujjat β bu funksiyani chaqiruvchi kishi nima berishi va nima olishini izohsiz biladi.
- Himoya β noto'g'ri tur kelsa, PHP ishlashni davom ettirmay, darhol
TypeErrortashlaydi. Xato chuqurga kirib, allaqachon ma'lumotni buzgandan keyin emas, balki kirish chegarasida tutiladi. - Tahlil imkoni β PHPStan/Psalm kabi statik analizatorlar va IDE kodingizni ishga tushirmasdan tekshiradi: noto'g'ri tur, mavjud bo'lmagan metod,
nullehtimoli β bularning hammasi kompilyatsiyagacha aniqlanadi.
Tipni qancha aniq (specific) qilsangiz, shartnoma shuncha kuchli bo'ladi. mixed β "har narsa bo'lishi mumkin", ya'ni aslida hech qanday shartnoma yo'q. int esa juda qattiq va'da. Shu sababli bu bobning markaziy qoidasi:
Oltin qoida: imkon qadar eng aniq turni tanlang.
mixeddan qoching,?Tdan zarurat bo'lsa foydalaning,A|Bfaqat haqiqatan ikki xil natija bo'lganda,A&Bβ bir nechta interfeys talab qilinganda.
declare(strict_types=1) β eng muhim bir qator¶
PHP ikki rejimda ishlaydi: coercive (jimgina aylantiruvchi, standart) va strict (qat'iy). Farqi shundaki β parametr turi mos kelmaganda PHP qiymatni aylantiradimi yoki xato tashlaydimi.
declare(strict_types=1) β bu har bir PHP faylning birinchi qatori (<?php dan keyin, boshqa har qanday koddan oldin) bo'lishi kerak bo'lgan direktiva. U joriy faylda yozilgan funksiya/metod chaqiruvlarini qat'iy rejimga o'tkazadi.
Coercive rejim: yashirin aylantirish xavfi¶
Quyidagi faylda declare yo'q β demak coercive rejim:
<?php
// DIQQAT: strict_types YO'Q β coercive rejim
function add(int $a, int $b): int {
return $a + $b;
}
echo add("5", "3"), "\n"; // 8 β "5" va "3" jimgina int ga aylandi
Bu "ishladi", lekin xavfli: PHP "5" satrini so'ramasdan 5 ga aylantirdi. Kichik loyihada bilinmaydi, ammo katta tizimda bu "jimlik" sizni aldaydi. Aytaylik, foydalanuvchi formadan kelgan "10 bananlar" qiymati funksiyaga uzatildi:
<?php
// coercive rejim
function add(int $a, int $b): int {
return $a + $b;
}
echo add("10 bananlar", 5), "\n"; // β TypeError (PHP 8.0+)
PHP 8.0 dan boshlab bunday "ifloslangan" satr coercive rejimda ham TypeError tashlaydi β bu yaxshilanish. Lekin tuzoq shundaki, toza son-satrlar hamon jimgina o'tadi:
<?php
// coercive rejim β quyidagi uchala holatning XULQI har xil
function add(int $a, int $b): int { return $a + $b; }
echo add("10 ", 5), "\n"; // 15 β orqadagi bo'sh joy kesiladi, o'tadi
echo add("12.0", 0), "\n"; // 12 β int-ga-aniq-mos float-satr, o'tadi
echo add("12.5", 0), "\n"; // 12 + Deprecated: aniqlik yo'qoladi (12.5 -> 12)
Oxirgi qatorda 12.5 ning .5 qismi yo'qoldi β bu jimgina yuz bergan ma'lumot yo'qotishi. Coercive rejim sizni shu kabi mayda, sezilmas xatolarga ochiq qoldiradi.
Strict rejim: aylantirish o'chadi, TypeError keladi¶
Endi xuddi shu kodga declare(strict_types=1) qo'shamiz:
<?php
declare(strict_types=1);
function add(int $a, int $b): int {
return $a + $b;
}
echo add(5, 3), "\n"; // 8 β to'g'ri turlar, ishlaydi
try {
echo add("5", 3), "\n"; // β strict: aylantirmaydi -> TypeError
} catch (\TypeError $e) {
echo "TypeError: ", $e->getMessage(), "\n";
// add(): Argument #1 ($a) must be of type int, string given
}
Strict rejimda "5" aylantirilmaydi β turlar mos kelmaydi, demak TypeError. Xato darhol, kirish nuqtasida ko'rinadi. Bu β siz xohlagan xulq: nosozlik jimgina davom etmaydi.
Yagona istisno: int β float kengayishi¶
Strict rejim ham bitta xavfsiz aylantirishga ruxsat beradi: int qiymatni float parametrga uzatish (chunki har bir butun son aniq float bo'la oladi, ma'lumot yo'qolmaydi):
<?php
declare(strict_types=1);
function needFloat(float $x): float { return $x; }
echo needFloat(5), "\n"; // 5 β int->float kengayishi strict da ham O'TADI
function needInt(int $x): int { return $x; }
try {
needInt(5.0); // β float->int: ma'lumot yo'qolishi mumkin -> TypeError
} catch (\TypeError $e) {
echo "rad: float ni int parametrga berib bo'lmaydi\n";
}
E'tibor bering: kengayish faqat bir yo'nalishda (int β float). Teskarisi (float β int) .5 kabi qismni yo'qotishi mumkinligi uchun strict rejimda taqiqlanadi.
Amaliy qoida: loyihangizning har bir
.phpfaylidadeclare(strict_types=1)yozing. Bu shartnomalaringizni haqiqiy qiladi.strict_typesfaqat joriy fayldagi chaqiruvlarga ta'sir qiladi (chaqirilgan funksiya qaysi faylda ekanligi muhim emas) β shuning uchun "bitta faylda yozdim, yetadi" degani noto'g'ri; uni hamma joyga yozish kerak.
Skalyar turlar va ?T (nullable)¶
PHP ning to'rt asosiy skalyar turi: int, float, string, bool. Bularga array, object, callable, iterable, hamda har bir sinf/interfeys nomi (User, PDO, ...) qo'shiladi. Bularni boshlovchi kitobda class va obyekt bobida ko'rgansiz; bu yerda ularning tiplash jihatiga e'tibor beramiz.
Nullable tur β ?T β bu "T yoki null" degani. Bu eng ko'p uchraydigan union turi (aslida ?T = T|null ning qisqa shakli):
<?php
declare(strict_types=1);
final class User {
public function __construct(
public int $id,
public string $email,
public ?string $phone = null, // telefon ixtiyoriy bo'lishi mumkin
) {}
}
function findEmail(?User $u): string {
// $u null bo'lishi MUMKIN β buni hisobga olishimiz SHART
if ($u === null) {
return 'mehmon';
}
return $u->email; // bu yerda $u aniq User (narrowing β quyida ko'ramiz)
}
echo findEmail(new User(1, 'a@b.uz')), "\n"; // a@b.uz
echo findEmail(null), "\n"; // mehmon
?T ning kuchi shundaki, u "yo'qlik" (null) ehtimolini turning o'ziga yozadi. PHPStan endi sizdan null ni tekshirishingizni talab qiladi β aks holda $u->email "ehtimoliy null ga murojaat" deb ogohlantiriladi.
Tuzoq:
?Tfaqatnullni qo'shadi, "bo'sh string" yoki0ni emas.?stringqiymati""(bo'sh satr) bo'lishi mumkin β bunullEMAS. Quyida"" == 0tuzog'ida bu farq muhim bo'ladi.
A|B union: "yoki" turi¶
Union tur (A|B) qiymat bir nechta turdan biri bo'lishi mumkinligini bildiradi (PHP 8.0). Eng tabiiy ishlatilishi β funksiya ikki xil natija qaytarishi mumkin bo'lganda. Klassik misol: topildi/topilmadi.
false literal turi (8.2) bilan birga "topilmadi" holatini aniq ifodalaymiz:
<?php
declare(strict_types=1);
final class User {
public function __construct(public string $name) {}
}
function findUser(int $id): User|false {
// bazadan qidirish o'rniga soddalashtirilgan misol
return $id === 1 ? new User('Oqil') : false;
}
$u = findUser(1);
echo $u === false ? 'topilmadi' : $u->name, "\n"; // Oqil
var_dump(findUser(2) === false); // true β topilmadi
User|false β "User yoki false (topilmadi)" degan o'qiladigan shartnoma. Buni User|null bilan ham yozish mumkin; tanlov uslubga bog'liq, lekin false ko'pincha "muvaffaqiyatsizlik" ni, null esa "qiymat yo'q" ni bildiradi.
Union β "Result" naqshining asosi¶
Union turlari xatolarni exception siz ifodalashning chiroyli usuli. Boshlovchi kitobda xatolarni boshqarish da try/catch ni ko'rdingiz; ba'zan esa "xato β bu kutilgan natija" bo'ladi (masalan, foydalanuvchi kiritmasini tekshirish). Bunday holatda Ok|Err union tabiiyroq:
<?php
declare(strict_types=1);
final class Ok { public function __construct(public int $value) {} }
final class Err { public function __construct(public string $error) {} }
function parsePositive(string $in): Ok|Err {
if (!ctype_digit($in)) {
return new Err('raqam emas');
}
$n = (int) $in;
return $n > 0 ? new Ok($n) : new Err('musbat emas');
}
$r = parsePositive('42');
echo $r instanceof Ok ? "OK={$r->value}" : "ERR={$r->error}", "\n"; // OK=42
$r = parsePositive('x');
echo $r instanceof Ok ? "OK={$r->value}" : "ERR={$r->error}", "\n"; // ERR=raqam emas
Bu yerda chaqiruvchi majburan ikkala holatni ko'rib chiqishi kerak β tur uni shunga undaydi. Bu exception dan farqi: xato "alohida kanal" emas, oddiy qaytish qiymati.
Qachon union? Faqat haqiqatan bir nechta mantiqan teng natija bo'lganda. Agar union 4-5 turdan iborat bo'lsa β bu odatda dizayn xatosi belgisi; balki umumiy interfeys (
A&Byoki abstract sinf) kerakdir.
A&B intersection: "ham...ham" turi¶
Intersection tur (A&B) qiymat bir vaqtda barcha sanab o'tilgan turlarga mos kelishini talab qiladi (PHP 8.1). Muhim cheklov: intersection da faqat interfeyslar (va class/interface nomlari) ishlatiladi β skalyar turlar (int&string ma'nosiz) bo'lmaydi.
Bu nima uchun kerak? Tasavvur qiling, funksiyangiz obyekt ham sanaladigan (Countable), ham aylanib chiqiladigan (Traversable) bo'lishini xohlaydi:
<?php
declare(strict_types=1);
interface HasId { public function id(): int; }
interface Named { public function name(): string; }
final class User implements HasId, Named {
public function __construct(private int $id, private string $n) {}
public function id(): int { return $this->id; }
public function name(): string { return $this->n; }
}
// $e ham HasId, HAM Named bo'lishi shart:
function label(HasId&Named $e): string {
return $e->id() . ':' . $e->name(); // ikkala interfeysga ham tayanamiz
}
echo label(new User(7, 'Olim')), "\n"; // 7:Olim
HasId&Named β "shu obyekt ikkala shartnomani ham bajaradi" degani. User ikkalasini ham implements qilgani uchun mos keladi. Faqat HasId ni amalga oshirgan boshqa sinf esa mos kelmaydi β bu aynan xohlagan qattiqlikingiz.
Union vs intersection:
A|Bβ "kamida biri" (kengroq, ko'proq qiymat o'tadi).A&Bβ "hammasi" (torroq, kamroq qiymat o'tadi, lekin ko'proq imkoniyat beradi, chunki ikkala interfeys metodlarini ham chaqira olasiz).
(A&B)|C DNF: union va intersection birga¶
DNF (Disjunctive Normal Form, 8.2) β intersection va union ni birga ishlatish imkoni. Sintaksis: intersection guruhlari qavs ichida, ular | bilan birlashtiriladi. Eng ko'p uchraydigan zarurat β intersection turini nullable qilish:
<?php
declare(strict_types=1);
interface HasId { public function id(): int; }
interface Named { public function name(): string; }
final class User implements HasId, Named {
public function __construct(private int $id, private string $n) {}
public function id(): int { return $this->id; }
public function name(): string { return $this->n; }
}
function label(HasId&Named $e): string {
return $e->id() . ':' . $e->name();
}
// (HasId&Named) YOKI null:
function maybe((HasId&Named)|null $e): string {
return $e === null ? 'yoq' : label($e);
}
echo maybe(null), "\n"; // yoq
echo maybe(new User(9, 'Vali')), "\n"; // 9:Vali
(HasId&Named)|null ni ?(HasId&Named) deb yozib bo'lmaydi β ? qisqartmasi murakkab intersection bilan ishlamaydi, shuning uchun DNF zarur. Qavslar (A&B) atrofida majburiy: PHP turning "normal shaklini" talab qiladi.
Qachon DNF? Amalda asosan
(A&B)|nullko'rinishida. Murakkabroq(A&B)|(C&D)nazariy jihatdan mumkin, lekin kamdan-kam kerak bo'ladi β agar shunga ehtiyoj tug'ilsa, dizayningizni qayta ko'rib chiqing.
Maxsus turlar: never, void, mixed¶
never β funksiya hech qachon qaytmaydi (8.1)¶
never qaytish turi PHP ga (va analizatorlarga) "bu funksiya hech qachon normal yo'l bilan qaytmaydi" deb aytadi β u doim throw qiladi yoki exit/die chaqiradi:
<?php
declare(strict_types=1);
function fail(string $msg): never {
throw new \RuntimeException($msg);
// bu yerdan keyin kod YO'Q β qaytish imkonsiz
}
function getOrFail(?string $value): string {
return $value ?? fail('qiymat yo`q');
// PHP biladi: fail() qaytmaydi, demak bu yerga yetganda $value aniq string
}
echo getOrFail('bor'), "\n"; // bor
try {
getOrFail(null);
} catch (\RuntimeException $e) {
echo "tutildi: ", $e->getMessage(), "\n"; // tutildi: qiymat yo`q
}
never ning kuchi β boshqaruv oqimini tahlil qilishni yaxshilaydi. Analizator fail() dan keyin kod "o'lik" ekanini biladi va ?? dan keyin $value ning null bo'la olmasligini tushunadi. void dan farqi: void "qaytadi, lekin qiymatsiz", never esa "umuman qaytmaydi".
void β qaytaradi, lekin qiymatsiz¶
void β funksiya ish bajaradi, ammo foydali qiymat qaytarmaydi (yon-effekt uchun: log yozish, saqlash, chop etish):
<?php
declare(strict_types=1);
function logMsg(string $m): void {
echo "[LOG] $m\n";
// 'return;' yozish mumkin (qiymatsiz), lekin 'return $x;' XATO
}
logMsg('boshlandi'); // [LOG] boshlandi
void funksiyadan qiymat qaytarsangiz (return 5;) β bu xato. void ning natijasini o'zgaruvchiga olsangiz, u null bo'ladi, lekin bunga tayanmaslik kerak β bu "natija yo'q" degani.
mixed β "tip yo'q" degani (oxirgi chora)¶
mixed har qanday turni qabul qiladi: int|float|string|bool|array|object|callable|null|resource. Texnik jihatdan u "hamma narsa", amalda esa "men turni bilmayman" degani β ya'ni shartnomaning yo'qligi:
<?php
declare(strict_types=1);
function dump(mixed $v): void {
echo get_debug_type($v), "\n"; // qiymatning ANIQ turini chiqaradi
}
dump(1); // int
dump(1.5); // float
dump('x'); // string
dump([1, 2]); // array
dump(null); // null
dump(new stdClass); // stdClass
dump(fn() => 1); // Closure
get_debug_type() β gettype() ga qaraganda aniqroq (sinf nomini to'liq beradi, "integer" emas "int" deydi). mixed ni faqat haqiqatan har qanday tur kelishi mumkin bo'lganda ishlating (masalan, umumiy serializatsiya, var_dump kabi vositalar). 99% holatda mixed β dizayningizni yetarli o'ylamaganingiz belgisi.
Eslatma:
mixedparametr β eng zaif shartnoma. Agar funksiyangizmixedqabul qilsa, ichida deyarli har doimis_*/instanceofbilan narrowing qilishingiz kerak bo'ladi (keyingi bo'lim). Ko'pincha buning o'rniga aniq union (int|string) yozish to'g'riroq.
false/true literal turlar (8.2)¶
PHP 8.2 dan false va true ni mustaqil literal tur sifatida ishlatish mumkin. false ni yuqorida User|false da ko'rdik. Yolg'iz true yoki false kamdan-kam, lekin ma'noli holatlari bor β masalan, har doim true qaytaradigan (yoki exception tashlaydigan) metod:
<?php
declare(strict_types=1);
final class Validator {
/** Tekshiruv o'tsa true; aks holda exception (hech qachon false qaytmaydi) */
public function assertEmail(string $value): true {
if (!str_contains($value, '@')) {
throw new \InvalidArgumentException('email noto`g`ri');
}
return true;
}
}
$v = new Validator();
var_dump($v->assertEmail('a@b.uz')); // bool(true)
: true qaytish turi o'quvchiga "bu metod faqat true qaytaradi yoki umuman qaytmaydi (throw)" degan kuchli signal beradi. Bu β niyatni turga yozishning yana bir misoli.
Qaytish turlari: self vs static vs parent¶
Sinf ichida qaytish turi sifatida uchta maxsus kalit so'z ishlatiladi. Ularning farqi meros (inheritance) da ko'rinadi:
selfβ shu sinf (e'lon qilingan joydagi sinf), meros bo'lsa ham o'zgarmaydi.staticβ chaqirilgan haqiqiy sinf (kech statik bog'lanish, LSB). Bola sinfdan chaqirilsa, bola turini qaytaradi.parentβ ota sinf turi (kamdan-kam).
<?php
declare(strict_types=1);
class Model {
public static function create(): static { // "chaqirgan sinf" turi
return new static();
}
public function copy(): self { // doim Model
return clone $this;
}
}
final class Post extends Model {}
var_dump(Post::create() instanceof Post); // true β static: Post qaytdi
var_dump(Model::create() instanceof Model); // true
static β fluent interfeys va fabrika (factory) metodlari uchun zarur: Post::create() chaqirilganda Post obyektini qaytaradi, self bo'lganida esa har doim Model qaytar edi. Bu kovariant qaytish (covariant return) ning bir ko'rinishi β bola sinf otaga nisbatan aniqroq turni qaytarishi mumkin. (Kovariantlik va variance ni keyingi 06-bobda chuqurroq ko'ramiz.)
Type narrowing: kompilyatorga "endi bu aniq shu tur" deyish¶
Narrowing (toraytirish) β keng turdan (mixed, union, ?T) aniqroq turga "o'tish". Siz is_* yoki instanceof bilan tekshirgandan keyin, shu shox ichida PHP (va analizator) qiymatning aniq turini biladi. Bu β union va ?T ni xavfsiz ishlatishning asosiy mexanizmi.
null tekshiruvi bilan narrowing¶
<?php
declare(strict_types=1);
function greet(?string $name): string {
if ($name === null) {
return 'Salom, mehmon!';
}
// bu yerdan keyin $name aniq STRING β narrowing yuz berdi
return "Salom, " . strtoupper($name) . "!";
}
echo greet('oqil'), "\n"; // Salom, OQIL!
echo greet(null), "\n"; // Salom, mehmon!
null ni boshida tekshirib, erta qaytarish ("early return") β eng toza naqsh. Keyingi kodda $name aniq string, demak strtoupper() xavfsiz.
instanceof va is_* bilan union ni tarmoqlash¶
match (true) bilan tur bo'yicha tarmoqlash β narrowing ning eng ifodali shakli. Bu ko'pincha cheksiz if/elseif zanjiridan toza:
<?php
declare(strict_types=1);
function describe(int|string|null $v): string {
return match (true) {
$v === null => 'qiymat: yo`q',
is_int($v) => "butun son: $v",
is_string($v) => "satr: \"$v\"",
};
}
echo describe(42), "\n"; // butun son: 42
echo describe('salom'), "\n"; // satr: "salom"
echo describe(null), "\n"; // qiymat: yo`q
Real domenda bu naqsh "tur bo'yicha dispatcher" sifatida juda kuchli. Aytaylik, to'lov usuliga qarab har xil matn:
<?php
declare(strict_types=1);
interface PaymentMethod {}
final class CardPayment implements PaymentMethod { public function __construct(public string $last4) {} }
final class CashPayment implements PaymentMethod { public function __construct(public int $amount) {} }
final class CryptoPayment implements PaymentMethod { public function __construct(public string $wallet) {} }
function receipt(PaymentMethod $p): string {
return match (true) {
$p instanceof CardPayment => "Karta ***{$p->last4}",
$p instanceof CashPayment => "Naqd {$p->amount} so`m",
$p instanceof CryptoPayment => "Kripto {$p->wallet}",
};
}
echo receipt(new CardPayment('4242')), "\n"; // Karta ***4242
echo receipt(new CashPayment(50000)), "\n"; // Naqd 50000 so`m
echo receipt(new CryptoPayment('bc1q...')), "\n"; // Kripto bc1q...
Har bir instanceof shoxida $p ning aniq turi ma'lum bo'lgani uchun $p->last4, $p->amount, $p->wallet ga xavfsiz murojaat qila olamiz β IDE ham, analizator ham xato qilmaydi.
array_is_list β massiv shaklini tekshirish¶
PHP massivlari "list" (0,1,2,... kalitli) yoki "assotsiativ" bo'lishi mumkin. array_is_list() (8.1) bu farqni aniqlaydi β JSON serializatsiyasi va validatsiyada zarur:
<?php
declare(strict_types=1);
function jsonKind(array $a): string {
if ($a === []) return 'bo`sh';
return array_is_list($a) ? 'JSON massiv []' : 'JSON obyekt {}';
}
var_dump(array_is_list([1, 2, 3])); // true β list
var_dump(array_is_list([1 => 'a'])); // false β 0 dan boshlanmaydi
echo jsonKind(['a', 'b']), "\n"; // JSON massiv []
echo jsonKind(['id' => 1]), "\n"; // JSON obyekt {}
Bu narrowing ning massivlarga tatbiqi: bir "array" turi ichidagi ikki xil shaklni ajratamiz, chunki PHP ning tip tizimi list<int> vs array<string,int> farqini o'zi bilmaydi (buni faqat PHPStan/Psalm generiklari biladi).
Type coercion tuzoqlari: ==, === va son-satr solishtirish¶
Tiplashning eng zararli tuzog'i β bo'sh (loose) solishtirish ==. U turlarni jimgina aylantirib solishtiradi, natijada kutilmagan haqiqat qiymatlari chiqadi. Qoida: deyarli har doim === (qat'iy, tur ham mos kelsin) ishlating.
PHP 8.0 son-satr solishtirishni tuzatdi: endi int ni non-numeric string bilan solishtirganda, son satr ga emas, satr songa aylanmaydi β ikkalasi satr sifatida solishtiriladi. Bu juda muhim xavfsizlik yaxshilanishi:
<?php
declare(strict_types=1);
// PHP 8.0+ semantikasi:
var_dump(0 == "abc"); // false (8.0 GACHA: true edi! β xavfli)
var_dump(0 == ""); // false (8.0 gacha: true edi)
var_dump(0 == "0"); // true ("0" β numeric, songa aylanadi)
var_dump("" == null); // true (ikkalasi "bo`sh" deb hisoblanadi)
var_dump("1" == "01"); // true (ikkalasi numeric -> son sifatida: 1==1)
var_dump("10" == "1e1"); // true (1e1 = 10.0, numeric)
var_dump(100 == "1e2"); // true (1e2 = 100)
var_dump(0 === "0"); // false (=== : int va string har xil tur)
0 == "abc" ning eski (8.0 gacha) true natijasi katta xavfsizlik kamchiligi edi: if ($userInput == 0) kabi tekshiruv "abc" kiritmasini ham true deb hisoblardi. PHP 8.0 buni tuzatdi, lekin eski kodga tayanmang β har doim === ishlating.
"" va 0 farqi β ?? vs ?:¶
Yana bir tuzoq: 0, "", "0", [], null, false β bularning hammasi if da "falsy". Lekin ular bir-biridan farq qiladi. ?: (Elvis) "falsy" ni tekshiradi, ?? (null coalescing) esa faqat null ni:
<?php
declare(strict_types=1);
function quantity(?int $n): int {
// β XATO: 0 ham "falsy" β foydalanuvchi 0 kiritganda ham 1 ga aylantiradi
// return $n ?: 1;
// β
TO'G'RI: faqat null bo'lsa standart qiymat
return $n ?? 1;
}
echo quantity(0), "\n"; // 0 β to'g'ri (0 ham haqiqiy qiymat)
echo quantity(null), "\n"; // 1 β standart
echo quantity(5), "\n"; // 5
?: foydalanuvchi atayin kiritgan 0 yoki "" ni "yo'q" deb xato talqin qiladi. ?? faqat null ni ushlaydi β shu sababli ?T turidagi qiymatlar uchun deyarli har doim ?? to'g'riroq.
Massiv kaliti coercion'i¶
Massiv kalitlari ham jimgina aylanadi: "1", 1, true β hammasi bitta kalit (1) ga aylanadi:
<?php
declare(strict_types=1);
$arr = [];
$arr["1"] = 'a'; // "1" -> int kalit 1
$arr[1] = 'b'; // o'sha kalit β ustiga yozadi
$arr[true] = 'c'; // true -> 1 β yana o'sha kalit
var_dump(count($arr)); // 1 β uchalasi bir kalitga tushdi
var_dump($arr[1]); // "c"
Bu tuzoq β kalitlar foydalanuvchidan kelganda (masalan ID lar satr sifatida) kutilmagan ustiga-yozishni keltirib chiqaradi.
To'liq tiplangan domen sinfi: hammasini birlashtiramiz¶
Endi yuqoridagi g'oyalarni real Money value object ida birlashtiramiz β har bir property, parametr va return turlangan, readonly bilan o'zgarmas (immutable), va xato turlar bilan himoyalangan. (readonly va value object dizaynini keyingi 06-bobda batafsil ochamiz; bu yerda tiplashga e'tibor.)
<?php
declare(strict_types=1);
final class Money {
public function __construct(
public readonly int $amount, // tiyinlarda (float emas β aniqlik uchun!)
public readonly string $currency, // ISO kod: 'UZS', 'USD'
) {
if ($amount < 0) {
throw new \InvalidArgumentException('summa manfiy bo`la olmaydi');
}
if (strlen($currency) !== 3) {
throw new \InvalidArgumentException('valyuta kodi 3 harf bo`lsin');
}
}
public function add(Money $other): self {
if ($this->currency !== $other->currency) {
throw new \InvalidArgumentException('valyutalar mos emas');
}
return new self($this->amount + $other->amount, $this->currency);
}
public function format(): string {
return number_format($this->amount / 100, 2) . ' ' . $this->currency;
}
}
$wallet = new Money(150_00, 'UZS'); // 150.00 UZS (15000 tiyin)
$income = new Money(50_00, 'UZS'); // 50.00 UZS
$total = $wallet->add($income);
echo $total->format(), "\n"; // 200.00 UZS
// β readonly propertyni tashqaridan o'zgartirish -> Error
try {
$total->amount = 9999;
} catch (\Error $e) {
echo "Error: ", $e->getMessage(), "\n"; // Cannot modify readonly property Money::$amount
}
// β valyuta mos kelmasa -> InvalidArgumentException
try {
$wallet->add(new Money(10_00, 'USD'));
} catch (\InvalidArgumentException $e) {
echo "rad: ", $e->getMessage(), "\n"; // rad: valyutalar mos emas
}
E'tibor bering: amount ni float emas, int (tiyinlarda) qildik β chunki float arifmetikasi pul uchun xavfli (yaxlitlash xatolari). Bu β turni dizayn vositasi sifatida ishlatishning yorqin misoli: tur tanlovingiz xatolikni strukturaviy ravishda oldini oladi.
PHP 8.4 yangiligi: property hooks va asymmetric visibility¶
PHP 8.4 tiplangan property'larni yanada kuchaytirdi. Property hooks (get/set) β property o'qish/yozishda kod ishlatishga imkon beradi, lekin tashqaridan u oddiy property kabi ko'rinadi:
<?php
declare(strict_types=1);
final class Temperature {
public function __construct(private float $celsius) {}
// hisoblanadigan property β saqlanmaydi, har o'qishda hisoblanadi
public float $fahrenheit {
get => $this->celsius * 9 / 5 + 32;
set (float $f) { $this->celsius = ($f - 32) * 5 / 9; }
}
}
$t = new Temperature(25.0);
echo $t->fahrenheit, "\n"; // 77 β get hook ishladi
$t->fahrenheit = 212.0; // set hook: celsius ni qayta hisoblaydi
echo $t->fahrenheit, "\n"; // 212
get/set hook turlari ham qat'iy: set (float $f) faqat float qabul qiladi. Bu β getter/setter metodlarning toza, tiplangan muqobili.
Asymmetric visibility (public private(set)) β property'ni tashqaridan o'qish mumkin, lekin faqat sinf ichidan yozish mumkin qiladi. Bu boshlovchi kitobdagi kirish darajalari ning kuchaytirilgan shakli:
<?php
declare(strict_types=1);
final class Account {
// tashqaridan O'QISH ochiq, YOZISH faqat ichkaridan:
public function __construct(public private(set) int $balance) {}
public function deposit(int $amount): void {
if ($amount <= 0) {
throw new \InvalidArgumentException('summa musbat bo`lsin');
}
$this->balance += $amount; // ichkaridan yozish β RUXSAT
}
}
$a = new Account(100);
echo $a->balance, "\n"; // 100 β tashqaridan o'qish mumkin
$a->deposit(50);
echo $a->balance, "\n"; // 150
// β tashqaridan yozish -> Error
try {
$a->balance = 999;
} catch (\Error $e) {
echo "rad: ", $e->getMessage(), "\n"; // Cannot modify private(set) property...
}
public private(set) readonly dan farq qiladi: readonly faqat bir marta (konstruktorda) yozishga ruxsat beradi; private(set) esa sinf ichidan istalgancha o'zgartirishga, lekin tashqaridan umuman yozishga ruxsat bermaydi. balance β aynan shunday: ichkarida o'zgaradi, tashqaridan faqat ko'rinadi.
#[Override] atributi β meros xatolarini ushlash¶
PHP 8.3 ning #[Override] atributi metod haqiqatan ota sinf/interfeys metodini qoplayotganini tekshiradi. Agar metod nomini xato yozsangiz (yoki ota o'zgarib qolsa), kompilyatsiya xatosi olasiz β bu tip tizimining "shartnoma to'g'riligi" jihatini kuchaytiradi:
<?php
declare(strict_types=1);
interface Repo {
public function find(int $id): ?string;
}
final class MemoryRepo implements Repo {
private array $data = [1 => 'birinchi'];
#[\Override] // "men Repo::find ni qoplayapman" β PHP tekshiradi
public function find(int $id): ?string {
return $this->data[$id] ?? null;
}
}
$repo = new MemoryRepo();
var_dump($repo->find(1)); // string(8) "birinchi"
var_dump($repo->find(9)); // NULL
Agar find ni adashib fnd deb yozsangiz, #[Override] tufayli PHP "ota turida bunday metod yo'q" deb darhol xato beradi β narrowing/tiplash bilan birga, bu meros shartnomalarini ham himoya qiladi.
Ko'prik: bu boblar (tip tizimi, value object, PSR standartlari) keyinroq o'z mini-frameworkingizni qurish (L4 β router + DI konteyner) uchun poydevor. Qat'iy tiplangan, intersection bilan ishlaydigan servislar DI konteynerga "qaysi interfeysni qaysi sinf bajaradi" deb ishonchli aytishga imkon beradi.
Tip tizimini dizayn vositasi sifatida: xulosa¶
| Vosita | Qachon | Tuzoq |
|---|---|---|
declare(strict_types=1) |
Har faylda | Yo'q bo'lsa "5" jimgina int bo'ladi |
int/string/... |
Imkon bo'lsa har doim | Eng aniqini tanlang |
?T (nullable) |
Qiymat yo'q bo'lishi mumkin | ?? ishlating, ?: emas |
A\|B union |
Bir nechta teng natija | 4-5 turdan ko'p β dizayn xatosi |
A&B intersection |
Bir nechta interfeys talab | Faqat interfeyslar |
(A&B)\|null DNF |
Intersection + nullable | Qavslar majburiy |
never |
Doim throw/exit | Oqim tahlilini yaxshilaydi |
void |
Yon-effekt, qiymatsiz | return $x; xato |
mixed |
Deyarli hech qachon | "Shartnoma yo'q" demak |
=== |
Deyarli har doim | == jimgina aylantiradi |
Markaziy g'oya: tur β bu sizning niyatingizning mashina o'qiy oladigan, majburlanadigan ifodasi. Qancha aniq tiplasangiz, kodingiz shuncha o'zini-o'zi hujjatlaydi, IDE shuncha yordam beradi, xatolar shuncha erta β kirish chegarasida β tutiladi.
Mashqlar¶
Oson¶
- Berilgan funksiyaga
declare(strict_types=1)qo'shing vaadd("5", 3)chaqiruvi endi nima qilishini (natija yoki xato) ayting. Keyinadd(5, 3.0)vaadd(5.0, 3)ni strict rejimda sinab, qaysi biri o'tishini tushuntiring. ?string $nameparametrligreetfunksiyasini yozing:nullkelsa"mehmon", aks holda nomni qaytarsin.??operatoridan foydalaning.describeType(mixed $v): stringfunksiyasiniget_debug_type()bilan yozing vaint,array,null,Closureuchun sinab ko'ring. (Diqqat: funksiyanigetTypedeb nomlamang β PHP funksiya nomlari registrga befarq, shuning uchun u o'rnatilgangettype()bilan to'qnashadi va "Cannot redeclare" xatosi beradi.)
O'rta¶
User|falseqaytaradiganfindUserById(int $id): User|falseyozing (id=1 bo'lsaUser, aks holdafalse). Chaqiruvchi tomondainstanceofyoki=== falsebilan natijani to'g'ri tarmoqlang.match (true)bilanformat(int|float|string $v): stringyozing:int->"butun: N",float->"kasr: N",string->"matn: ...".- Quyidagi solishtirishlarning PHP 8.4 dagi natijasini (true/false) avval bashorat qiling, keyin run qilib tekshiring:
0 == "abc",0 == "0","" == null,"1e2" == 100,0 === "0".
Qiyin¶
Shapeinterfeysi va uni amalga oshiruvchiCircle,Rectangle,Trianglesinflarini yozing. So'ngtotalArea(Shape ...$shapes): floatfunksiyasini yozing:match (true)vainstanceofbilan har bir shaklning yuzini hisoblab, yig'indini qaytarsin. Har bir sinfreadonlyproperty bilan o'zgarmas bo'lsin.HasIdvaTimestampedinterfeyslarini yozing.(HasId&Timestamped)|nullqabul qiluvchisummaryfunksiyasini yozing:nullbo'lsa"yoq", aks holda"#{id} ({yaratilgan_sana})". DNF turidan to'g'ri foydalaning va nima uchun?(HasId&Timestamped)` yozib bo'lmasligini izohlang.
Yechim β 1
<?php
declare(strict_types=1);
function add(int $a, int $b): int { return $a + $b; }
// add("5", 3): strict da "5" int ga AYLANTIRILMAYDI -> TypeError
try {
add("5", 3);
} catch (\TypeError $e) {
echo "add(\"5\", 3): TypeError\n";
}
// add(5, 3.0): 2-parametr int kutilyapti, 3.0 float -> TypeError
try {
add(5, 3.0);
} catch (\TypeError $e) {
echo "add(5, 3.0): TypeError (float ni int ga bermaymiz)\n";
}
// add(5.0, 3): xuddi shu sabab -> TypeError
try {
add(5.0, 3);
} catch (\TypeError $e) {
echo "add(5.0, 3): TypeError\n";
}
Strict rejimda uchala chaqiruv ham TypeError beradi: "5" satr, 3.0 va 5.0 float β birortasi int emas. Yagona istisno int qiymatni float parametrga berishdir (needFloat(5)), bu yerda esa aksincha (float ni int parametrga) β bu taqiqlangan.
Yechim β 2
<?php
declare(strict_types=1);
function greet(?string $name): string {
return 'Salom, ' . ($name ?? 'mehmon') . '!';
}
echo greet('Oqil'), "\n"; // Salom, Oqil!
echo greet(null), "\n"; // Salom, mehmon!
echo greet(''), "\n"; // Salom, ! β DIQQAT: "" null EMAS, shuning uchun o'tadi
?? faqat null ni almashtiradi. Agar ?: ishlatganingizda bo'sh satr "" ham "mehmon" ga aylanardi β bu odatda noto'g'ri. ?T bilan deyarli har doim ?? to'g'riroq.
Yechim β 3
<?php
declare(strict_types=1);
function describeType(mixed $v): string {
return get_debug_type($v);
}
echo describeType(42), "\n"; // int
echo describeType([1, 2]), "\n"; // array
echo describeType(null), "\n"; // null
echo describeType(fn() => 1), "\n"; // Closure
echo describeType(new stdClass), "\n";// stdClass
get_debug_type() gettype() dan afzal: u "integer" o'rniga "int", obyektlar uchun esa to'liq sinf nomini qaytaradi. Funksiyani getType deb nomlasak edi, PHP uni o'rnatilgan gettype() ning takrori deb hisoblab xato berardi β PHP funksiya nomlari registrga befarq.
Yechim β 4
<?php
declare(strict_types=1);
final class User {
public function __construct(public int $id, public string $name) {}
}
function findUserById(int $id): User|false {
return $id === 1 ? new User(1, 'Oqil') : false;
}
$u = findUserById(1);
if ($u === false) {
echo "topilmadi\n";
} else {
echo "topildi: {$u->name}\n"; // narrowing: $u aniq User
}
var_dump(findUserById(2) === false); // true
=== false (qat'iy) ishlatamiz, == false emas β chunki == false null, 0, "" ni ham true qilardi. else shoxida $u aniq User (narrowing), demak $u->name xavfsiz.
Yechim β 5
<?php
declare(strict_types=1);
function format(int|float|string $v): string {
return match (true) {
is_int($v) => "butun: $v",
is_float($v) => "kasr: $v",
is_string($v) => "matn: $v",
};
}
echo format(42), "\n"; // butun: 42
echo format(3.14), "\n"; // kasr: 3.14
echo format('salom'), "\n";// matn: salom
is_int ni is_float dan oldin tekshiramiz: tartib muhim, chunki shoxlar yuqoridan pastga tekshiriladi. match (true) to'liq qamrab olmasa UnhandledMatchError tashlaydi β bu yerda union ning uchala turi qoplangan.
Yechim β 6
<?php
declare(strict_types=1);
var_dump(0 == "abc"); // false β 8.0+ : son-satr emas, satr sifatida
var_dump(0 == "0"); // true β "0" numeric, songa aylanadi
var_dump("" == null); // true β ikkalasi "bo`sh"
var_dump("1e2" == 100); // true β 1e2 = 100.0 (numeric)
var_dump(0 === "0"); // false β === : int va string har xil tur
Asosiy saboq: 0 == "abc" PHP 8.0 GACHA true edi (xavfli!), endi false. Lekin numeric satrlar ("0", "1e2") hamon songa aylanadi. Shuning uchun har doim === ishlatib, bu noaniqlikdan butunlay qoching.
Yechim β 7
<?php
declare(strict_types=1);
interface Shape {}
final class Circle implements Shape {
public function __construct(public readonly float $radius) {}
}
final class Rectangle implements Shape {
public function __construct(
public readonly float $width,
public readonly float $height,
) {}
}
final class Triangle implements Shape {
public function __construct(
public readonly float $base,
public readonly float $height,
) {}
}
function area(Shape $s): float {
return match (true) {
$s instanceof Circle => M_PI * $s->radius ** 2,
$s instanceof Rectangle => $s->width * $s->height,
$s instanceof Triangle => 0.5 * $s->base * $s->height,
};
}
function totalArea(Shape ...$shapes): float {
$sum = 0.0;
foreach ($shapes as $s) {
$sum += area($s); // har bir shoxda $s ning aniq turi ma'lum
}
return $sum;
}
$total = totalArea(
new Circle(2.0), // ~12.566
new Rectangle(3.0, 4.0),// 12.0
new Triangle(6.0, 2.0), // 6.0
);
echo round($total, 3), "\n"; // ~30.566
Bu yerda bir nechta tip g'oyasi birga ishlaydi: Shape interfeysi umumiy shartnoma; readonly property'lar o'zgarmaslik beradi; Shape ...$shapes variadic parametr har biri Shape bo'lgan istalgancha argument qabul qiladi; match (true) + instanceof esa har bir aniq turga narrowing qilib, mos formulani tanlaydi. area ichida $s instanceof Circle shoxida $s->radius ga xavfsiz murojaat qilamiz β IDE va analizator buni tasdiqlaydi.
Agar yangi shakl (Square) qo'shsangiz va area da uni qo'shishni unutsangiz, match (true) UnhandledMatchError tashlaydi β bu "to'liqlik" xatosini run-time da bo'lsa ham ushlaydi (PHPStan esa buni static tahlilda ham aytadi).
Yechim β 8
<?php
declare(strict_types=1);
interface HasId {
public function id(): int;
}
interface Timestamped {
public function createdAt(): string;
}
final class Order implements HasId, Timestamped {
public function __construct(
private int $id,
private string $createdAt,
) {}
public function id(): int { return $this->id; }
public function createdAt(): string { return $this->createdAt; }
}
// (HasId&Timestamped) YOKI null β DNF
function summary((HasId&Timestamped)|null $e): string {
if ($e === null) {
return 'yo`q';
}
// narrowing: $e endi aniq HasId&Timestamped β ikkala metod ham xavfsiz
return "#{$e->id()} ({$e->createdAt()})";
}
echo summary(null), "\n"; // yo`q
echo summary(new Order(42, '2026-06-11')), "\n"; // #42 (2026-06-11)
Nega ?(HasId&Timestamped) emas? PHP grammatikasi ? qisqartmasini faqat yagona tur bilan ruxsat beradi (?int, ?User). Intersection β murakkab tur, shuning uchun uni nullable qilish uchun DNF (to'liq) shaklini yozish kerak: (HasId&Timestamped)|null. Qavslar majburiy, chunki PHP "disjunktiv normal shakl" ni β ya'ni | bilan ajratilgan, har biri qavsdagi intersection guruhini β talab qiladi.
Order ikkala interfeysni ham implements qilgani uchun HasId&Timestamped ga mos keladi. null shoxidan keyin $e aniq HasId&Timestamped (narrowing), demak $e->id() ham $e->createdAt() ham β ikkala interfeys metodi β xavfsiz chaqiriladi.
β¬ οΈ Oldingi: 04 β JWT va stateless auth Β· π README Β· Keyingi: 06 β readonly, Value Object va variance β‘οΈ