10 β PSR standartlari va PHP-FIG¶
β¬ οΈ Oldingi: 09 β WeakMap, SPL va reference Β· π README Β· Keyingi: 11 β HTTP xabarlari: PSR-7 va PSR-17 β‘οΈ
Bu bobda: PHP ekotizimining "umumiy tili" β PSR standartlarini o'rganamiz. Avval PHP-FIG nima va PSR (PHP Standards Recommendation) nima uchun kerakligini β eng muhim g'oya interoperability (turli vendor paketlari bir-biriga moslashishi: istalgan PSR-3 logger ni istalgan framework ga ulay olish) β tushunamiz. So'ng amaliy standartlarni ketma-ket ochamiz: PSR-1 / PSR-12 kod stili (+ avtomatlashtirish: PHP-CS-Fixer, phpcs, Composer scripts), PSR-4 autoload (Composer namespace ni fayl yo'liga qanday moslaydi), PSR-3 LoggerInterface (log darajalari, kontekst, nega aynan interfeys), PSR-7 / PSR-17 / PSR-15 (HTTP Message immutable, factory, middleware β qisqacha, kelajakdagi framework boblariga ko'prik) va PSR-11 ContainerInterface (DI konteyner kontrakti). Har bir PSR uchun bir xil sxema: muammo β standart β foyda. Amalda o'z kodimizni PSR-12 ga moslaymiz va konkret implementatsiyaga emas, interfeysga dasturlashni ko'rsatamiz. Bu bob boshlovchi kitobdagi class va obyekt, kirish darajalari, enum, xatolarni boshqarish va namespace va autoloading boblariga tayanadi.
PHP-FIG va PSR: nima va nega kerak?¶
Tasavvur qiling: siz ikkita ajoyib kutubxonadan foydalanmoqchisiz. Biri β log yozadigan kutubxona (masalan Monolog), ikkinchisi β to'lov tizimi bilan ishlaydigan paket. To'lov paketi xatoliklarni logga yozishi kerak. Savol: u qaysi logger ni chaqirsin? Agar to'lov paketi muallifi "men faqat Monolog ni ishlataman" desa, siz boshqa logger ishlatsangiz (yoki testda logni o'chirmoqchi bo'lsangiz) β qiynalasiz. Har bir paket o'z logger ini, o'z HTTP klientini, o'z konteynerini talab qilsa β ular bir-biriga mos kelmaydi, kodingiz chalkash bo'lib ketadi.
Aynan shu muammoni hal qilish uchun PHP-FIG (PHP Framework Interoperability Group) tashkil etilgan. Bu β yirik PHP loyihalari (Symfony, Laravel, Drupal, Composer, Slim va boshqalar) vakillarining birlashmasi. Ular birgalikda PSR (PHP Standards Recommendation β "PHP standartlari tavsiyasi") deb ataladigan hujjatlarni yozadi. Har bir PSR raqamga ega (PSR-1, PSR-3, PSR-4, ...) va aniq bir muammoni standartlashtiradi.
Eng muhim so'z β interoperability (o'zaro moslashuv). PSR larning bosh maqsadi shu: turli mualliflar yozgan, bir-birini umuman tanimaydigan paketlar bitta umumiy interfeys orqali bir-biriga ulanishi. Siz "logger" deganda nimani nazarda tutsangiz, men ham aynan shuni tushunishim kerak. Buni ta'minlovchi til β bu interfeyslar (bu β boshlovchi kitobdagi class va obyekt tushunchasining mantiqiy davomi).
PSR turlari: standart vs tavsiya¶
PSR lar ikki guruhga bo'linadi va buni bilish muhim:
- Kod stili PSR lari (PSR-1, PSR-12) β kodni qanday yozish kerakligini aytadi: probel, qavs, nomlash. Bular kod o'qilishini birxillashtiradi. Mantiqqa ta'sir qilmaydi.
- Interfeys PSR lari (PSR-3, PSR-7, PSR-11, PSR-15, PSR-17, PSR-18) β bular
vendor/ichidagi haqiqiy PHP interfeyslari (paketlar:psr/log,psr/http-message,psr/containerva h.k.). Siz ularni Composer bilan o'rnatasiz va kodingizda type hint sifatida ishlatasiz.
Ikkinchi guruh kuchliroq, chunki ular shunchaki tavsiya emas β ular kontrakt. Bir paket "men PSR-3 logger qabul qilaman" desa, demak u Psr\Log\LoggerInterface tipini kutadi β va siz unga istalgan PSR-3 mos logger ni bera olasiz.
Sxema: muammo β standart β foyda¶
Butun bob davomida har bir PSR ni shu uch savol bilan ko'ramiz:
| Savol | Mazmun |
|---|---|
| Muammo | PSR bo'lmasa nima sodir bo'ladi? Qaysi og'riq? |
| Standart | PSR aniq nimani belgilaydi? |
| Foyda | Buning natijasida nimaga erishamiz? |
PSR-1 va PSR-12: kod stili¶
Muammo¶
Bir loyihada uch dasturchi ishlaydi. Biri function getName(){ deb yozadi (qavs shu qatorda), ikkinchisi function getName()\n{ (qavs yangi qatorda), uchinchisi tab bilan, to'rtinchisi 2 probel bilan chekinish (otstup) qiladi. Pull request larda yarmi "kod" o'zgarishi emas, balki shunchaki formatlash urushi bo'lib qoladi. Diff lar shovqinga to'lib ketadi.
Standart¶
PSR-1 β eng asosiy qoidalar (Basic Coding Standard):
- PHP teglari faqat
<?phpyoki<?=. - Fayl kodlamasi β UTF-8 (BOM siz).
- Bir fayl yoki e'lon qiladi (sinf, funksiya, konstanta), yoki yon ta'sir qiladi (
echo,require) β ikkalasini birga emas. - Sinf nomlari β
StudlyCaps(PascalCase):UserRepository. - Metod nomlari β
camelCase:findById. - Konstantalar β
UPPER_CASE:MAX_RETRIES.
PSR-12 β kengaytirilgan stil (Extended Coding Style, PSR-2 ni almashtirgan). Asosiy qoidalar:
- Chekinish (otstup) β 4 probel (tab emas).
- Qator uzunligi yumshoq chegarasi β 120 belgi.
- Sinf va metod ochuvchi qavsi
{β yangi qatorda. - Boshqaruv tuzilmalari (
if,for) qavsi β shu qatorda, kalit so'zdan keyin bitta probel. <?phpdan keyin bitta bo'sh qator, keyindeclare(strict_types=1);, keyin namespace, keyinusebloklari.- Kalit so'zlar (
true,false,null,public) β kichik harf.
Mana PSR-12 ga to'liq mos fayl skeleti (bu kod ishga tushadi):
<?php
declare(strict_types=1);
namespace App\Notify;
use App\Contract\LoggerInterface;
// PSR-12 ga mos: <?php dan keyin bo'sh qator, declare alohida,
// namespace dan keyin bo'sh qator, sinf PascalCase, metod { yangi qatorda,
// kalit so'zlar kichik harf, 4 probel chekinish.
final class EmailNotifier
{
public function __construct(
private readonly LoggerInterface $logger,
) {
}
public function send(string $to, string $subject): bool
{
if ($to === '') {
$this->logger->log('warning', 'Bo\'sh manzil');
return false;
}
$this->logger->log('info', 'Yuborildi: {to}', ['to' => $to]);
return true;
}
}
E'tibor bering: konstruktor argumenti private readonly LoggerInterface $logger β biz konkret sinfga emas, interfeysga bog'lanyapmiz. Bu β butun bobning markaziy g'oyasi.
Foyda¶
Stil bir xil bo'lganda kod o'qilishi osonlashadi, diff lar toza bo'ladi, yangi dasturchi loyihaga tez moslashadi. Eng yaxshisi β buni qo'lda emas, avtomatlashtirib ta'minlash mumkin.
Avtomatlashtirish: PHP-CS-Fixer va phpcs¶
Stilni qo'lda tekshirmaysiz. Ikki vosita bor:
- PHP_CodeSniffer (
phpcs) β qoidabuzarliklarni topadi (phpcs),phpcbfesa avtomatik tuzatadi. - PHP-CS-Fixer β kodni standartga moslab qayta yozadi (eng ko'p ishlatiladi).
O'rnatish va ishlatish (illustrativ buyruqlar β bu mashinada bu paketlar o'rnatilmagan, lekin loyihangizda shunday qilasiz):
# dev-bog'liqlik sifatida o'rnatamiz
composer require --dev friendsofphp/php-cs-fixer
composer require --dev squizlabs/php_codesniffer
# tekshirish (faqat ko'rsatadi, o'zgartirmaydi)
vendor/bin/php-cs-fixer fix --dry-run --diff
# tuzatish (kodni PSR-12 ga moslaydi)
vendor/bin/php-cs-fixer fix
Eng kuchli usul β buni Composer scripts ga ulash, shunda jamoa bir xil buyruqdan foydalanadi. composer.json:
{
"scripts": {
"cs:check": "php-cs-fixer fix --dry-run --diff",
"cs:fix": "php-cs-fixer fix",
"lint": [
"@cs:check"
]
}
}
Endi har kim composer cs:fix deb yozadi va kod avtomatik formatlanadi. Buni CI (continuous integration) da composer cs:check sifatida ishlatib, mos kelmagan PR larni rad etish mumkin β stil endi muhokama mavzusi emas, mashina ishi.
Tuzoq:
php-cs-fixero'z konfiguratsiyasini.php-cs-fixer.dist.phpfaylidan o'qiydi. Unda@PSR12qoidalar to'plamini yoqib qo'ying, aks holda u o'z noyob qoidalarini qo'llashi mumkin. Jamoada bitta konfiguratsiyani git ga qo'shing, shunda hammada bir xil natija chiqadi.
PSR-4: autoload (Composer ning yuragi)¶
Muammo¶
Boshlovchi kitobdagi namespace va autoloading bobida ko'rdingiz: katta loyihada yuzlab sinf bor. Har birini require 'src/Service/Mailer/SmtpMailer.php'; deb qo'lda ulash β jahannam. Bitta faylni ko'chirsangiz, o'nlab require ni yangilashga to'g'ri keladi.
Standart¶
PSR-4 namespace ni fayl yo'liga mexanik moslash qoidasini belgilaydi. Mantiq juda sodda:
- Namespace prefiksi (masalan
App\) bitta bazaviy katalogga (masalansrc/) moslanadi. - Prefiksdan keyingi qism β namespace ajratuvchi
\β katalog ajratuvchi/ga aylanadi. - Oxiriga
.phpqo'shiladi.
Composer da buni composer.json da e'lon qilasiz:
So'ng composer dump-autoload buyrug'i bilan Composer optimallashtirilgan autoloader generatsiya qiladi. Endi new \App\Service\Mailer\SmtpMailer() yozsangiz, Composer avtomatik ravishda src/Service/Mailer/SmtpMailer.php faylini topib ulaydi.
PSR-4 mexanizmini qo'lda ko'ramiz¶
Composer "sehri" aslida spl_autoload_register ga asoslangan oddiy funksiya. Mana uning yuragi (bu kod ishlaydi β pastda natijasini ko'rasiz). Avval sinfimiz, src/Service/Mailer/SmtpMailer.php:
<?php
declare(strict_types=1);
namespace App\Service\Mailer;
final class SmtpMailer
{
public function send(string $to): string
{
return "SMTP orqali $to ga yuborildi";
}
}
Endi autoloader (autoload.php):
<?php
declare(strict_types=1);
// PSR-4 ning eng yuragi: prefiks -> bazaviy katalog moslamasi.
// (Composer aynan shu mantiqni avtomatik generatsiya qiladi.)
spl_autoload_register(function (string $class): void {
$prefix = 'App\\';
$baseDir = __DIR__ . '/src/';
// Sinf shu prefiks bilan boshlanmasa - bu autoloader emas, boshqasiga yo'l beramiz.
if (!str_starts_with($class, $prefix)) {
return;
}
// Prefiksdan keyingi qism -> nisbiy sinf nomi.
$relative = substr($class, strlen($prefix));
// Namespace ajratuvchi \ ni katalog ajratuvchi / ga aylantiramiz, oxiriga .php.
$file = $baseDir . str_replace('\\', '/', $relative) . '.php';
if (is_file($file)) {
require $file;
}
});
Va foydalanish (index.php):
<?php
declare(strict_types=1);
require __DIR__ . '/autoload.php';
// Hech qanday require yozmadik - autoloader sinfni o'zi topib yuklaydi:
$mailer = new \App\Service\Mailer\SmtpMailer();
echo $mailer->send('aziz@misol.uz') . PHP_EOL;
echo 'Sinf yuklandimi: ' . (class_exists(\App\Service\Mailer\SmtpMailer::class) ? 'ha' : 'yoq') . PHP_EOL;
Natija:
Foyda¶
Bitta require yozmasdan butun loyiha sinflari avtomatik yuklanadi β faqat katalog tuzilishi namespace bilan aynan mos bo'lsa. Bu shuningdek interoperability ning poydevori: vendor/ ichidagi har bir paket o'z PSR-4 moslamasini e'lon qiladi, Composer ularning hammasini bitta umumiy autoloader ga birlashtiradi.
Tuzoq: PSR-4 da fayl nomi sinf nomiga harf bo'yicha aniq (case-sensitive) mos bo'lishi shart:
SmtpMailersinfiSmtpMailer.phpda bo'lishi kerak,smtpmailer.phpda emas. Windows da fayl tizimi case ni e'tiborsiz qoldirgani uchun kodingiz lokal ishlaydi, lekin Linux serverda "class not found" bilan yiqiladi. Bu β eng ko'p uchraydigan "menda ishlaydi-ku" xatosi.
PSR-3: LoggerInterface¶
Muammo¶
Har bir kutubxona log yozishi kerak. Lekin biri echo, biri faylga, biri Monolog, biri sizning maxsus tizimingizga yozadi. Agar to'lov paketi new Monolog\Logger(...) ni o'z ichida qattiq kodlasa (hardcode), siz uni almashtira olmaysiz. Bu β qattiq bog'lanish (tight coupling).
Standart¶
PSR-3 bitta interfeys belgilaydi β Psr\Log\LoggerInterface. U sakkizta log darajasi uchun metod beradi (RFC 5424 dan): emergency, alert, critical, error, warning, notice, info, debug β hamda umumiy log($level, $message, $context) metodi.
Darajalarni enum bilan modellashtirib, ularning ma'nosini ko'ramiz (boshlovchi kitobdagi enum bobiga asoslanadi):
<?php
declare(strict_types=1);
// PSR-3 sakkizta log darajasi (RFC 5424 dan). Enum bilan modellaymiz.
enum LogLevel: string
{
case Emergency = 'emergency'; // tizim ishlamayapti
case Alert = 'alert'; // darhol chora kerak
case Critical = 'critical'; // jiddiy nosozlik
case Error = 'error'; // ish bajarilmadi
case Warning = 'warning'; // xato emas, lekin e'tibor
case Notice = 'notice'; // muhim, lekin normal
case Info = 'info'; // odatiy hodisalar
case Debug = 'debug'; // batafsil tahlil
public function jiddiymi(): bool
{
return match ($this) {
self::Emergency, self::Alert, self::Critical, self::Error => true,
default => false,
};
}
}
foreach (LogLevel::cases() as $lvl) {
echo $lvl->value . ' => ' . ($lvl->jiddiymi() ? 'jiddiy' : 'oddiy') . PHP_EOL;
}
Natija:
emergency => jiddiy
alert => jiddiy
critical => jiddiy
error => jiddiy
warning => oddiy
notice => oddiy
info => oddiy
debug => oddiy
Kontekst va xabar interpolatsiyasi¶
PSR-3 da xabar matnida {kalit} ko'rinishidagi joy egasi (placeholder) bo'lishi mumkin, ular $context massividan to'ldiriladi. Bu strukturali log uchun muhim: xabar shabloni o'zgarmaydi, faqat qiymatlar almashadi.
Endi soddalashtirilgan, lekin to'liq ishlaydigan PSR-3 uslubidagi logger lar to'plamini ko'ramiz. Diqqat: foydalanuvchi xizmati (OrderService) faqat interfeysga bog'lanadi:
<?php
declare(strict_types=1);
// PSR-3 LoggerInterface ning soddalashtirilgan ko'rinishi (illustrativ).
interface LoggerInterface
{
public function log(string $level, string|\Stringable $message, array $context = []): void;
public function info(string|\Stringable $message, array $context = []): void;
public function error(string|\Stringable $message, array $context = []): void;
}
// Umumiy mantiqni bir joyda jamlovchi abstrakt asos.
abstract class AbstractLogger implements LoggerInterface
{
public function info(string|\Stringable $message, array $context = []): void
{
$this->log('info', $message, $context);
}
public function error(string|\Stringable $message, array $context = []): void
{
$this->log('error', $message, $context);
}
}
// 1-implementatsiya: ekranga chiqaradi.
final class EchoLogger extends AbstractLogger
{
public function log(string $level, string|\Stringable $message, array $context = []): void
{
// {placeholder} larni kontekst bilan almashtiramiz (PSR-3 interpolatsiya).
$replace = [];
foreach ($context as $key => $val) {
if (is_scalar($val) || $val instanceof \Stringable) {
$replace['{' . $key . '}'] = (string) $val;
}
}
$msg = strtr((string) $message, $replace);
echo strtoupper($level) . ': ' . $msg . PHP_EOL;
}
}
// 2-implementatsiya: hech narsa qilmaydi (Null Object naqshi - testda foydali).
final class NullLogger extends AbstractLogger
{
public function log(string $level, string|\Stringable $message, array $context = []): void
{
// ataylab bo'sh
}
}
// Foydalanuvchi kodi FAQAT interfeysga bog'lanadi:
final class OrderService
{
public function __construct(private LoggerInterface $logger) {}
public function place(int $orderId): void
{
$this->logger->info('Buyurtma qabul qilindi: {id}', ['id' => $orderId]);
}
}
$svc = new OrderService(new EchoLogger());
$svc->place(42);
$silent = new OrderService(new NullLogger());
$silent->place(99); // hech narsa chiqarmaydi
(new EchoLogger())->error('Xato kodi {code}', ['code' => 500]);
Natija:
Nega aynan interfeys?¶
E'tibor bering: OrderService ni o'zgartirmasdan logger ni almashtirdik β EchoLogger dan NullLogger ga. Bu β interfeysga dasturlashning butun kuchi. Agar OrderService ichida new EchoLogger() deb yozganimizda, uni almashtirish uchun xizmat kodini titkilashga to'g'ri kelardi.
Amalda siz hech qanday logger yozmaysiz β composer require monolog/monolog qilasiz. Monolog PSR-3 mos (implements Psr\Log\LoggerInterface), shuning uchun u to'g'ridan-to'g'ri sizning OrderService ga "joylashadi". Mana interoperability: siz kodingizni Monolog ga emas, PSR-3 ga bog'ladingiz; ertaga boshqa logger ga o'tsangiz β bitta qator o'zgaradi.
Foyda¶
Kutubxona muallifi LoggerInterface $logger ni qabul qiladi va loyihangizdagi istalgan PSR-3 logger ishlaydi. Test paytida NullLogger berasiz β log shovqin qilmaydi. Production da Monolog berasiz β fayl, Slack, Sentry ga yoziladi. Xizmat kodi hech qachon o'zgarmaydi.
PSR-7, PSR-17, PSR-15: HTTP qatlami¶
Bu uchta PSR birgalikda HTTP so'rov/javobni standartlashtiradi. Bu boblar kelajakdagi framework internals (o'z mini-framework ingizni qurish) uchun poydevor, shuning uchun bu yerda qisqacha ko'ramiz β chuqur amaliyot L4 boblarida bo'ladi.
PSR-7: HTTP Message (immutable)¶
Muammo: har bir framework HTTP so'rovni o'zicha ifodalaydi ($_GET, $_POST, yoki o'z obyekti). Middleware yozsangiz, u faqat bitta framework da ishlaydi.
Standart: PSR-7 RequestInterface, ResponseInterface, StreamInterface va boshqalarni belgilaydi. Eng muhim xususiyat β ular immutable (o'zgarmas): obyektni o'zgartirmaysiz, balki withHeader(), withBody() kabi metodlar yangi nusxa qaytaradi.
Immutable'lik g'oyasini soddalashtirilgan Message da ko'ramiz (bu kod ishlaydi):
<?php
declare(strict_types=1);
// PSR-7 immutable'lik g'oyasi (juda soddalashtirilgan, illustrativ).
final class Message
{
/** @param array<string, string> $headers */
public function __construct(
private readonly string $body = '',
private readonly array $headers = [],
) {}
public function getBody(): string { return $this->body; }
public function getHeader(string $name): ?string
{
return $this->headers[strtolower($name)] ?? null;
}
// "with*" - o'zini O'ZGARTIRMAYDI, YANGI nusxa qaytaradi (immutable).
public function withHeader(string $name, string $value): self
{
$new = $this->headers;
$new[strtolower($name)] = $value;
return new self($this->body, $new);
}
public function withBody(string $body): self
{
return new self($body, $this->headers);
}
}
$r1 = new Message('salom');
$r2 = $r1->withHeader('Content-Type', 'application/json');
// r1 o'zgarmadi - bu immutable'likning isboti:
var_dump($r1->getHeader('Content-Type')); // NULL
var_dump($r2->getHeader('Content-Type')); // application/json
var_dump($r1 === $r2); // false: boshqa obyekt
Natija:
Nega immutable? Chunki bir so'rov obyekti ko'p middleware orqali o'tadi. Agar har biri obyektni joyida o'zgartirsa, kim nimani o'zgartirgani chalkashadi (yashirin yon ta'sirlar). Immutable bo'lsa β har bir o'zgartirish yangi, aniq nusxa beradi. readonly xususiyatlar bu yerda ideal vosita.
PSR-17: HTTP Factory¶
Muammo: PSR-7 obyektlarini qanday yaratish ham standartlashtirilmagan bo'lsa, har bir kutubxona o'z konstruktorini talab qiladi.
Standart: PSR-17 RequestFactoryInterface, ResponseFactoryInterface, StreamFactoryInterface ni beradi β PSR-7 obyektlarini yaratish uchun "fabrika" kontraktlari. Endi middleware "menga ResponseFactoryInterface bering, men javob yarataman" deydi, konkret sinfni bilmaydi.
PSR-15: Server Request Handlers (middleware)¶
Muammo: middleware (autentifikatsiya, log, CORS) β har bir framework da boshqacha yoziladi, qayta ishlatib bo'lmaydi.
Standart: PSR-15 ikki interfeys beradi β MiddlewareInterface (process($request, $handler)) va RequestHandlerInterface (handle($request)). Middleware so'rovni oladi, ish qiladi va $handler ga uzatadi yoki erta javob qaytaradi.
Zanjir (pipeline) g'oyasini soddalashtirilgan ko'rinishda ko'ramiz (bu kod ishlaydi):
<?php
declare(strict_types=1);
// PSR-15 g'oyasi: middleware zanjiri (soddalashtirilgan, illustrativ).
interface RequestHandler
{
public function handle(array $request): array; // [status, body]
}
interface Middleware
{
public function process(array $request, RequestHandler $next): array;
}
// Zanjirni o'rab, RequestHandler ko'rinishida beradigan adapter.
final class Pipeline implements RequestHandler
{
/** @param list<Middleware> $queue */
public function __construct(
private array $queue,
private RequestHandler $core,
) {}
public function handle(array $request): array
{
if ($this->queue === []) {
return $this->core->handle($request);
}
$current = array_shift($this->queue);
return $current->process($request, $this);
}
}
final class AuthMiddleware implements Middleware
{
public function process(array $request, RequestHandler $next): array
{
if (($request['token'] ?? null) !== 'sirli') {
return [401, 'Ruxsat yo\'q']; // zanjirni TO'XTATADI
}
return $next->handle($request);
}
}
final class LogMiddleware implements Middleware
{
public function process(array $request, RequestHandler $next): array
{
echo "LOG: so'rov keldi\n";
$response = $next->handle($request);
echo "LOG: javob status={$response[0]}\n";
return $response;
}
}
final class Controller implements RequestHandler
{
public function handle(array $request): array
{
return [200, 'Salom, ' . $request['user']];
}
}
$app = new Pipeline(
[new LogMiddleware(), new AuthMiddleware()],
new Controller(),
);
print_r($app->handle(['token' => 'sirli', 'user' => 'Oqil']));
Natija (to'g'ri token bilan):
Foyda: bitta middleware ni yozasiz va u istalgan PSR-15 mos framework da (Slim, Mezzio, Laminas) ishlaydi. Bu β middleware larning butun ekotizimi (rate-limit, CORS, auth) bir-biriga moslashishi demak.
Ko'prik: mana shu PSR-7/15/17 uchligi sizning o'z mini-framework ingizning HTTP qatlamini tashkil etadi β bu L4 (framework internals) boblarida to'liq quriladi. Hozircha asosiy g'oyani β immutable so'rov + middleware zanjiri β yodda saqlang.
PSR-11: ContainerInterface¶
Muammo¶
Ilova o'sib borgani sayin obyektlar bir-biriga bog'lanadi: PaymentService ga LoggerInterface kerak, unga Db kerak, unga konfiguratsiya kerak... Bularni qo'lda new bilan ulash β new PaymentService(new ArrayLogger(), new Db(new Config(...))) β chalkash va takrorlanuvchi. Bu vazifani DI konteyner (dependency injection container) bajaradi: u "qaysi xizmat qanday yasaladi" ni biladi va kerak bo'lganda yasab beradi.
Standart¶
PSR-11 konteyner uchun ikki metodli interfeys beradi β Psr\Container\ContainerInterface:
get(string $id): mixedβ xizmatni qaytaradi (kerak bo'lsa yasaydi).has(string $id): boolβ xizmat mavjudligini tekshiradi.
Shuningdek ikki istisno kontrakti: ContainerExceptionInterface (umumiy xato) va NotFoundExceptionInterface (xizmat topilmadi). Bu istisnolar boshlovchi kitobdagi xatolarni boshqarish bobidagi interfeys-istisno g'oyasiga asoslanadi.
Soddalashtirilgan, lekin ishlaydigan konteyner:
<?php
declare(strict_types=1);
// PSR-11 ContainerInterface (soddalashtirilgan, illustrativ).
interface ContainerInterface
{
public function get(string $id): mixed;
public function has(string $id): bool;
}
// PSR-11 talab qiladigan istisnolar (markerli interfeyslar).
interface ContainerExceptionInterface extends \Throwable {}
interface NotFoundExceptionInterface extends ContainerExceptionInterface {}
final class NotFoundException extends \RuntimeException implements NotFoundExceptionInterface {}
final class Container implements ContainerInterface
{
/** @var array<string, callable> fabrikalar */
private array $factories = [];
/** @var array<string, mixed> yaratilgan (kesh) */
private array $instances = [];
public function set(string $id, callable $factory): void
{
$this->factories[$id] = $factory;
}
public function get(string $id): mixed
{
if (array_key_exists($id, $this->instances)) {
return $this->instances[$id]; // bir marta yaratiladi (singleton)
}
if (!isset($this->factories[$id])) {
throw new NotFoundException("Xizmat topilmadi: $id");
}
return $this->instances[$id] = ($this->factories[$id])($this);
}
public function has(string $id): bool
{
return isset($this->factories[$id]) || array_key_exists($id, $this->instances);
}
}
final class Db
{
public function __construct(public readonly string $dsn) {}
}
final class UserRepo
{
public function __construct(private Db $db) {}
public function dsn(): string { return $this->db->dsn; }
}
$c = new Container();
$c->set(Db::class, fn() => new Db('sqlite::memory:'));
$c->set(UserRepo::class, fn(ContainerInterface $c) => new UserRepo($c->get(Db::class)));
$repo = $c->get(UserRepo::class);
echo $repo->dsn() . PHP_EOL;
var_dump($c->get(Db::class) === $c->get(Db::class)); // true: bir xil instans
var_dump($c->has('yoq'));
try {
$c->get('NoSuchService');
} catch (NotFoundExceptionInterface $e) {
echo 'Tutildi: ' . $e->getMessage() . PHP_EOL;
}
Natija:
Foyda¶
PSR-11 tufayli kutubxona "menga ContainerInterface bering" deydi va u istalgan konteyner (PHP-DI, Symfony DI, Laravel ning konteyneri) bilan ishlaydi. NotFoundExceptionInterface ni catch qilsangiz, qaysi konteyner ishlatilayotgani muhim emas.
Muhim chegara: PSR-11 faqat olish (
get/has) ni standartlashtiradi, xizmatlarni ro'yxatga olish (registratsiya) usulini emas. Har bir konteyner o'zset()/ config sintaksisiga ega. Bu ataylab shunday: PSR-11 maqsadi β kutubxonalar konteynerdan xizmat o'qiy olishi, uni sozlash esa ilova ishi.Ko'prik: to'liq DI konteyner β avtomatik bog'liqlik aniqlash (autowiring, Reflection orqali), singleton vs har safar yangi, kontekstual bog'lanish β bularning hammasi L4 (framework internals) boblarida quriladi. Bu yerda muhim narsa: kontrakt PSR-11, implementatsiya esa har xil bo'lishi mumkin.
Mashqlar¶
Oson¶
composer.jsonningautoload.psr-4blokiga"Acme\\Blog\\": "modules/blog/src/"moslamasi qo'shilgan.Acme\Blog\Entity\Postsinfi qaysi fayl yo'lida bo'lishi kerak?- PSR-3 ning sakkizta log darajasini eng jiddiydan eng kichigiga qarab yozing.
- Quyidagi kod PSR-12 ni buzadi. Uchta qoidabuzarlikni toping:
<?php function Get_user(){return TRUE;} - PSR-1 ga ko'ra fayl bir vaqtda nima qilmasligi kerak (sinf e'lon qilish va ... bir faylda)?
O'rta¶
- Bir xizmat konstruktorida
private Monolog\Logger $loggerdeb yozilgan. Buni PSR-3 ga moslab, interoperability ni ta'minlash uchun nimaga o'zgartirasiz va nega? php-cs-fixerni Composer scripts ga ulang:composer cs:fixvacomposer cs:checkbuyruqlarini bering.composer.jsonningscriptsblokini yozing.- PSR-7 obyektlari nega immutable?
$response->setStatus(404)o'rniga PSR-7 da qanday yoziladi va bu nima farq qiladi? - PSR-11
get()ikki marta chaqirilganda bir xil obyekt qaytishi shartmi? Standart bu haqda nima deydi va amalda nega ko'pincha shunday qilinadi?
Qiyin¶
- PSR-3 mos
PrefixLoggeryozing: u boshqa istalgan PSR-3 logger ni "o'rab" oladi (decorator) va har bir xabarga sobit prefiks (masalan[BILLING]) qo'shib, o'ralgan logger ga uzatadi. Konstruktor faqat interfeysga bog'lansin. - Soddalashtirilgan PSR-11 konteyner va PSR-3 logger ni birlashtiring:
PaymentServiceni konteynerda simlab, logger niArrayLoggerdanNullLoggerga xizmat kodini o'zgartirmasdan almashtiring. Bu interoperability ni qanday namoyish qiladi?
Yechim β 1
Prefiks Acme\Blog\ katalog modules/blog/src/ ga moslangan. Qolgan qism Entity\Post β \ ni / ga aylantirib, .php qo'shamiz:
Diqqat: harf registri aniq mos bo'lishi shart (Post.php, post.php emas) β aks holda Linux serverda topilmaydi.
Yechim β 2
Eng jiddiydan kichigiga: emergency β alert β critical β error β warning β notice β info β debug. Bu RFC 5424 dagi tartib. Eslab qolish: emergency β tizim butunlay yiqildi; debug β eng mayda tafsilot.
Yechim β 3
Uchta (aslida ko'proq) qoidabuzarlik:
Get_userβ metod/funksiya nomicamelCasebo'lishi kerak:getUser.TRUEβ kalit so'zlar kichik harf bo'lishi kerak:true.function Get_user(){β ochuvchi{funksiya nomidan keyin yangi qatorda bo'lishi kerak, tana esa chekinish (otstup) bilan.
To'g'ri ko'rinish:
Yechim β 4
PSR-1 ga ko'ra fayl yo e'lon qiladi (sinf, interfeys, funksiya, konstanta), yo yon ta'sir qiladi (echo, require, ini_set, global o'zgaruvchini o'zgartirish) β lekin ikkalasini bir faylda emas. Sabab: e'lon qiluvchi faylni require qilsangiz, u hech qanday kutilmagan harakat (chiqarish, ulanish ochish) qilmasligi kerak. Bu autoloader bilan xavfsiz ishlashni ta'minlaydi.
Yechim β 5
private Monolog\Logger $logger ni private \Psr\Log\LoggerInterface $logger ga o'zgartirasiz.
Nega: Monolog\Logger β konkret sinf. Unga bog'lansangiz, xizmatingiz faqat Monolog bilan ishlaydi. Testda log ni o'chirmoqchi bo'lsangiz yoki boshqa logger ga o'tsangiz β qiynalasiz. LoggerInterface ga bog'lansangiz, xizmatga istalgan PSR-3 logger (Monolog, NullLogger, sizning maxsus logger ingiz) "joylashadi". Muhimi: Monolog ning o'zi LoggerInterface ni implements qiladi, shuning uchun mavjud Monolog instansingiz baribir ishlaydi β siz faqat tip e'lonini kengaytirdingiz. Bu β interoperability.
Yechim β 6
{
"scripts": {
"cs:fix": "php-cs-fixer fix",
"cs:check": "php-cs-fixer fix --dry-run --diff",
"ci": [
"@cs:check"
]
}
}
Endi composer cs:fix kodni formatlaydi, composer cs:check esa faqat tekshiradi (o'zgartirmaydi) va mos kelmasa nol bo'lmagan chiqish kodi qaytaradi β bu CI uchun ideal. @cs:check β boshqa skriptga havola sintaksisi. Buni GitHub Actions da composer ci sifatida chaqirib, formatlanmagan PR larni avtomatik rad etish mumkin.
Yechim β 7
PSR-7 da setStatus() umuman yo'q. O'rniga withStatus() ishlatasiz, u yangi nusxa qaytaradi:
Nega immutable: bir javob obyekti ko'p middleware orqali o'tadi. Agar har biri obyektni joyida o'zgartirsa (setStatus), kim nimani qachon o'zgartirgani noaniq β yashirin yon ta'sirlar paydo bo'ladi va xatolarni topish qiyinlashadi. with* har bir o'zgarishni aniq, kuzatib bo'ladigan yangi nusxaga aylantiradi. Shu sababli qaytgan qiymatni o'zlashtirishni unutmaslik kerak β $response->withStatus(404) ni o'zlashtirmasangiz, natija yo'qoladi (bu eng ko'p uchraydigan PSR-7 tuzog'i).
Yechim β 8
PSR-11 standarti get() ikki marta chaqirilganda bir xil obyekt qaytishini majburlamaydi β bu konteyner implementatsiyasiga bog'liq. Standart faqat "agar has($id) true bo'lsa, get($id) istisno tashlamasdan qiymat qaytarishi kerak" deydi.
Amalda ko'pchilik konteyner xizmatni bir marta yasab keshlaydi (singleton xulq-atvor), chunki ilovada Db ulanish yoki Logger kabi obyektlar bitta bo'lishi mantiqan to'g'ri va resurs tejaydi. Yuqoridagi Container misolimizda $this->instances aynan shu keshni amalga oshiradi: birinchi get() yasaydi, keyingilari keshdan qaytaradi (var_dump($c->get(Db::class) === $c->get(Db::class)) β true). Lekin "har safar yangi" (factory/prototype) xulqni xohlagan implementatsiya ham PSR-11 ga mos bo'la oladi.
Yechim β 9 (PrefixLogger decorator)
<?php
declare(strict_types=1);
interface LoggerInterface
{
public function log(string $level, string $message, array $context = []): void;
}
// Oddiy logger - ekranga chiqaradi.
final class EchoLogger implements LoggerInterface
{
public function log(string $level, string $message, array $context = []): void
{
$r = [];
foreach ($context as $k => $v) {
$r['{' . $k . '}'] = (string) $v;
}
echo strtoupper($level) . ': ' . strtr($message, $r) . PHP_EOL;
}
}
// Decorator: boshqa LoggerInterface ni o'rab oladi va prefiks qo'shadi.
// Diqqat: o'ralgan logger - KONKRET sinf emas, INTERFEYS.
final class PrefixLogger implements LoggerInterface
{
public function __construct(
private readonly LoggerInterface $inner,
private readonly string $prefix,
) {}
public function log(string $level, string $message, array $context = []): void
{
// Prefiksni qo'shib, ishni o'ralgan logger ga topshiramiz.
$this->inner->log($level, $this->prefix . ' ' . $message, $context);
}
}
// Foydalanish: istalgan PSR-3 logger ni o'rashimiz mumkin.
$logger = new PrefixLogger(new EchoLogger(), '[BILLING]');
$logger->log('info', 'To\'lov bajarildi: {id}', ['id' => 77]);
$logger->log('error', 'Karta rad etildi');
Natija:
Nega bu naqsh kuchli? PrefixLogger ham LoggerInterface ni implements qiladi, ham uni qabul qiladi. Demak u o'zi ham PSR-3 logger sifatida ishlatilishi mumkin β uni boshqa decorator ga o'rashingiz, yoki istalgan PSR-3 ni kutadigan xizmatga berishingiz mumkin. Ichkarida EchoLogger o'rniga Monolog ham bo'lishi mumkin edi β PrefixLogger farqini sezmaydi. Bu β Decorator naqshi va interoperability ning birgalikdagi kuchi: yangi xulq-atvorni (prefiks) mavjud kodga tegmasdan qo'shdik.
Yechim β 10 (konteyner + logger interoperability)
<?php
declare(strict_types=1);
// --- Kontraktlar (PSR uslubidagi interfeyslar) ---
interface LoggerInterface
{
public function log(string $level, string $message, array $context = []): void;
}
interface ContainerInterface
{
public function get(string $id): mixed;
public function has(string $id): bool;
}
// --- Container (PSR-11 uslubida) ---
final class Container implements ContainerInterface
{
private array $factories = [];
private array $cache = [];
public function set(string $id, \Closure $f): void { $this->factories[$id] = $f; }
public function get(string $id): mixed
{
return $this->cache[$id] ??= ($this->factories[$id]
?? throw new \RuntimeException("yo'q: $id"))($this);
}
public function has(string $id): bool { return isset($this->factories[$id]); }
}
// --- Ikki implementatsiya (almashtiriladi) ---
final class ArrayLogger implements LoggerInterface
{
/** @var list<string> */
public array $lines = [];
public function log(string $level, string $message, array $context = []): void
{
$r = [];
foreach ($context as $k => $v) { $r['{' . $k . '}'] = (string) $v; }
$this->lines[] = strtoupper($level) . ': ' . strtr($message, $r);
}
}
final class NullLogger implements LoggerInterface
{
public function log(string $level, string $message, array $context = []): void {}
}
// --- Biznes xizmati: FAQAT LoggerInterface ga bog'liq ---
final class PaymentService
{
public function __construct(private LoggerInterface $logger) {}
public function charge(int $userId, int $amount): void
{
$this->logger->log('info', 'To\'lov: user={u}, summa={a}', ['u' => $userId, 'a' => $amount]);
}
}
// --- Konteynerda simlash (1-variant: ArrayLogger) ---
$c = new Container();
$c->set(LoggerInterface::class, fn() => new ArrayLogger());
$c->set(PaymentService::class, fn(ContainerInterface $c) => new PaymentService($c->get(LoggerInterface::class)));
$c->get(PaymentService::class)->charge(7, 1500);
/** @var ArrayLogger $log */
$log = $c->get(LoggerInterface::class);
print_r($log->lines);
// --- 2-variant: NullLogger - PaymentService kodi O'ZGARMAYDI ---
$c2 = new Container();
$c2->set(LoggerInterface::class, fn() => new NullLogger());
$c2->set(PaymentService::class, fn(ContainerInterface $c) => new PaymentService($c->get(LoggerInterface::class)));
$c2->get(PaymentService::class)->charge(9, 200);
echo "NullLogger bilan: hech narsa chiqmadi (xizmat kodi bir xil)\n";
Natija:
Array
(
[0] => INFO: To'lov: user=7, summa=1500
)
NullLogger bilan: hech narsa chiqmadi (xizmat kodi bir xil)
Bu interoperability ni qanday namoyish qiladi? PaymentService ning kodi ikkala variantda ham bir xil β u faqat LoggerInterface ga bog'langan. Qaysi konkret logger ishlatilishini konteyner (kompozitsiya joyi) hal qiladi, xizmatning o'zi emas. Bu ikki PSR ning kuchidir: PSR-11 (konteyner) "qaysi xizmat qanday yasaladi" ni markazlashtiradi, PSR-3 (interfeys) esa logger ni almashtirib bo'ladigan qiladi. Production da ArrayLogger o'rniga Monolog, testda esa NullLogger berasiz β PaymentService farqini bilmaydi. Mana shu β butun bobning markaziy g'oyasi: kontraktga dasturlang, implementatsiyaga emas.
Xulosa va keyingisi¶
Bu bobda PHP ekotizimining "umumiy tili" β PSR standartlarini o'rgandik. Asosiy xulosalar:
- PHP-FIG PSR larni yozadi, ularning bosh maqsadi β interoperability: turli vendor paketlari umumiy interfeyslar orqali bir-biriga ulanishi.
- PSR-1 / PSR-12 kod stilini birxillashtiradi; buni PHP-CS-Fixer / phpcs + Composer scripts bilan avtomatlashtiramiz.
- PSR-4 namespace ni fayl yo'liga mexanik moslab, Composer autoload poydevorini tashkil etadi.
- PSR-3 (logger), PSR-7/17/15 (HTTP message/factory/middleware), PSR-11 (konteyner) β bular
vendor/dagi haqiqiy interfeyslar (kontraktlar). Ularga dasturlasangiz, konkret paketni xizmat kodiga tegmasdan almashtira olasiz. - Takrorlanuvchi g'oya: kontraktga (interfeysga) dasturlang, konkret implementatsiyaga emas.
Bu β Wave 2 ning yakuniy bobi. Endi siz zamonaviy PHP tip tizimi, atributlar, Reflection, SPL va PSR standartlari bilan qurollangansiz. Keyingisi β framework internals (L4): PSR-11 ni haqiqiy, autowiring (Reflection orqali avtomatik bog'lash) qiladigan DI konteyner ga aylantirish; PSR-7/15 ustiga o'z mini-framework ingizni (router + middleware pipeline + konteyner) qurish. Shu bobda ko'rgan kontraktlar β o'sha framework ning skeletidir.
β¬ οΈ Oldingi: 09 β WeakMap, SPL va reference Β· π README Β· Keyingi: 11 β HTTP xabarlari: PSR-7 va PSR-17 β‘οΈ