افزونهٔ وردپرس، در ساده‌ترین شکل، یک فایل PHP است. ولی افزونهٔ حرفه‌ای، ساختار مشخصی دارد که نگهداری، توسعهٔ تیمی، و انتشار در مخزن رسمی را ممکن می‌کند. تجربه‌ام: تفاوت بین افزونهٔ «آماتور» و «حرفه‌ای» معمولاً نه در قابلیت‌ها، که در ساختار و سازمان‌دهی کد است. این مقاله، ساختار استاندارد افزونهٔ وردپرس را از پایه باز می‌کند: نقش هر پوشه، جای درست هر فایل، و الگوهای حرفه‌ای که در پروژه‌های جدی استفاده می‌کنم. اگر با مفاهیم پایه آشنا نیستید، افزونه وردپرس چیست و توسعهٔ افزونه از صفر را پیش از ادامه ببینید.

حداقل فایل‌ها

حداقلِ یک افزونهٔ معتبر: یک فایل PHP با هدر افزونه. همین. اگر می‌خواهید افزونه در پیشخوان نمایش داده شود، همین کافی است. ولی برای افزونهٔ جدی، ساختار پیشنهادی:

my-plugin/
├── my-plugin.php          # فایل اصلی با هدر
├── uninstall.php          # پاک‌سازی هنگام حذف
├── readme.txt             # توضیحات رسمی
├── includes/
│   ├── class-loader.php
│   ├── class-activator.php
│   ├── class-deactivator.php
│   └── functions.php
├── admin/
│   ├── class-admin.php
│   ├── css/admin.css
│   ├── js/admin.js
│   └── views/
│       └── settings-page.php
├── public/
│   ├── class-public.php
│   ├── css/public.css
│   └── js/public.js
└── languages/
    └── my-plugin.pot

این ساختار، الگوی استاندارد اکثر افزونه‌های حرفه‌ای در مخزن رسمی وردپرس است. در ادامه، نقش هر بخش را توضیح می‌دهم.

افزونهٔ حرفه‌ای، نه به‌خاطر تعداد فایل‌ها، که به‌خاطر مرزهای روشنِ هر پوشه، حرفه‌ای است.

فایل اصلی افزونه، فقط یک هدر PHP دارد که وردپرس با آن افزونه را شناسایی می‌کند. حداقل هدر:

<?php
/**
 * Plugin Name:       My Plugin
 * Plugin URI:        https://example.com/my-plugin
 * Description:       توضیح کوتاه عملکرد افزونه
 * Version:           1.0.0
 * Requires at least: 5.8
 * Requires PHP:      7.4
 * Author:            WordPressKar
 * Author URI:        https://wordpresskar.ir
 * License:           GPL-2.0-or-later
 * License URI:       https://www.gnu.org/licenses/gpl-2.0.html
 * Text Domain:       my-plugin
 * Domain Path:       /languages
 */

نکتهٔ کلیدی: پس از هدر، فایل اصلی نباید پر از کد باشد. الگوی استاندارد: فایل اصلی، فقط شامل require_once برای فایل‌های دیگر و bootstrap افزونه است. تجربه‌ام: افزونه‌هایی که فایل اصلی‌شان بالای ۳۰۰ خط است، در آپدیت دوم یا سوم به مشکل می‌خورند.

ساختار پوشه‌های حرفه‌ای

پنج پوشهٔ استاندارد: یک — includes: منطق اصلی، کلاس‌ها و توابع. دو — admin: کد و منابع مربوط به پیشخوان. سه — public: کد و منابع front-end. چهار — languages: فایل‌های ترجمه. پنج — assets (اختیاری): تصاویر و فایل‌های استاتیک. جداسازی admin از public، یکی از مهم‌ترین الگوهاست. دلیلش: در پیشخوان، نباید CSS و JS مربوط به front-end لود شود و برعکس. این جداسازی، هم سرعت را بهبود می‌دهد و هم سطح تعارض را کاهش می‌دهد. الگوی `wp_enqueue_scripts` برای front و `admin_enqueue_scripts` برای admin، در استفادهٔ درست از هوک‌ها آمده است.

پوشهٔ includes

پوشهٔ includes/، خانهٔ منطق کسب‌وکار افزونه است. الگوی استاندارد برای افزونهٔ متوسط:

includes/
├── class-loader.php          # ثبت هوک‌ها و مدیریت وابستگی
├── class-activator.php       # کد اجرا هنگام فعال‌سازی
├── class-deactivator.php     # کد اجرا هنگام غیرفعال‌سازی
├── class-i18n.php            # لود text domain
├── class-my-plugin.php       # کلاس اصلی
├── class-post-types.php      # (اختیاری) post type سفارشی
├── class-taxonomies.php      # (اختیاری) taxonomy سفارشی
├── class-rest-api.php        # (اختیاری) endpointها
└── functions.php             # توابع کمکی

الگوی کلاس‌محور، استاندارد افزونه‌های جدی است. مزیتش: جلوگیری از تعارض نام توابع، تست‌پذیری، و انسجام. تجربه‌ام: در افزونه‌های متوسط تا بزرگ، سبک procedural در دو سال به دیوار می‌خورد؛ کلاس‌محور، سال‌ها سالم می‌ماند. الگوی شی‌گرایی در PHP در شی‌گرایی در PHP و الگوی Registry در هوک‌های وردپرس آمده است.

جدا کردن admin و public

الگوی استاندارد: دو کلاس جدا، یکی برای admin و یکی برای public. در class-loader.php، هر کدام به هوک‌های مرتبطشان وصل می‌شوند:

// admin hooks
$loader->add_action( 'admin_menu', $admin, 'add_menu' );
$loader->add_action( 'admin_enqueue_scripts', $admin, 'enqueue_styles' );

// public hooks
$loader->add_action( 'wp_enqueue_scripts', $public, 'enqueue_styles' );
$loader->add_filter( 'the_content', $public, 'modify_content' );

مزیت این جداسازی: در front-end، هیچ کد مربوط به admin اجرا نمی‌شود و برعکس. این، هم مصرف منابع را کاهش می‌دهد و هم امکان خطا را کم می‌کند. تجربه‌ام: در افزونه‌ای که به‌درستی admin و public را جدا کرده بود، در زمان بحران، تشخیص تعارض ساده‌تر بود — چون می‌شد یک لایه را موقتاً خاموش کرد. روش عیب‌یابی در شناسایی افزونهٔ مشکل‌ساز.

پوشهٔ languages

پوشهٔ languages/، محل نگهداری فایل‌های ترجمه است. حداقلِ لازم: یک فایل .pot که رشته‌های قابل ترجمه را نگه می‌دارد. ساخت این فایل با ابزارهایی مثل Poedit یا WP-CLI (دستور wp i18n make-pot) انجام می‌شود. نکته: در فایل اصلی افزونه، Domain Path: /languages را در هدر ذکر کنید و در کد، با load_plugin_textdomain() یا از نسخهٔ ۴.۶ به بعد با load_plugin_textdomain() در init آن را لود کنید. راهنمای کامل i18n در آماده‌سازی برای فارسی.

uninstall.php و پاک‌سازی

هنگام حذف افزونه، وردپرس به‌طور پیش‌فرض فقط فایل‌ها را پاک می‌کند؛ جدول‌ها، ردیف‌ها در wp_options، و متادیتای اضافه باقی می‌مانند. برای پاک‌سازی کامل، فایل uninstall.php بسازید:

<?php
if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) {
    exit;
}

// پاک‌سازی تنظیمات
delete_option( 'my_plugin_settings' );

// پاک‌سازی جدول اختصاصی
global $wpdb;
$wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}my_plugin_data" );

تجربه‌ام: افزونه‌های محترم که uninstall.php دارند، در حافظهٔ پروژه می‌مانند؛ آن‌هایی که بعد از حذف، چندین مگابایت جدول باقی می‌گذارند، حس بدی به کاربر می‌دهند. راهنمای پاک‌سازی دیتابیس در چگونه دیتابیس وردپرس را پاک‌سازی کنیم.

readme.txt

برای انتشار در مخزن رسمی وردپرس، فایل readme.txt الزامی است. ساختار استاندارد شامل: نام افزونه، توضیح کوتاه، نصب، سوالات متداول، changelog، و اطلاعات مجوز. مزیت: نمایش اطلاعات در صفحهٔ افزونه در سایت wordpress.org و امکان به‌روزرسانی از پیشخوان. حتی اگر افزونهٔ شما در مخزن نیست، نگه‌داشتن این فایل، یک عادت حرفه‌ای است. الگوی دقیق در مستندات رسمی وردپرس.

Autoloader و مدیریت کلاس‌ها

وقتی تعداد کلاس‌ها زیاد می‌شود، به‌جای require_onceهای دستی، از autoloader استفاده کنید. دو گزینه: یک — Composer: استاندارد صنعتی. در فایل composer.json، نام‌فضای PSR-4 را تعریف کنید و با composer dump-autoload، لودر خودکار ساخته می‌شود. دو — Autoloader دستی: برای پروژه‌های سبک، با spl_autoload_register می‌توانید لودر خودکار بسازید. تجربه‌ام: برای پروژه‌های با بیش از ده کلاس، Composer انتخاب درست است. برای پروژه‌های کوچک، autoloader دستی کافی است و وابستگی بیرونی اضافه نمی‌کند.

نگهداری با Git

افزونهٔ جدی، نسخه‌بندی Git دارد. الگوی استاندارد: .gitignore برای حذف فایل‌های اضافه (مثل node_modules، vendor اگر نیاز نیست، و فایل‌های IDE)، شاخهٔ main برای نسخهٔ پایدار، شاخه‌های feature برای توسعه، و تگ‌گذاری برای نسخه‌ها. راهنمای Git در وردپرس در گیت در وردپرس. یک نکتهٔ عملی: در مخزن رسمی وردپرس، SVN استفاده می‌شود؛ ولی می‌توانید از Git در توسعهٔ محلی و از SVN برای انتشار استفاده کنید. پل بین این دو، ابزارهایی مثل git-svn است.

دید مهندسی

برای توسعه‌دهنده‌های سطح بالا، سه الگوی معماری که افزونه را از «کارآمد» به «مقیاس‌پذیر» می‌برد: یک — Dependency Injection ساده. به‌جای دسترسی مستقیم به سرویس‌ها، آن‌ها را به constructor پاس دهید. این کار، تست را ساده‌تر و وابستگی‌ها را شفاف‌تر می‌کند. دو — Service Container. در افزونه‌های بزرگ، یک Container سبک (پارامتریک) که تمام سرویس‌ها را نگه می‌دارد، جایگزین متغیرهای سراسری می‌شود. سه — Event Dispatcher. در کنار هوک‌های وردپرس، یک dispatcher داخلی برای رویدادهای اختصاصی افزونه، امکان جداسازی ماژول‌ها را می‌دهد. این الگوها در پروژه‌های بزرگ وردپرسی (مثل ووکامرس) به‌کار می‌روند و تفاوت بین کد «قابل قبول» و «حرفه‌ای» را می‌سازند. یکی از تجربه‌های مستقیم من: در پروژه‌ای با پنج ماژول داخلی، انتقال از procedural به این الگو، زمان اضافه‌کردن هر ماژول جدید را از دو هفته به دو روز کاهش داد.

جمع‌بندی

ساختار افزونهٔ استاندارد، شش بخش دارد: فایل اصلی با هدر، پوشهٔ includes، جداسازی admin/public، languages، uninstall و readme. اگر امروز فقط یک کار می‌کنید: به فایل اصلی افزونهٔ فعلی خودتان نگاه کنید و ببینید آیا بالای ۳۰۰ خط است. اگر بله، شروع به جداکردن منطق به فایل‌های مستقل کنید — این کار، در شش ماه آینده، خودش را چند برابر پس می‌دهد. تجربه‌تان از یک افزونهٔ با ساختار نامنظم، در دیدگاه‌ها ارزشمند است. 📦