آن روز که یک add_action اشتباه، دو ساعت وقتم را گرفت

سال ۱۳۹۶، در یکی از اولین پروژه‌های فریلنسری‌ام، برای یک فروشگاه کوچک، یک افزونه اختصاصی نوشتم که موجودی محصولات را پس از هر سفارش، در یک سرویس انبار همگام می‌کرد. کد را با add_action( 'woocommerce_thankyou', 'sync_inventory' ) نوشتم و روی لوکال تست کردم؛ کار می‌کرد. روی سرور مشتری رفتم و فعال کردم؛ ولی هیچ اتفاقی نیفتاد. دو ساعت وقت گذاشتم تا کشف کنم که مشکل، در نام callback بود — تابعی که در فایل افزونه تعریف کرده بودم، به‌درستی ثبت نشده بود چون نامش را با یک کاراکتر بزرگ‌تر از حد معمول نوشته بودم و آن نام، با نام واقعی تابع یکی نبود. یک اشتباه تایپی کوچک در add_action، دو ساعت وقت گرفت. آن روز یاد گرفتم که add_action ساده‌ترین تابع وردپرس است برای نوشتن، ولی بدون درک دقیق پارامترهایش، همان سادگی می‌تواند منبع باگ‌های ساکت باشد. این مقاله، همان تجربه و تجربه‌های بعدی من است با add_action — نه فهرست نحو، بلکه پروتکل عملیاتی. اگر با مفاهیم پایه آشنا نیستید، پیش از ادامه هوک‌های وردپرس چیست، تفاوت اکشن و فیلتر در وردپرس و مهم‌ترین اکشن هوک‌های وردپرس را بخوانید. مکمل این مقاله نحوه استفاده از add_filter، Priority در هوک‌ها و راهنمای حرفه‌ای کار با هوک‌ها است.

add_action دقیقاً چه کاری انجام می‌دهد؟

add_action تابعی است که به وردپرس می‌گوید «هر زمان هوک X اجرا شد، تابع Y من را هم صدا بزن». خودِ add_action کاری انجام نمی‌دهد؛ فقط یک callback را در فهرست داخلی وردپرس ثبت می‌کند. زمانی که هسته (یا افزونه‌ای دیگر) به آن هوک می‌رسد و do_action را صدا می‌زند، وردپرس فهرست callbackهای ثبت‌شده را به ترتیب اولویت اجرا می‌کند. سه نکته بنیادین: یک — add_action تابع شما را بلافاصله اجرا نمی‌کند؛ فقط ثبت می‌کند. دو — callback شما فقط زمانی اجرا می‌شود که هوک مربوطه در آن درخواست اجرا شود. سه — اگر هوک در آن درخواست اجرا نشود، callback شما هم اجرا نمی‌شود؛ این نکته در پروژه‌های AJAX و REST حیاتی است. توضیح کامل مکانیزم در هوک‌های وردپرس چیست، ساختار هسته وردپرس و ساخت قابلیت اختصاصی با هوک‌ها آمده است.

add_action، مثل ثبت‌نام در فهرست خبرنامه است؛ صرف ثبت‌نام، ایمیلی به شما نمی‌فرستد. ایمیل وقتی می‌آید که خبری منتشر شود.

نحو add_action و چهار پارامتر آن

تابع add_action چهار پارامتر دارد؛ دوتای اول الزامی، دوتای دوم اختیاری:

add_action( $hook_name, $callback, $priority = 10, $accepted_args = 1 );

هر پارامتر را با یک مثال واقعی باز می‌کنم:

پارامتر اول — $hook_name

نام رشته‌ایِ هوک. مثلاً init، save_post، wp_footer، wp_enqueue_scripts. این نام، «در» اتصال است. یک اشتباه تایپی در این نام، callback شما را کاملاً بی‌اثر می‌کند بدون آن‌که هیچ خطایی صادر شود. راهنمای فهرست هوک‌ها در مهم‌ترین اکشن هوک‌های وردپرس و مهم‌ترین فیلتر هوک‌های وردپرس آمده است.

پارامتر دوم — $callback

تابعی که باید اجرا شود. سه شکل دارد:

// شکل اول: نام تابع سراسری
add_action( 'init', 'myplugin_register_cpt' );

// شکل دوم: متد یک کلاس (استاتیک)
add_action( 'init', array( 'My_Plugin', 'register_cpt' ) );

// شکل سوم: متد یک شیء
$obj = new My_Plugin();
add_action( 'init', array( $obj, 'register_cpt' ) );

// شکل چهارم (توصیه نمی‌شود): تابع ناشناس
add_action( 'init', function() {
    // کد
} );

توصیه من در پروژه‌های واقعی: از شکل اول یا دوم استفاده کنید، چون امکان remove_action را برای افزونه‌های دیگر نگه می‌دارد. تابع ناشناس (closure) قابل حذف نیست و در پروژه‌های تیمی، این محدودیت به بدهی فنی تبدیل می‌شود. الگوهای مشابه در حذف اکشن هوک، استفاده درست از هوک‌ها، اصول کدنویسی تمیز و استانداردهای کدنویسی وردپرس آمده است.

پارامتر سوم — $priority

عددی که ترتیب اجرا را تعیین می‌کند. عدد کمتر، اجرای زودتر. پیش‌فرض ۱۰. سه الگوی عملی:

// اجرا قبل از بقیه (مثلاً قبل از افزونه‌های دیگر که priority 10 دارند)
add_action( 'init', 'myplugin_early_setup', 5 );

// اجرا بعد از بقیه (مثلاً بعد از افزونه سئو)
add_action( 'wp_head', 'myplugin_meta_tags', 20 );

// اجرا در آخرین لحظه (مثلاً برای override)
add_action( 'wp_footer', 'myplugin_tracking_code', 999 );

راهنمای کامل priority و ترتیب اجرا در Priority در هوک‌ها، کنترل ترتیب اجرای هوک‌ها و راهنمای حرفه‌ای کار با هوک‌ها. یک قاعده در پروژه‌های خودم: هر priority غیرپیش‌فرض را با یک کامنت مستند کنید؛ سه ماه بعد، خودتان هم نمی‌دانید چرا ۲۰ گذاشته‌اید.

پارامتر چهارم — $accepted_args

تعداد پارامترهایی که callback شما دریافت می‌کند. پیش‌فرض ۱. اگر هوک دو یا سه پارامتر پاس می‌دهد و شما این پارامتر را ندهید، callback فقط پارامتر اول را می‌بیند:

// نامناسب - فقط $post_id را می‌گیرد
add_action( 'save_post', 'myplugin_save_meta' );

// مناسب - سه پارامتر را می‌گیرد
add_action( 'save_post', 'myplugin_save_meta', 10, 3 );

function myplugin_save_meta( $post_id, $post, $update ) {
    // هر سه پارامتر در دسترس است
}

راهنمای کامل پارامترها در پارامترهای هوک وردپرس، هوک‌های وردپرس چیست و استفاده درست از هوک‌ها.

مثال‌های کاربردی از پروژه‌های واقعی

مثال اول — ثبت نوع‌نوشته سفارشی روی init:

add_action( 'init', 'myplugin_register_portfolio' );

function myplugin_register_portfolio() {
    register_post_type( 'portfolio', array(
        'public'       => true,
        'has_archive'  => true,
        'show_in_rest' => true,
        'supports'     => array( 'title', 'editor', 'thumbnail' ),
    ) );
}

راهنمای کامل در ساخت نوع نوشته سفارشی، کار با CPT در وردپرس، ساخت تاکسونومی سفارشی و کار با تاکسونومی سفارشی.

مثال دوم — enqueue asset روی wp_enqueue_scripts:

add_action( 'wp_enqueue_scripts', 'myplugin_enqueue_assets' );

function myplugin_enqueue_assets() {
    if ( ! is_singular( 'portfolio' ) ) {
        return;
    }
    wp_enqueue_style(
        'myplugin-portfolio',
        plugins_url( 'assets/css/portfolio.css', __FILE__ ),
        array(),
        '1.0.0'
    );
    wp_enqueue_script(
        'myplugin-portfolio',
        plugins_url( 'assets/js/portfolio.js', __FILE__ ),
        array( 'jquery' ),
        '1.0.0',
        true
    );
}

راهنمای enqueue در افزودن کد سفارشی به وردپرس، افزودن کد بدون ویرایش هسته، ساختار فایل‌های افزونه استاندارد و بهینه‌سازی کد وردپرس.

مثال سوم — ذخیره متادیتا روی save_post با سه پارامتر:

add_action( 'save_post', 'myplugin_save_source_meta', 10, 3 );

function myplugin_save_source_meta( $post_id, $post, $update ) {
    if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
        return;
    }
    if ( wp_is_post_revision( $post_id ) ) {
        return;
    }
    if ( ! current_user_can( 'edit_post', $post_id ) ) {
        return;
    }
    if ( ! isset( $_POST['myplugin_nonce'] ) 
         || ! wp_verify_nonce( $_POST['myplugin_nonce'], 'myplugin_save' ) ) {
        return;
    }
    if ( isset( $_POST['source'] ) ) {
        update_post_meta(
            $post_id,
            '_source',
            sanitize_text_field( wp_unslash( $_POST['source'] ) )
        );
    }
}

راهنمای کامل در کار با متاباکس‌ها، توابع متادیتا، نانس وردپرس، PHP امن در وردپرس و پاک‌سازی داده‌ها.

مثال چهارم — AJAX با نانس و capability:

add_action( 'wp_ajax_myplugin_action', 'myplugin_ajax_handler' );

function myplugin_ajax_handler() {
    check_ajax_referer( 'myplugin_nonce', 'nonce' );
    if ( ! current_user_can( 'edit_posts' ) ) {
        wp_send_json_error( array( 'message' => 'دسترسی غیرمجاز' ), 403 );
    }
    $data = sanitize_text_field( wp_unslash( $_POST['data'] ?? '' ) );
    wp_send_json_success( array( 'result' => $data ) );
}

راهنمای کامل در نانس وردپرس، پیاده‌سازی نانس در فرم‌ها، توابع نقش و دسترسی، ساخت API اختصاصی و REST API وردپرس.

add_action بدون نانس و capability، مثل در باز است؛ ممکن است امروز کسی وارد نشود، ولی فردا قطعاً وارد می‌شود.

priority در عمل: سه سناریوی واقعی

priority را با دلیل عوض کنید، نه با سلیقه. سه سناریوی واقعی:

سناریو اول — افزودن متا پیش از افزونه سئو: اگر می‌خواهید افزونه سئو، متای شما را ببیند، priority شما باید کمتر از ۱۰ باشد. مثلاً ۵:

add_action( 'wp_head', 'myplugin_add_meta', 5 );

سناریو دوم — افزودن کد رهگیری پس از همه: اگر می‌خواهید کد رهگیری شما آخرین اسکریپت در فوتر باشد، priority بالاتر بگذارید:

add_action( 'wp_footer', 'myplugin_tracking', 999 );

سناریو سوم — تغییر رفتار افزونه دیگر: اگر می‌خواهید خروجی افزونه‌ای را بازنویسی کنید، callback شما باید بعد از آن اجرا شود، یعنی priority بالاتر:

add_action( 'wp_footer', 'myplugin_override_footer', 20 );

راهنمای کامل در Priority در هوک‌ها، کنترل ترتیب اجرای هوک‌ها، حذف اکشن هوک، حذف فیلتر هوک و راهنمای حرفه‌ای کار با هوک‌ها آمده است. یک تجربه میدانی: در پروژه‌ای، افزونه‌ای با priority 5 کد رهگیری درج می‌کرد و باعث می‌شد GTAG قبل از jQuery بار شود و خطا بدهد. تغییر priority به 99، مشکل را حل کرد.

الگوی کلاس‌محور: Registry Pattern

در پروژه‌های بزرگ، تمام فراخوانی‌های add_action را در یک نقطه ثبت کنید:

class My_Plugin_Hooks {
    public static function init() {
        add_action( 'init', array( __CLASS__, 'register_post_type' ) );
        add_action( 'init', array( __CLASS__, 'register_taxonomy' ) );
        add_action( 'wp_enqueue_scripts', array( __CLASS__, 'enqueue_assets' ) );
        add_action( 'save_post', array( __CLASS__, 'save_meta' ), 10, 3 );
        add_action( 'wp_footer', array( __CLASS__, 'render_tracking', ), 999 );
        add_action( 'wp_ajax_myplugin_action', array( __CLASS__, 'ajax_handler' ) );
    }

    public static function register_post_type() {
        // ثبت post type
    }

    public static function register_taxonomy() {
        // ثبت taxonomy
    }

    public static function enqueue_assets() {
        // enqueue
    }

    public static function save_meta( $post_id, $post, $update ) {
        // ذخیره متا
    }

    public static function render_tracking() {
        // کد رهگیری
    }

    public static function ajax_handler() {
        // پردازش AJAX
    }
}
My_Plugin_Hooks::init();

مزیت: خوانایی، امکان تست، و مدیریت ترتیب اجرا. الگوهای مشابه در کدنویسی اختصاصی افزونه، توسعه افزونه از صفر، ساختار فایل‌های افزونه استاندارد، استانداردهای کدنویسی وردپرس، ساختاربندی پروژه وردپرس، اصول کدنویسی تمیز و ساختار استاندارد کدنویسی آمده است.

یک تجربه میدانی: در پروژه‌ای با هفت افزونه اختصاصی که هرکدام در فایل اصلی، چند فراخوانی add_action پراکنده داشتند، انتقال به این الگو در یک کلاس متمرکز، زمان دیباگ تعارض‌ها را نصف کرد.

حذف add_action با remove_action

برای حذف یک action که افزونه یا قالب دیگری ثبت کرده، از remove_action استفاده کنید:

remove_action( 'wp_footer', 'myplugin_tracking', 999 );

// برای متدهای کلاس استاتیک
remove_action( 'init', array( 'My_Plugin', 'method_name' ), 10 );

// برای متدهای شیء
$obj = My_Plugin::get_instance();
remove_action( 'init', array( $obj, 'method_name' ), 10 );

سه نکته حیاتی: یک — priority باید مطابق باشد. اگر اکشن با priority 999 ثبت شده و شما 10 بدهید، حذف نمی‌شود. دو — حذف باید بعد از ثبت انجام شود. اگر افزونه‌ای دیرتر از شما بار شود، کد حذف شما اثری ندارد. سه — callbackهای closure قابل حذف نیستند. راهنمای کامل در حذف اکشن هوک، حذف فیلتر هوک، قالب چایلد چیست، توسعه با چایلد تم و استفاده درست از هوک‌ها آمده است.

یک تجربه میدانی: در پروژه‌ای، افزونه‌ای در فوتر یک ویجت تبلیغاتی درج می‌کرد که با طراحی ناهماهنگ بود. سازنده افزونه راهی برای غیرفعال‌کردن آن نگذاشته بود. راه‌حل: در چایلد تم، با remove_action و priority درست، ویجت حذف شد.

الگوی پیشرفته: روش Object-Oriented

در افزونه‌های حرفه‌ای، از یک کلاس با متدهای نمونه استفاده کنید:

class My_Plugin_Admin {
    public function __construct() {
        add_action( 'admin_menu', array( $this, 'add_menu' ) );
        add_action( 'admin_init', array( $this, 'register_settings' ) );
        add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) );
    }

    public function add_menu() {
        // ثبت منو
    }

    public function register_settings() {
        // ثبت تنظیمات
    }

    public function enqueue_assets() {
        // لود asset
    }
}

new My_Plugin_Admin();

مزیت این الگو: تفکیک مسئولیت‌ها، امکان تزریق وابستگی، و امکان تست. راهنمای کامل در ساخت صفحه تنظیمات اختصاصی، ساخت منوی مدیریتی وردپرس، Customizer وردپرس، توابع تنظیمات قالب و کار با Options API آمده است.

add_action در قالب و افزونه: تفاوت‌های ظریف

در قالب، add_action معمولاً در functions.php چایلد تم یا والد استفاده می‌شود. در افزونه، در فایل اصلی یا کلاس bootstrap. سه قاعده عملی:

add_action در ووکامرس: نکات ویژه

در فروشگاه‌های ووکامرسی، add_action ابزار اصلی منطق کسب‌وکار است:

// پس از پرداخت موفق
add_action( 'woocommerce_thankyou', 'myshop_after_purchase', 10, 1 );

function myshop_after_purchase( $order_id ) {
    $order = wc_get_order( $order_id );
    if ( ! $order ) {
        return;
    }
    // ارسال به CRM
}

// هنگام تغییر وضعیت سفارش
add_action( 'woocommerce_order_status_completed', 'myshop_on_order_completed', 10, 1 );

function myshop_on_order_completed( $order_id ) {
    // ارسال پیام تشکر
}

راهنمای کامل در هوک‌های ووکامرس، مدیریت سفارش‌های ووکامرس، تنظیم روش‌های پرداخت، تنظیم روش‌های ارسال، مدیریت مالیات در ووکامرس، سفارشی‌سازی سبد و تسویه‌حساب، افزونه‌های کاربردی ووکامرس، افزایش سرعت فروشگاه، امنیت فروشگاه ووکامرس، سئوی فروشگاه ووکامرس و تخفیف و کد تخفیف در ووکامرس آمده است.

دیباگ add_action: ابزارها

وقتی add_action کار نمی‌کند، چهار ابزار در پروژه‌های خودم استفاده می‌کنم:

  • Query Monitor: لیست تمام اکشن‌ها با ترتیب و priority و callback. راهنمای کامل در توابع دیباگ وردپرس.
  • error_log در callback: بررسی اینکه callback اجرا می‌شود یا نه. راهنما در دیباگ کد سفارشی وردپرس.
  • global $wp_filter: برای دیدن تمام callbackهای ثبت‌شده روی یک هوک. راهنما در دیباگ اکشن و فیلتر.
  • تست با priority بالا: اگر callback شما در انتهای زنجیره اجرا می‌شود، از priority بالا استفاده کنید.

یک تجربه میدانی: در پروژه‌ای، callback پس از نصب افزونه سئو، متای ما را بازنویسی می‌کرد. با Query Monitor کشف کردیم که افزونه سئو priority 5 دارد و متای ما priority 10. تغییر priority به 15، مشکل را حل کرد. راهنما در تست و دیباگ پروژه‌های وردپرس، بهترین روش تست وردپرس و خطای Deprecated در PHP.

اشتباهات رایج در استفاده از add_action

فهرست کامل اشتباهات در اشتباهات رایج هوک‌ها، اشتباهات رایج توسعه وردپرس و اشتباهات رایج کدنویسی وردپرس آمده است.

امنیت در add_action: پنج قاعده طلایی

هر callback که با add_action ثبت می‌شود، یک نقطه ورود بالقوه است. پنج قاعده امنیتی الزامی:

  1. بررسی دسترسی در callbackهای حساس: current_user_can پیش از هر عملیات. راهنما در نقش و دسترسی و امن‌سازی ورود ادمین.
  2. نانس در فرم‌ها و AJAX: wp_verify_nonce و check_ajax_referer. راهنما در نانس وردپرس و پیاده‌سازی نانس در فرم‌ها.
  3. پاک‌سازی ورودی: sanitize_text_field، absint، esc_url_raw. راهنما در پاک‌سازی داده‌ها.
  4. Escape خروجی: esc_html، esc_attr، esc_url. راهنما در PHP امن در وردپرس.
  5. اعتبارسنجی داده پیش از ذخیره: sanitize_email، wp_kses_post بسته به نوع. راهنما در اعتبارسنجی داده‌ها و توابع امنیت و پاک‌سازی.

مباحث امنیتی تکمیلی در هوک‌های وردپرس و امنیت کد، امنیت پروژه وردپرس، امنیت وردپرس برای مبتدیان، افزونه‌های امنیتی وردپرس، امنیت فروشگاه ووکامرس و محافظت وردپرس در برابر هکرها آمده است.

add_action بدون نانس و capability، یک در باز است با تابلوی «فقط خودی وارد شود»؛ هیچ مهاجمی به تابلو نگاه نمی‌کند.

جمع‌بندی

نحوه استفاده از add_action در وردپرس، در چهار اصل خلاصه می‌شود: انتخاب هوک درست برای نقطه اتصال، callback نام‌دار با پیشوند یکتا برای جلوگیری از تعارض، priority با دلیل و مستندسازی برای ترتیب اجرا، و امنیت در هر callback (نانس، capability، پاک‌سازی و escape). سه اصل را در پایان تاکید می‌کنم: اول، callback را همیشه نام‌دار بنویسید تا امکان حذف توسط افزونه‌های دیگر باقی بماند. دوم، priority را با دلیل عوض کنید و دلیلش را در کامنت مستند کنید. سوم، در همه callbackهایی که به ورودی کاربر دسترسی دارند، نانس و capability را جدی بگیرید.

اگر امروز یک کار در این مسیر انجام می‌دهید: در آخرین افزونه یا قالب خود، فهرست تمام فراخوانی‌های add_action را بنویسید و سه چیز را بازبینی کنید — نام callback، priority، و تعداد پارامترهای accepted_args. همان یک بازبینی کوچک، در آپدیت بعدی نجات‌دهنده است. اگر تجربه‌ای از یک add_action دارید که پروژه‌ای را نجات داد یا باگی را حل کرد، در دیدگاه‌ها بنویسید — همان گزارش‌های واقعی، این راهنما را برای توسعه‌دهنده بعدی دقیق‌تر می‌کند. 🔗