05 β Zamonaviy PHP: namespace, autoload, OOP¶
β¬ οΈ Oldingi: 04 β Hooks: action va filter (chuqur) Β· π README Β· Keyingi: 06 β Settings API va admin sahifalar β‘οΈ
Bu bobda: WordPress barcha plugin'larni bitta global PHP makonida yuklaganini, shu sababli prefikssiz funksiya/sinf nomlari boshqa plugin bilan toqnashib "Fatal error" berishini ko'ramiz; yechim sifatida
namespaceva prefiksni, har faylni qo'ldarequireqilish o'rniga avtomatik yuklash (Composer PSR-4 hamda o'zspl_autoload_register) ni, OOP plugin tuzilmasini (asosiyPluginsinfi, singleton va DI konteyner taqqosi,PostTypes/Admin/Restkomponentlarga bo'lish),KITKAT_VERSION/KITKAT_PATH/KITKAT_URLkonstantalarini vasrc/papkali toza fayl tashkilini β barchasini izchil "Kitoblar katalogi" plugin'i ustida quramiz.
Muammo: hammasi bitta xonada yashaydi¶
Tasavvur qiling: yuzlab odam bitta katta zalda ishlaydi va hech kimning stoli, eshigi yo'q. Kimdir "Ali!" deb baqirsa, uchta Ali boshini ko'taradi. Aynan shu narsa WordPress'da sodir bo'ladi.
WordPress har bir so'rovda barcha faol plugin'larni bitta PHP jarayoniga yuklaydi. Sizning plugin'ingiz, mening plugin'im va wordpress.org'dagi yana 30 ta plugin β hammasi bitta global makonda yashaydi. Agar siz function init() deb funksiya e'lon qilsangiz va boshqa plugin ham xuddi shunday qilsa, PHP ikkalasini ham e'lon qila olmaydi:
Bu β boshlovchi plugin mualliflari uchun eng keng tarqalgan baxtsizlik. Siz hamma narsani to'g'ri yozasiz, lekin foydalanuvchining saytida sizning plugin'ingiz boshqa birovning kodi tufayli ishdan chiqadi. Va aksincha β sizning umumiy nomdagi funksiyangiz boshqa plugin'ni buzadi.
π Asosiy qoida. Plugin'ingiz global makonga chiqaradigan har bir nom β funksiya, sinf, konstanta, hatto global o'zgaruvchi β noyob bo'lishi shart. Buni ta'minlashning ikki yo'li bor: zamonaviy namespace (afzal) yoki eski uslubdagi prefiks.
Yechim 1: prefiks (eski uslub, lekin bilib qo'yish kerak)¶
Eng oddiy himoya β har bir nomning oldiga noyob, qisqa belgi qo'yish. WordPress Plugin Handbook'ning "Best Practices" bo'limi buni shunday tavsiflaydi: kamida 4-5 harfli noyob prefiks ishlatish va wp_, __, bitta tagchiziq (_) bilan boshlanmaslik β bular WordPress yadrosi uchun band.
Bizning plugin'imiz "Kitoblar katalogi" bo'lgani uchun prefiks kitkat_ (yoki sinflar uchun KitKat_) bo'ladi:
<?php
function kitkat_init(): void {
// ...
}
class KitKat_Book {
// ...
}
const KITKAT_VERSION = '1.0.0';
Endi kitkat_init() boshqa plugin'ning init() bilan toqnashmaydi.
β οΈ Prefiks β yetarli emas, balki minimum. U ishlaydi, lekin uzun, takrorlanuvchan va xunuk: KitKat_Book_Repository_Factory. Zamonaviy PHP'da buning aniq, toza muqobili bor β namespace. Prefiks endi faqat global qolishi shart bo'lgan narsalar uchun kerak: konstantalar (KITKAT_VERSION), hook nomlari (do_action('kitkat_book_saved')) va option kalitlari (kitkat_settings). Sinf va funksiyalarni esa namespace ichiga olamiz.
Yechim 2: namespace β zamonaviy javob¶
namespace β bu kodingiz uchun alohida xona yaratadi. Xona ichida nomlar qisqa qoladi (Book, init), lekin tashqaridan ularning to'liq nomi (FQCN β Fully Qualified Class Name) noyob bo'ladi.
Plugin'ning har bir PHP faylini faylning eng boshida (<?php dan keyin, har qanday kod oldidan) namespace bilan boshlaymiz:
<?php
namespace Oqil\KitobKatalog;
class Book {
public function title(): string {
return 'Misol kitob';
}
}
Endi bu sinfning haqiqiy, global nomi β Oqil\KitobKatalog\Book. Boshqa plugin'da Book sinfi bo'lsa ham (Boshqa\Plugin\Book), ular butunlay boshqa sinf β toqnashmaydi.
π Namespace nomi konvensiyasi. Odatda Vendor\Paket shaklida: Vendor β siz yoki tashkilotingiz (Oqil), Paket β plugin nomi (KitobKatalog). Kitob bo'ylab biz doimo Oqil\KitobKatalog ishlatamiz.
use β boshqa namespace'dan import¶
Boshqa namespace'dagi (jumladan global makondagi WordPress) sinflarni ishlatish uchun use bilan import qilamiz:
<?php
namespace Oqil\KitobKatalog;
use WP_Query; // global sinf
use WP_Error;
function topib_ber(): WP_Query {
return new WP_Query(['post_type' => 'kitob']);
}
π‘ Funksiyalar va global makon. Namespace ichidan add_action() kabi global funksiyani chaqirsangiz, PHP avval Oqil\KitobKatalog\add_action ni qidiradi, topmasa global'ga tushadi β shuning uchun WordPress funksiyalari avtomatik ishlaydi. Lekin global sinflarni (WP_Query, WP_Error) namespace ichidan ishlatganda boshiga \ qo'yish yoki use qilish kerak: new \WP_Query() yoki yuqoridagidek use WP_Query;.
β οΈ Bitta faylda bitta <?php, bitta namespace. namespace e'loni faylning birinchi ifodasi bo'lishi shart (faqat izoh va declare(strict_types=1); undan oldin kelishi mumkin). Oraga bo'sh qator yoki HTML tushsa, PHP xato beradi.
Avtomatik yuklash (autoloading): require jahannamidan qutulish¶
Namespace nomlarni ajratdi, lekin yangi muammo tug'diradi. Har bir sinf alohida faylda yashashi kerak (toza tashkil uchun). Eski uslubda har faylni qo'lda ulardingiz:
<?php
// β require jahannami β har yangi sinfda qo'lda qator qo'shasiz
require __DIR__ . '/src/Book.php';
require __DIR__ . '/src/PostTypes.php';
require __DIR__ . '/src/Admin.php';
require __DIR__ . '/src/Rest.php';
// ... 50 ta sinf bo'lsa, 50 ta qator. Biror faylni unutsangiz β Fatal error.
Bu xato oson, tartibga bog'liq va zerikarli. Autoloading muammoni hal qiladi: PHP'ga "agar nomi shu bo'lgan sinf kerak bo'lib, u hali yuklanmagan bo'lsa β mana shu funksiyani chaqir, u faylni topadi" deb aytamiz. Sinf birinchi marta ishlatilganda (new, statik chaqiruv, instanceof) faqat o'sha fayl yuklanadi β qolganlari diskdan ham o'qilmaydi.
Buning standarti β PSR-4. U bitta oddiy qoidani belgilaydi: namespace daraxti = papka daraxti, sinf nomi = fayl nomi.
Masalan, Oqil\KitobKatalog\ prefiksi src/ papkasiga moslangan bo'lsa:
| Sinfning to'liq nomi | Fayl yo'li |
|---|---|
Oqil\KitobKatalog\Plugin |
src/Plugin.php |
Oqil\KitobKatalog\PostTypes |
src/PostTypes.php |
Oqil\KitobKatalog\Rest\Controller |
src/Rest/Controller.php |
β οΈ Katta-kichik harf muhim. Linux serverlarda fayl tizimi katta-kichik harfni ajratadi. class PostTypes faylning nomi aniq PostTypes.php bo'lishi shart β posttypes.php emas. Windows'da ishlab, Linux'da deploy qilsangiz, bu eng ko'p uchraydigan "menda ishlardi-ku" xatosi.
Yo'l A: Composer PSR-4 (sanoat standarti)¶
Agar plugin'ingiz uchinchi tomon kutubxonalaridan foydalansa yoki jamoada ishlasangiz β Composer ishlating. Plugin ildizida composer.json yarating:
{
"name": "oqil/kitoblar-katalogi",
"description": "Kitoblar katalogi WordPress plugin",
"type": "wordpress-plugin",
"require": {
"php": ">=8.3"
},
"autoload": {
"psr-4": {
"Oqil\\KitobKatalog\\": "src/"
}
}
}
So'ng terminalda autoloader'ni generatsiya qiling:
Bu vendor/autoload.php faylini yaratadi. Plugin'ning asosiy faylida faqat bitta qator yozasiz va barcha sinflar avtomatik yuklanadi:
<?php
require __DIR__ . '/vendor/autoload.php';
// Endi Oqil\KitobKatalog\* sinflari "new" qilinganda o'zi yuklanadi.
π‘ JSON'da ikki teskari chiziq. "Oqil\\KitobKatalog\\" da \\ yozilishiga e'tibor bering β JSON'da \ maxsus belgi, shuning uchun har bir namespace ajratuvchisi ikki marta yoziladi. Bu eng ko'p uchraydigan composer.json xatosi.
π vendor/ ni Git'ga qo'shasizmi? Faqat o'z PSR-4 kodingiz bo'lsa β vendor/ ni .gitignore ga qo'shib, deploy paytida composer install --no-dev -o ishlatish toza yo'l. Lekin wordpress.org'ga yuborilgan plugin'da foydalanuvchi Composer ishlata olmaydi β shuning uchun distribution paketiga vendor/ ni qo'shib yuborasiz (28-bobda batafsil). -o (--optimize-autoloader) sinfβfayl xaritasini oldindan tuzib, productionda tezlashtiradi.
Yo'l B: o'z spl_autoload_register (Composer'siz)¶
Oddiy plugin uchun (tashqi kutubxona kerak emas), atigi 15 qatorlik o'z PSR-4 autoloader'ingiz yetarli β Composer'ga bog'liqlik qo'shmaysiz. PHP'ning spl_autoload_register() funksiyasi yuklovchi funksiyalarni ro'yxatga oladi; sinf topilmaganda PHP ularni navbat bilan chaqiradi.
src/Autoloader.php:
<?php
namespace Oqil\KitobKatalog;
final class Autoloader {
public static function register(string $baseDir): void {
spl_autoload_register(static function (string $class) use ($baseDir): void {
$prefix = __NAMESPACE__ . '\\'; // "Oqil\KitobKatalog\"
$len = strlen($prefix);
// Faqat o'z namespace'imizni boshqaramiz; boshqasini tark etamiz.
if (strncmp($class, $prefix, $len) !== 0) {
return;
}
// Prefiksdan keyingi qism: "PostTypes" yoki "Rest\Controller"
$relative = substr($class, $len);
// Namespace ajratuvchini papka ajratuvchiga aylantiramiz + .php
$file = $baseDir . '/' . str_replace('\\', '/', $relative) . '.php';
if (is_readable($file)) {
require $file;
}
});
}
}
Mantiqning har bir qadami:
__NAMESPACE__joriy namespace nomini beradi (Oqil\KitobKatalog) β qattiq yozib qo'ymaymiz, o'zgartirsangiz avtomatik moslashadi.strncmp(...) !== 0β boshqa namespace'dagi sinflar (masalanWP_Query) bizniki emas; biz indamaymiz va PHP keyingi autoloader'ga (Composer'niki bo'lsa) o'tadi.substrprefiksni kesib tashlaydi,str_replace('\\', '/', ...)namespace yo'lini fayl yo'liga aylantiradi.is_readabletekshiruvi β fayl yo'q bo'lsa "Fatal error" o'rniga jim o'tib ketamiz (boshqa autoloader urinib ko'rsin).
β οΈ Nega require_once emas, require? Autoloader bir sinf uchun faqat bir marta chaqiriladi (yuklangach PHP uni eslab qoladi), shuning uchun require yetarli va bir tomchi tezroq. require_once ham xato bo'lmaydi β ikkalasi ham to'g'ri.
OOP plugin boilerplate: Plugin sinfi dirijyor¶
Endi tartiblangan kodimiz bor. Lekin barcha hook'larni asosiy faylga to'kib tashlash β yana o'sha eski "spagetti". Zamonaviy plugin obyektga yo'naltirilgan quriladi: bitta asosiy Plugin sinfi dirijyorlik qiladi, har bir mas'uliyat alohida komponent sinfda yashaydi.
Komponent sinflar¶
Har bir sinf bitta vazifa uchun javobgar (Single Responsibility printsipi). Har birida register() metodi bo'ladi β u o'z hook'larini WordPress'ga ulaydi.
src/PostTypes.php:
<?php
namespace Oqil\KitobKatalog;
final class PostTypes {
public function register(): void {
add_action('init', [$this, 'registerKitob']);
}
public function registerKitob(): void {
// To'liq register_post_type 07-bobda. Hozir tuzilmaga e'tibor bering.
register_post_type('kitob', [
'labels' => ['name' => __('Kitoblar', 'kitoblar-katalogi')],
'public' => true,
'show_in_rest' => true,
'supports' => ['title', 'editor', 'thumbnail'],
]);
}
}
src/Admin.php:
<?php
namespace Oqil\KitobKatalog;
final class Admin {
public function register(): void {
// Admin sahifa va sozlamalar β 06-bobda to'liq.
add_action('admin_menu', [$this, 'addMenu']);
}
public function addMenu(): void {
add_menu_page(
__('Kitoblar katalogi', 'kitoblar-katalogi'),
__('Kitoblar', 'kitoblar-katalogi'),
'manage_options',
'kitoblar-katalogi',
[$this, 'renderPage'],
'dashicons-book'
);
}
public function renderPage(): void {
echo '<div class="wrap"><h1>'
. esc_html__('Kitoblar katalogi', 'kitoblar-katalogi')
. '</h1></div>';
}
}
π‘ [$this, 'metod'] β callback'lar. Hook'ga obyekt metodini ulash uchun [$this, 'metodNomi'] massivini beramiz. PHP 8.1+ da first-class callable sintaksisi ham bor: $this->metodNomi(...) β ikkalasi teng ishlaydi.
Asosiy Plugin sinfi β singleton¶
src/Plugin.php barcha komponentlarni yig'adi:
<?php
namespace Oqil\KitobKatalog;
final class Plugin {
private static ?Plugin $instance = null;
public static function instance(): Plugin {
return self::$instance ??= new self();
}
private function __construct() {}
public function init(): void {
(new PostTypes())->register();
(new Admin())->register();
// (new Rest())->register(); // 16-bobda qo'shamiz
}
public static function activate(): void {
// CPT registratsiyasidan keyin permalinklarni yangilash kerak.
(new PostTypes())->registerKitob();
flush_rewrite_rules();
}
public static function deactivate(): void {
flush_rewrite_rules();
}
}
Bu yerda singleton naqshi qo'llanilgan:
private static ?Plugin $instanceβ yagona nusxani saqlaydigan maydon.instance()β??=(null coalescing assignment) bilan: nusxa yo'q bo'lsa yaratadi, bor bo'lsa o'shani qaytaradi. Ya'ni butun so'rov davomida bittaPluginobyekti bo'ladi.private __construct()β tashqaridannew Plugin()qilishni taqiqlaydi; faqatPlugin::instance()orqali olinadi.
β οΈ activate() da flush_rewrite_rules(). CPT'ga ega plugin aktivatsiyada permalink qoidalarini yangilamasa, kitob sahifalari 404 berishi mumkin. Lekin flush_rewrite_rules() og'ir amal β uni faqat aktivatsiya/deaktivatsiyada chaqiring, hech qachon har so'rovda initda emas.
Bootstrap fayl β faqat ulash, mantiq yo'q¶
Plugin'ning asosiy fayli (kitoblar-katalogi.php) endi juda yupqa bo'ladi: header, konstantalar, autoload va init() chaqiruvi. Hech qanday biznes-mantiq bu yerda yashamaydi.
<?php
/**
* Plugin Name: Kitoblar katalogi
* Description: Kitoblar uchun CPT, taksonomiya, REST va blok.
* Version: 1.0.0
* Requires at least: 7.0
* Requires PHP: 8.3
* Author: Oqil Imomnazarov
* Text Domain: kitoblar-katalogi
* License: GPL-2.0-or-later
*/
namespace Oqil\KitobKatalog;
// To'g'ridan-to'g'ri faylga kirishni to'sib qo'yamiz.
if (!defined('ABSPATH')) {
exit;
}
// Plugin konstantalari.
define('KITKAT_VERSION', '1.0.0');
define('KITKAT_FILE', __FILE__);
define('KITKAT_PATH', plugin_dir_path(__FILE__)); // .../plugins/kitoblar-katalogi/
define('KITKAT_URL', plugin_dir_url(__FILE__)); // https://sayt.uz/wp-content/plugins/kitoblar-katalogi/
// Composer bo'lsa o'shani, bo'lmasa o'z autoloader'imizni ishlatamiz.
if (is_readable(KITKAT_PATH . 'vendor/autoload.php')) {
require KITKAT_PATH . 'vendor/autoload.php';
} else {
require KITKAT_PATH . 'src/Autoloader.php';
Autoloader::register(KITKAT_PATH . 'src');
}
// Hamma plugin yuklangach, o'z plugin'imizni ishga tushiramiz.
add_action('plugins_loaded', static function (): void {
Plugin::instance()->init();
});
// Aktivatsiya/deaktivatsiya hook'lari (03-bobdan tanish).
register_activation_hook(__FILE__, [Plugin::class, 'activate']);
register_deactivation_hook(__FILE__, [Plugin::class, 'deactivate']);
Konstantalar β to'g'ri yo'l va URL olish¶
Plugin fayllariga (rasm, CSS, JS, shablon) yo'l yoki URL kerak bo'lganda qattiq yozmang β WordPress yordamchilaridan foydalaning. Bu funksiyalarni rasmiy hujjat bilan tasdiqladik:
| Konstanta | Funksiya | Qiymat |
|---|---|---|
KITKAT_PATH |
plugin_dir_path(__FILE__) |
Fayl tizimi yo'li (oxirida /) β require, file_exists uchun |
KITKAT_URL |
plugin_dir_url(__FILE__) |
Brauzer URL'i (oxirida /) β <img src>, enqueue uchun |
KITKAT_FILE |
__FILE__ |
Asosiy fayl yo'li β plugin_basename, hook'lar uchun |
π Yo'l != URL. KITKAT_PATH serverdagi haqiqiy papka (/var/www/.../kitoblar-katalogi/) β uni brauzerga bera olmaysiz. KITKAT_URL esa brauzer ko'radigan manzil (https://sayt.uz/.../kitoblar-katalogi/) β undan require qila olmaysiz. Ikkalasi ham trailing slash (oxirgi /) bilan keladi, shuning uchun KITKAT_URL . 'assets/style.css' to'g'ri ulanadi. plugin_dir_path ichki tuzilishi trailingslashit(dirname(__FILE__)) β slash kafolatlangan.
βΉοΈ if (!defined('ABSPATH')) exit; β har bir PHP faylning boshida turadigan himoya. ABSPATH faqat WordPress ichida aniqlangan; kimdir kitoblar-katalogi.php ga to'g'ridan-to'g'ri brauzerdan kirsa, kod ishlamay to'xtaydi (ma'lumot sizib chiqmaydi).
Singleton emas β Dependency Injection: qaysi biri?¶
Singleton sodda va WordPress dunyosida juda keng tarqalgan. Lekin u kamchiliksiz emas, va katta plugin'da Dependency Injection (DI) afzal. Ikkalasini taqqoslaymiz.
Singleton β sinf o'z yagona nusxasini o'zi yaratib, global nuqtadan beradi (Plugin::instance()):
Bu qulay, lekin yashirin bog'liqlik yaratadi: Repository::instance() chaqirilgan har bir sinf aslida Repository'ga bog'langan, biroq buni konstruktoridan ko'rib bo'lmaydi. Test yozganda haqiqiy Repository o'rniga soxtasini qo'yish ham qiyin (global holatni almashtirish kerak).
Dependency Injection β sinf o'ziga kerakli obyektni o'zi yaratmaydi, balki tashqaridan, odatda konstruktor orqali oladi:
<?php
namespace Oqil\KitobKatalog;
final class BookService {
// Bog'liqlik OCHIQ ko'rinadi va tashqaridan beriladi.
public function __construct(private Repository $repository) {}
public function eng_yangi(): array {
return $this->repository->latest(10);
}
}
Endi BookService ga test yozganda soxta Repository ni shunchaki konstruktorga uzatasiz β global holatga tegmaysiz. Bog'liqliklar aniq: konstruktorga qarab sinf nimaga muhtojligini bilasiz.
Kimdir bog'liqliklarni yig'ib berishi kerak β bu vazifani DI konteyner bajaradi. Mana minimal, ishlaydigan konteyner:
<?php
namespace Oqil\KitobKatalog;
use RuntimeException;
final class Container {
/** @var array<string, callable> */
private array $factories = [];
/** @var array<string, object> */
private array $instances = [];
public function set(string $id, callable $factory): void {
$this->factories[$id] = $factory;
}
public function get(string $id): object {
// Bir marta yaratilgan obyektni qayta qaytaramiz (lazy, kesh).
if (isset($this->instances[$id])) {
return $this->instances[$id];
}
if (!isset($this->factories[$id])) {
throw new RuntimeException("Konteynerda topilmadi: {$id}");
}
return $this->instances[$id] = ($this->factories[$id])($this);
}
}
Ro'yxatga olish va ishlatish:
$c = new Container();
$c->set(Repository::class, static fn(): Repository => new Repository());
$c->set(
BookService::class,
static fn(Container $c): BookService => new BookService($c->get(Repository::class))
);
// Endi konteyner bog'liqlikni o'zi yig'adi:
$service = $c->get(BookService::class);
π Qaysi birini tanlash?
- Kichik/o'rta plugin (bir nechta komponent, kapston darajasigacha): Plugin singleton + komponentlarni new bilan yaratish β soddaroq va o'qilishi oson. Biz kitobning aksar boblarida shu yo'ldan boramiz.
- Katta plugin (ko'p komponent, jiddiy testlash, jamoaviy ishlab chiqish): DI konteyner β bog'liqliklar aniq, test oson, kengaytirish toza. 25-bobda (testlash) DI ning qiymati ayon bo'ladi.
π‘ Singleton'ni ham testga moslash mumkin. Plugin ni singleton qilsangiz ham, komponentlarini (Repository, BookService) oddiy DI bilan yozing. Shunda yagona singleton β eng tashqi "kompozitsiya ildizi", ichki mantiq esa testlanadigan bo'lib qoladi. Bu ikki dunyoning eng yaxshisi.
Yakuniy fayl tuzilishi¶
Boshqaruv yo'lidan o'tib, plugin'imiz endi shunday tashkil etilgan:
kitoblar-katalogi/
βββ kitoblar-katalogi.php β faqat BOOTSTRAP (header, konstanta, autoload, init)
βββ composer.json β (Composer yo'lini tanlasangiz) PSR-4 xaritasi
βββ uninstall.php β (03-bobdan) o'chirishda tozalash
βββ src/ β BARCHA OOP kod, PSR-4 (Oqil\KitobKatalog\*)
β βββ Autoloader.php β (Composer'siz yo'l) o'z spl_autoload_register
β βββ Plugin.php β dirijyor (singleton)
β βββ PostTypes.php β CPT komponenti
β βββ Admin.php β admin menyu/sahifa
β βββ Rest/
β βββ Controller.php β Oqil\KitobKatalog\Rest\Controller
βββ assets/ β CSS, JS, rasm (KITKAT_URL bilan ulanadi)
βββ languages/ β tarjima fayllari (24-bob)
βΉοΈ Asosiy fayl nima uchun yupqa? WordPress plugin'ni har so'rovda yuklaydi. Asosiy fayl har doim o'qiladi β shuning uchun u qanchalik kichik bo'lsa, shuncha yaxshi. Og'ir mantiqni src/ ga olib, autoload bilan faqat kerakli sinfni yuklash β bu ham toza, ham tezroq (26-bob: performance).
π O'z saytingizda sinab ko'ring. Bu bobdagi sof PHP mantiq (namespace, autoload, OOP, DI konteyner) WordPress'siz ham ishlaydi β biz buni mahalliy
phpbilan haqiqatan tekshirdik. Lekinplugin_dir_path,register_post_type,add_actionkabilar faqat ishlab turgan WordPress ichida mavjud. Shuning uchun yuqoridagi to'liq plugin'niwp-content/plugins/kitoblar-katalogi/ga joylab, wp-admin β Plugins'da aktivatsiya qiling va menyuda "Kitoblar" paydo bo'lishini ko'ring. Lokal muhitni 02-bobdawp-envbilan ko'targansiz.
Xulosa¶
- WordPress barcha plugin'ni bitta global makonda yuklaydi β prefikssiz nomlar toqnashadi ("Cannot redeclare").
namespace(Oqil\KitobKatalog) β zamonaviy yechim; nomlarni ajratadi, kod toza qoladi. Eski prefiks (kitkat_) faqat global qoladigan narsalar (konstanta, hook nomi, option kaliti) uchun kerak.- Autoloading har faylni qo'lda
requireqilishdan qutqaradi: PSR-4 qoidasi (namespace = papka, sinf = fayl). Composer (autoload.psr-4+composer dump-autoload) yoki o'zspl_autoload_register. - OOP boilerplate: yupqa bootstrap fayl +
Plugindirijyor (singleton) + bitta-mas'uliyatli komponentlar (PostTypes,Admin,Rest), har biridaregister(). - Singleton vs DI: kichik plugin β singleton; katta/testlanadigan β DI konteyner (aniq bog'liqlik, oson test).
- Konstantalar:
KITKAT_VERSION,KITKAT_PATH(plugin_dir_path),KITKAT_URL(plugin_dir_url) β yo'l va URL aralashtirma.
Keyingi bobda shu OOP skeletga birinchi haqiqiy funksiyani β Settings API bilan admin sozlamalar sahifasini β qo'shamiz.
05-bob mashqlari¶
Tayanch sifatida PHP β Mutlaqo Noldan (namespace, OOP) va PHP β Ekspert Darajasi (DI, dizayn naqshlari) kitoblaridan foydalanishingiz mumkin.
Oson¶
- Namespace e'loni. Yangi
src/Janr.phpfaylidaOqil\KitobKatalognamespace'ida bo'shJanrsinfini e'lon qiling. Faylning birinchi qatorlari qanday bo'lishi kerakligini yozing. - FQCN aniqlash.
Oqil\KitobKatalog\Rest\Controllersinfining to'liq nomi (FQCN) nima va PSR-4 bo'yicha qaysi faylda yashashi kerak? - Prefiks tuzatish. Quyidagi nom WordPress qoidasini buzadi β nega va qanday tuzatasiz:
function _kitkat_setup()vafunction wp_kitkat_load()? - Yo'l yoki URL? Plugin papkasidagi
assets/logo.pngni<img src>ga qo'yish uchunKITKAT_PATHni ishlatasizmi yokiKITKAT_URLni? Diskdan o'qish (file_get_contents) uchun-chi? useimport. Namespace'langan fayldaWP_QueryvaWP_Errorni ishlatmoqchisiz. Faylning yuqorisiga qanday qatorlar qo'shasiz?
O'rta¶
composer.jsonyozish.Oqil\KitobKatalog\nisrc/ga moslaydigan to'liqcomposer.jsonni yozing. JSON'da namespace ajratuvchisini qanday yozish kerakligiga e'tibor bering.
Yechim
{
"name": "oqil/kitoblar-katalogi",
"type": "wordpress-plugin",
"require": {
"php": ">=8.3"
},
"autoload": {
"psr-4": {
"Oqil\\KitobKatalog\\": "src/"
}
}
}
Eng muhim joy β "Oqil\\KitobKatalog\\": JSON'da \ belgisi maxsus (escape) bo'lgani uchun har bir namespace ajratuvchisini ikki marta (\\) yozamiz, va prefiks \\ bilan tugaydi. Keyin composer dump-autoload ishlatib vendor/autoload.php ni generatsiya qilasiz, asosiy faylda esa require __DIR__ . '/vendor/autoload.php'; yetarli.
- Komponentni qo'shish.
Plugin::init()ga yangiShortcodeskomponentini ulang.src/Shortcodes.phpdaregister()metodli sinf yozing vainit()da uni chaqiring.
Yechim
src/Shortcodes.php:
<?php
namespace Oqil\KitobKatalog;
final class Shortcodes {
public function register(): void {
add_shortcode('kitoblar', [$this, 'render']);
}
public function render(array $atts): string {
// To'liq shortcode 13-bobda; bu yerda tuzilma muhim.
return esc_html__('Kitoblar ro\'yxati keladi', 'kitoblar-katalogi');
}
}
src/Plugin.php ichidagi init() ga qo'shimcha:
public function init(): void {
(new PostTypes())->register();
(new Admin())->register();
(new Shortcodes())->register(); // yangi qator
}
Autoload tufayli Shortcodes sinfini hech qayerda require qilish shart emas β new Shortcodes() chaqirilganda autoloader src/Shortcodes.php ni o'zi topadi.
- Konstantalarni belgilash. Asosiy faylda
KITKAT_VERSION,KITKAT_PATH,KITKAT_URLkonstantalarini to'g'ri WordPress funksiyalari bilan e'lon qiling.
Yechim
define('KITKAT_VERSION', '1.0.0');
define('KITKAT_PATH', plugin_dir_path(__FILE__));
define('KITKAT_URL', plugin_dir_url(__FILE__));
KITKAT_VERSIONβ qo'lda yozilgan satr (enqueue'da cache-busting uchun ishlatiladi, 14-bob).plugin_dir_path(__FILE__)β fayl tizimi yo'li, oxirida/;require/file_existsuchun.plugin_dir_url(__FILE__)β brauzer URL'i, oxirida/;<img>/enqueue uchun.
__FILE__ har doim asosiy plugin fayliga ishora qilishi kerak (shuning uchun konstantalarni aynan asosiy faylda e'lon qilamiz).
- Singleton tushuntirish.
Plugin::instance()daself::$instance ??= new self();qatori nima qiladi???=operatori nimani anglatadi va negaprivate __construct()kerak?
Yechim
??= β "null coalescing assignment". self::$instance ??= new self(); aynan if (self::$instance === null) { self::$instance = new self(); } ga teng. Ya'ni: nusxa hali yo'q bo'lsa (null), yangisini yarat; bor bo'lsa o'shani qoldir. Natijada instance() qancha marta chaqirilsa ham bitta obyekt qaytadi.
private __construct() tashqaridan new Plugin() qilishni taqiqlaydi β obyektni faqat Plugin::instance() orqali olish mumkin. Bu singleton kafolatini ta'minlaydi: ikkinchi nusxa umuman yaratilmaydi.
- Autoloader filtri. O'z
spl_autoload_registerautoloaderingizdagiif (strncmp($class, $prefix, $len) !== 0) { return; }qatorini olib tashlasangiz nima bo'ladi? Nega bu tekshiruv muhim?
Yechim
Bu tekshiruv autoloaderni faqat o'z namespace'imiz (Oqil\KitobKatalog\*) bilan cheklaydi. Uni olib tashlasangiz, autoloader har qanday sinf nomi uchun (masalan WP_Query, WooCommerce, boshqa plugin'ning sinflari) src/ ichidan fayl qidiradi. Topa olmasa β odatda jim o'tib ketadi (is_readable false qaytaradi), lekin bu keraksiz disk-tekshiruvlar va boshqa autoloaderlar bilan to'g'ri hamkorlikni buzishi mumkin. To'g'ri amaliyot: autoloader faqat o'zi mas'ul bo'lgan prefiksni boshqarsin, qolganini boshqa autoloaderlarga qoldirsin (return bilan).
usevs to'liq nom. Namespace ichidan globalWP_Errorsinfini ishlatishning ikki to'g'ri usulini ko'rsating.
Yechim
Birinchi usul β faylning yuqorisida use:
<?php
namespace Oqil\KitobKatalog;
use WP_Error;
function tekshir(): WP_Error {
return new WP_Error('xato', 'Xabar');
}
Ikkinchi usul β har gal boshiga \ (global makon ildizi):
<?php
namespace Oqil\KitobKatalog;
function tekshir(): \WP_Error {
return new \WP_Error('xato', 'Xabar');
}
use qator bir marta yoziladi va kod toza qoladi β ko'p ishlatadigan sinflar uchun afzal. Bir martalik ishlatishda \WP_Error to'g'ridan-to'g'ri ham mumkin. Funksiyalar uchun bu shart emas: add_action() namespace ichida ham avtomatik global'ga tushadi.
Qiyin¶
- To'liq autoloader yozish.
Oqil\KitobKatalog\prefiksini berilgan$baseDirpapkaga moslaydigan PSR-4spl_autoload_registerautoloaderni noldan yozing. U: (a) faqat o'z namespace'ini boshqarsin, (b) ichki namespace'larni (Rest\Controller) qo'llab-quvvatlasin, (c) fayl yo'q bo'lsa "Fatal error" bermasin.
Yechim
<?php
namespace Oqil\KitobKatalog;
final class Autoloader {
public static function register(string $baseDir): void {
spl_autoload_register(static function (string $class) use ($baseDir): void {
$prefix = __NAMESPACE__ . '\\'; // "Oqil\KitobKatalog\"
$len = strlen($prefix);
// (a) Faqat o'z prefiksimizni boshqaramiz.
if (strncmp($class, $prefix, $len) !== 0) {
return;
}
// (b) Prefiksdan keyingi qism, ichki namespace bilan birga.
$relative = substr($class, $len); // "Rest\Controller"
$file = $baseDir . '/' . str_replace('\\', '/', $relative) . '.php';
// (c) Fayl bo'lmasa jim o'tamiz (boshqa autoloader urinib ko'rsin).
if (is_readable($file)) {
require $file;
}
});
}
}
Tekshiruv: Oqil\KitobKatalog\Rest\Controller uchun β $relative = "Rest\Controller" β str_replace β Rest/Controller β $baseDir . '/Rest/Controller.php'. Ichki namespace str_replace('\\', '/', ...) tufayli avtomatik papkaga aylanadi. is_readable tekshiruvi (c) talabini bajaradi. __NAMESPACE__ ishlatish prefiksni qattiq yozib qo'yishdan saqlaydi.
Ushbu mantiqni WordPress'siz, sof PHP bilan ham ishga tushirib tekshirish mumkin: bir nechta sinf faylini src/ ga joylab, faqat Autoloader::register(...) ni chaqirib, so'ng new Oqil\KitobKatalog\PostTypes() qiling β fayl avtomatik yuklanadi.
- Minimal DI konteyner.
set(string $id, callable $factory)vaget(string $id): objectmetodli konteyner yozing.getbir marta yaratilgan obyektni keshlasin (har chaqiruvda yangi yaratmasin) va ro'yxatda yo'qidso'ralsa istisno (exception) tashlasin.
Yechim
<?php
namespace Oqil\KitobKatalog;
use RuntimeException;
final class Container {
/** @var array<string, callable> */
private array $factories = [];
/** @var array<string, object> */
private array $instances = [];
public function set(string $id, callable $factory): void {
$this->factories[$id] = $factory;
}
public function get(string $id): object {
if (isset($this->instances[$id])) {
return $this->instances[$id]; // kesh
}
if (!isset($this->factories[$id])) {
throw new RuntimeException("Topilmadi: {$id}");
}
return $this->instances[$id] = ($this->factories[$id])($this);
}
}
Ishlatish:
$c = new Container();
$c->set(Repository::class, static fn(): Repository => new Repository());
$c->set(
BookService::class,
static fn(Container $c): BookService => new BookService($c->get(Repository::class))
);
$service = $c->get(BookService::class); // Repository avtomatik yig'iladi
Kalit nuqtalar: $instances keshi tufayli get() ikki marta chaqirilsa bir xil obyekt qaytadi (lazy singleton xulqi). Factory'ga $this (konteyner) uzatamiz, shunda factory boshqa bog'liqlikni $c->get(...) bilan o'zi tortib oladi. Yo'q id da RuntimeException β xatoni erta ushlaymiz.
- Singleton'dan DI'ga ko'chirish. Quyidagi singleton'ga bog'langan kod testlash uchun yomon. Uni DI bilan qayta yozing va nega test osonlashganini tushuntiring.
final class BookService {
public function eng_yangi(): array {
return Repository::instance()->latest(10); // yashirin bog'liqlik
}
}
Yechim
<?php
namespace Oqil\KitobKatalog;
final class BookService {
// Bog'liqlik konstruktorda OCHIQ ko'rinadi va tashqaridan beriladi.
public function __construct(private Repository $repository) {}
public function eng_yangi(): array {
return $this->repository->latest(10);
}
}
Nega test osonlashdi: avvalgi kodda Repository::instance() qattiq yozilgan β testda haqiqiy Repository (va uning ortidagi ma'lumotlar bazasi) ishga tushadi, uni almashtirish uchun global singleton holatini buzish kerak. DI variantida esa testda shunchaki soxta (mock/stub) Repository ni konstruktorga uzatasiz:
$soxta = new class extends Repository {
public function latest(int $n): array { return [['title' => 'Test']]; }
};
$service = new BookService($soxta);
$this->assertSame('Test', $service->eng_yangi()[0]['title']);
Bog'liqlik aniq (konstruktordan ko'rinadi), almashtiriladigan (tashqaridan beriladi) va izolyatsiyalangan (haqiqiy bazaga tegmaymiz). Bu β DI ning asosiy ustunligi (25-bobda PHPUnit bilan amalda ko'ramiz).
- Bootstrap'ni qayta tuzish. Sizga
init()ichidaregister_post_typevaadd_actionto'g'ridan-to'g'ri chaqiradigan, hammasi bitta 200 qatorli asosiy faylda yozilgan plugin berildi. Uni shu bobning tuzilmasiga (yupqa bootstrap +src/+Plugindirijyor + komponentlar) qanday qayta tashkil qilasiz? Qadamlarni va yangi fayl daraxtini yozing.
Yechim
Qadamlar:
- Namespace qo'shish. Har bir mantiqiy bo'lakni alohida sinfga ajrating, har bir faylga
namespace Oqil\KitobKatalog;qo'ying. - Komponentlarga bo'lish. CPT kodini
src/PostTypes.php(register()ichidaadd_action('init', ...)), admin kodinisrc/Admin.php, REST kodinisrc/Rest/Controller.phpga ko'chiring. Har biridaregister()metodi. Plugindirijyor.src/Plugin.phpda singletonPluginsinfi yarating;init()ichida har bir komponentninewqilibregister()chaqiring;activate()/deactivate()ni shu yerga oling.- Autoload.
composer.json(PSR-4) yokisrc/Autoloader.phpqo'shing. - Asosiy faylni yupqalashtiring. Unda faqat: plugin header,
if (!defined('ABSPATH')) exit;, konstantalar, autoloadrequire,add_action('plugins_loaded', fn() => Plugin::instance()->init()), varegister_activation_hook/register_deactivation_hookqolsin. Boshqa hech narsa.
Yangi daraxt:
kitoblar-katalogi/
βββ kitoblar-katalogi.php β yupqa bootstrap
βββ composer.json
βββ src/
β βββ Autoloader.php
β βββ Plugin.php β init(), activate(), deactivate()
β βββ PostTypes.php
β βββ Admin.php
β βββ Rest/
β βββ Controller.php
βββ assets/
Natija: asosiy fayl ~30 qator, har bir komponent kichik va alohida testlanadi, yangi funksiya = yangi sinf (asosiy faylga tegmaysiz).
- PSR-4 buzilishini topish. Plugin "Class 'Oqil\KitobKatalog\PostTypes' not found" xatosini beradi, garchi
src/posttypes.phpfayli mavjud va ichidaclass PostTypesto'g'ri yozilgan. Sabab nima va qanday tuzatasiz?
Yechim
Sabab β fayl nomi katta-kichik harfi: PSR-4 bo'yicha Oqil\KitobKatalog\PostTypes sinfi aniq PostTypes.php faylida bo'lishi kerak, lekin fayl posttypes.php deb nomlangan. Windows/macOS'da fayl tizimi katta-kichik harfni ajratmagani uchun mahalliy ishlab chiqishda xato bilinmaydi β lekin Linux serverda (production) fayl tizimi harfni ajratadi, autoloader PostTypes.php ni qidiradi va topa olmaydi.
Tuzatish: faylni aniq PostTypes.php deb qayta nomlang (sinf nomi bilan bir xil, harfma-harf). Umumiy qoida: fayl nomi = sinf nomi, namespace yo'li = papka yo'li, harfma-harf. Composer ishlatsangiz, qayta nomlagandan keyin composer dump-autoload ni qaytadan ishga tushiring.
β¬ οΈ Oldingi: 04 β Hooks: action va filter (chuqur) Β· π README Β· Keyingi: 06 β Settings API va admin sahifalar β‘οΈ