تابع has_action یکی از توابع کلیدی وردپرس برای بررسی ثبت بودن یک اکشن و دریافت priority آن است. این تابع در جلوگیری از حذف ناموفق، کنترل هوک‌ها و شرط‌گذاری پیشرفته در افزونه‌نویسی نقش جدی دارد. تشخیص درست اینکه یک اکشن ثبت شده یا نه، می‌تواند از باگ‌های ظریف و رفتار غیرمنتظره جلوگیری کند. اشتباهات رایجی مانند نبود بررسی قبل از remove_action و نبود تست می‌تواند به حذف ناخواسته یا ناموفق منجر شود. تسلط بر این تابع برای کدنویسی تمیز و پایدار ضروری است و در کنترل هوک‌ها کاربرد جدی دارد.

چرا بررسی ثبت بودن اکشن حیاتی است؟

در توسعه افزونه وردپرس، گاهی لازم است که یک اکشن ثبت‌شده توسط قالب یا افزونه دیگری را حذف یا جایگزین کنید. اگر بدون بررسی، remove_action را فراخوانی کنید و آن اکشن ثبت نشده باشد، هیچ خطایی نمی‌گیرید اما عملیات شما بی‌اثر می‌ماند. همچنین گاهی می‌خواهید کد شما تنها در صورتی اجرا شود که یک اکشن مشخص توسط افزونه دیگری ثبت شده باشد. تابع has_action() این اطلاعات را فراهم می‌کند و پایه تصمیم‌گیری دقیق در این سناریوها است.

تابع has_action چیست؟

تابع has_action() یک تابع هسته وردپرس است که در فایل wp-includes/plugin.php تعریف شده است. این تابع بررسی می‌کند که آیا یک callback مشخص به یک اکشن خاص متصل است یا نه. خروجی این تابع به شکل زیر است: - اگر callback مشخص باشد و ثبت شده باشد: مقدار priority آن (عدد صحیح) برمی‌گردد - اگر callback مشخص باشد و ثبت نشده باشد: false برمی‌گردد - اگر callback مشخص نباشد: true اگر هر callbackی به آن اکشن متصل باشد این رفتار دوگانه، استفاده‌های مختلفی را ممکن می‌کند.

امضای تابع و پارامترها

امضای این تابع به‌شکل زیر است:
function has_action( $hook_name, $callback = false ) {
    return has_filter( $hook_name, $callback );
}
پارامتر اول (hook_name) نام اکشن است. پارامتر دوم (callback) اختیاری است و می‌تواند یک تابع، متد کلاس یا نام آن باشد. نکته مهم: تابع has_action در واقع has_filter را فراخوانی می‌کند چرا که در وردپرس، اکشن و فیلتر از یک ساختار مشترک استفاده می‌کنند.

سازوکار داخلی تابع

تابع has_action از متغیر جهانی $wp_filter استفاده می‌کند. این متغیر یک آرایه از تمام هوک‌های ثبت‌شده است که در آن، کلیدها نام هوک‌ها و مقادیر، شیء WP_Hook هستند. متد has_filter روی این شیء بررسی می‌کند که آیا callback مشخص در آن ثبت شده است یا نه. اگر callback مشخص نباشد، بررسی می‌کند که آیا هر callbackی ثبت شده است. نکته مهم این است که این تابع در هر زمان پس از اجرای هوک init قابل استفاده است. پیش از آن، هوک‌های ثبت‌شده توسط افزونه‌ها ممکن است هنوز کامل نشده باشند.

مقدار بازگشتی و تفسیر آن

سه حالت مختلف برای مقدار بازگشتی وجود دارد که در ادامه بررسی می‌شوند:
// بررسی ثبت یک callback مشخص
$priority = has_action( 'save_post', 'myplugin_save_handler' );
if ( false !== $priority ) {
    // این callback ثبت شده و priority آن در $priority است
}

// بررسی ثبت هر callbackی
$exists = has_action( 'save_post' );
if ( $exists ) {
    // حداقل یک callback به save_post متصل است
}
نکته مهم: هرگز نباید از if ( has_action( ... ) ) بدون بررسی دقیق استفاده کنید چرا که priority می‌تواند صفر باشد و در این حالت در برخی بررسی‌ها نادرست تفسیر می‌شود. همیشه با false !== $priority بررسی کنید.

کاربرد قبل از remove_action

یکی از رایج‌ترین کاربردهای این تابع، بررسی پیش از حذف است:
add_action( 'init', 'myplugin_replace_action' );
function myplugin_replace_action() {
    if ( has_action( 'wp_head', 'mytheme_custom_css' ) ) {
        remove_action( 'wp_head', 'mytheme_custom_css', 10 );

        add_action( 'wp_head', 'myplugin_replacement_css', 10 );
    }
}
نکته مهم: در remove_action، اولویت باید دقیقاً با اولویت ثبت‌شده مطابقت داشته باشد. اگر اولویت اشتباه باشد، حذف ناموفق می‌ماند. نمونه دقیق‌تر با استفاده از priority برگردانده‌شده:
function myplugin_safe_remove_action() {
    $priority = has_action( 'wp_head', 'mytheme_custom_css' );

    if ( false !== $priority ) {
        remove_action( 'wp_head', 'mytheme_custom_css', $priority );
    }
}
این الگو امکان حذف دقیق بدون دانستن اولویت اصلی را فراهم می‌کند.

کاربردهای شرطی در افزونه

افزونه‌ها گاهی باید رفتار خود را بر پایه حضور افزونه‌های دیگر تنظیم کنند:
function myplugin_conditional_setup() {
    if ( has_action( 'woocommerce_before_shop_loop', 'woocommerce_catalog_ordering' ) ) {
        // WooCommerce فعال است، از قابلیت‌های اضافی استفاده کن
        add_action( 'woocommerce_before_shop_loop', 'myplugin_custom_sorting', 5 );
    }
}
add_action( 'init', 'myplugin_conditional_setup' );
نکته مهم: استفاده از has_action به‌جای is_plugin_active در برخی موارد دقیق‌تر است چرا که رفتار اکشن را بررسی می‌کند نه فقط حضور افزونه را.

تفاوت با has_filter

از نظر فنی، has_action و has_filter یکسان عمل می‌کنند چرا که در وردپرس، اکشن و فیلتر از یک ساختار مشترک استفاده می‌کنند. تفاوت فقط در نامگذاری و کاربرد معنایی است: - has_action(): برای بررسی اکشن‌ها استفاده می‌شود - has_filter(): برای بررسی فیلترها استفاده می‌شود راهنمای تابع دیگر در صفحه has_filter آمده است.

نکات امنیتی و اشتباهات رایج

اشتباه اول، نبود بررسی پیش از remove_action است. اگر بدون بررسی حذف کنید و اکشن وجود نداشته باشد، حذف بی‌اثر می‌ماند. اشتباه دوم، اشتباه در تفسیر مقدار بازگشتی است. همیشه با false !== $result بررسی کنید، نه با if ( $result ) چرا که priority صفر ممکن است نادرست تفسیر شود. اشتباه سوم، استفاده پیش از هوک init است. اگر تابع شما پیش از init اجرا شود، ممکن است اکشن‌های ثبت‌شده توسط افزونه‌های دیگر را نبینید. اشتباه چهارم، نبود توجه به اولویت در remove_action است. اولویت باید دقیقاً مطابقت داشته باشد. اشتباه پنجم، نبود تست است. باید در سناریوهای مختلف (اکشن ثبت شده، ثبت نشده، با اولویت‌های مختلف) کد خود را بررسی کنید.

تحلیل فنی پیشرفته

در نگاه مهندسی، تابع has_action() یک نقطه دسترسی در لایه Hook Registry است که بر چند جنبه از سیستم اثر می‌گذارد. لایه اول لایه Reflection است. این تابع امکان بررسی وضعیت فعلی هوک‌ها را فراهم می‌کند، مشابه Reflection در زبان‌های برنامه‌نویسی. لایه دوم لایه Extensibility Control است. در افزونه‌های همکاری‌کننده، این تابع امکان حذف و جایگزینی دقیق اکشن‌های افزونه‌های دیگر را فراهم می‌کند. لایه سوم لایه Performance است. فراخوانی مکرر این تابع هزینه ناچیزی دارد چرا که تنها یک جستجوی ساده در آرایه انجام می‌دهد. لایه چهارم لایه Integration است. ترکیب has_action با remove_action و add_action یک الگوی کامل برای کنترل هوک‌ها می‌سازد. لایه پنجم لایه Order of Execution است. اگر افزونه‌ای اکشن‌ها را در هوک init ثبت کند، افزونه شما باید در اولویت بعدی این بررسی را انجام دهد. لایه ششم لایه Type Safety است. callback می‌تواند تابع، متد کلاس یا closure باشد. برای بررسی دقیق متد کلاس، باید از آرایه array( $object, 'method_name' ) استفاده کنید. لایه هفتم لایه Multisite است. در شبکه‌های Multisite، هوک‌ها در هر سایت مستقل هستند. لایه هشتم لایه Testing است. تست‌های واحد باید همه سناریوها را پوشش دهند: callback ثبت شده، ثبت نشده، با اولویت‌های مختلف. مفاهیم پایه‌ای Reflection در Reflection در ویکی‌پدیا توضیح داده شده است. برای مطالعه بیشتر روی توابع مرتبط، می‌توانید به راهنمای has_filter، راهنمای remove_action، راهنمای add_action، راهنمای do_action، راهنمای did_action، راهنمای doing_action و راهنمای current_filter مراجعه کنید.

پرسش‌های پرتکرار

تفاوت has_action و has_filter چیست؟ از نظر فنی یکسان عمل می‌کنند؛ تفاوت فقط در نامگذاری و کاربرد معنایی است. چه زمانی مقدار false برمی‌گرداند؟ وقتی callback مشخص باشد و ثبت نشده باشد. آیا priority صفر باعث می‌شود تابع مقدار false برگرداند؟ خیر، صفر یک مقدار معتبر است. با false !== $result بررسی کنید. آیا این تابع پیش از init کار می‌کند؟ ممکن است ناقص باشد چرا که افزونه‌ها هنوز اکشن‌های خود را ثبت نکرده‌اند. آیا می‌توان از این تابع برای بررسی closure استفاده کرد؟ بله، اما باید دقیقاً همان closure را پاس دهید، نه closure متفاوت.

نتیجه و مسیر ادامه

تابع has_action() یک ابزار قدرتمند برای بررسی ثبت بودن اکشن‌ها و دریافت priority آنها است. استفاده درست از آن یعنی بررسی دقیق مقدار بازگشتی، استفاده پیش از remove_action و add_action، توجه به اولویت و تست در سناریوهای مختلف. این تابع در افزونه‌های همکاری‌کننده و پروژه‌هایی که اکشن‌های افزونه‌های دیگر را مدیریت می‌کنند، ارزش بالایی دارد. اگر این تابع را در پروژه‌ای واقعی به کار برده‌اید و رفتار غیرمنتظره‌ای دیده‌اید — به‌خصوص در ترکیب با افزونه‌های پیچیده یا در Child Theme — تجربه‌تان می‌تواند راهگشای دیگران باشد. کدام بخش بیشترین زمان را از شما گرفت؟ دیدگاه خود را بنویسید.