یک اشتباه کوچک که سه ساعت وقت گرفت

سال ۱۳۹۸، در یک پروژه فروشگاهی، می‌خواستم قیمت محصولات را قبل از نمایش، ۱۰ درصد افزایش دهم. کد را نوشتم و روی هوک the_content ثبت کردم، ولی به‌جای add_filter از add_action استفاده کردم. سایت بالا آمد، صفحه محصول باز شد، ولی هیچ تغییری در قیمت‌ها دیده نمی‌شد. سه ساعت وقت گذاشتم تا بفهمم چرا کد اجرا می‌شود ولی نتیجه‌ای ندارد. لاگ‌گیری کردم، متغیرها را چاپ کردم، همه‌چیز درست به نظر می‌رسید. بالاخره یک همکار با تجربه‌تر، فایل را باز کرد و در پنج ثانیه گفت: «این باید add_filter باشد، نه add_action.» آن روز متوجه شدم که تفاوت این دو تابع، فقط در نامشان نیست؛ در ماهیت کاری است که انجام می‌دهند. اکشن کاری انجام می‌دهد، فیلتر داده‌ای را تغییر می‌دهد. اشتباه گرفتن این دو، منبع خطاهای ساکت و سخت‌پیداست. این مقاله، همان تجربه و تجربه‌های بعدی را در قالب یک راهنمای کاربردی باز می‌کند.

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

چرا درک تفاوت اکشن و فیلتر، از اولین روز مهم است؟

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

  1. اشتباه گرفتن این دو، خطای ساکت تولید می‌کند: برخلاف خطای نحوی که PHP فریاد می‌زند، استفاده اشتباه از add_action به‌جای add_filter سایت را نمی‌شکند — فقط کاری که می‌خواستید اتفاق نمی‌افتد. این نوع خطا، ساعت‌ها وقت می‌گیرد چون ظاهراً همه‌چیز درست است.
  2. اساس معماری افزونه و قالب: هر افزونه‌ای که می‌نویسید، از این دو تابع برای اتصال به هسته استفاده می‌کند. اگر انتخاب اشتباه باشد، معماری افزونه از روز اول مشکل دارد.
  3. تفاوت در قابلیت بازگشت: فیلتر همیشه یک مقدار برمی‌گرداند؛ اکشن چیزی برنمی‌گرداند. اگر این تفاوت را ندانید، callback شما ممکن است داده را از بین ببرد. راهنمای کامل در هوک‌های وردپرس چیست.
اکشن، مثل زنگ در است: کسی می‌زند تا شما کاری کنید. فیلتر، مثل صافی قهوه است: داده از آن رد می‌شود تا خالص‌تر شود.

تعریف اکشن: کاری که انجام می‌شود

اکشن (Action) در وردپرس، مکانیزمی است که در لحظه مشخصی از اجرای هسته، کد شما را صدا می‌زند. اکشن داده‌ای را تغییر نمی‌دهد؛ فقط کاری انجام می‌دهد. مثال‌های طبیعی از دنیای واقعی:

  • وقتی نوشته‌ای منتشر می‌شود، ایمیلی به نویسنده بفرست (publish_post).
  • وقتی کاربری ثبت‌نام می‌کند، در یک فایل لاگ ثبت کن (user_register).
  • وقتی سفارشی در ووکامرس تمام می‌شود، یک درخواست به CRM بفرست (woocommerce_thankyou).
  • در ابتدای بارگذاری وردپرس، یک متغیر سراسری مقداردهی کن (init).

الگوی ثبت اکشن:

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

function my_plugin_log_post_save( $post_id, $post, $update ) {
    // اینجا کاری انجام می‌دهیم؛ چیزی برنمی‌گردانیم
    error_log( sprintf( 'نوشته %d ذخیره شد', $post_id ) );
}

سه نکته حیاتی در استفاده صحیح از اکشن: یک — callback چیزی برنمی‌گرداند. اگر برگرداند، وردپرس آن را نادیده می‌گیرد. دو — اکشن روی مقدار تأثیری ندارد. اگر می‌خواهید داده‌ای را در لحظه اجرای اکشن تغییر دهید، اشتباه مسیر رفته‌اید. سه — تعداد پارامترها مهم است. اگر accepted_args را کمتر از مقدار واقعی بدهید، callback شما فقط پارامتر اول را می‌بیند. راهنمای کامل در نحوه استفاده از add_action، مهم‌ترین اکشن هوک‌ها، و پارامترهای هوک.

تعریف فیلتر: داده‌ای که تغییر می‌کند

فیلتر (Filter) در وردپرس، مکانیزمی است که داده‌ای را به کد شما می‌دهد، شما آن را تغییر می‌دهید و برمی‌گردانید. اگر callback فیلتر، مقدار را برنگرداند، مقدار از بین می‌رود — این یکی از شایع‌ترین باگ‌هاست. مثال‌های طبیعی:

  • عنوان نوشته را قبل از نمایش تغییر بده (the_title).
  • طول خلاصه نوشته را کوتاه‌تر کن (excerpt_length).
  • به محتوای هر نوشته یک بخش اضافه کن (the_content).
  • پارامترهای کوئری را قبل از اجرا تغییر بده (pre_get_posts).
  • متن یک فیلد تنظیمات را قبل از ذخیره پاک‌سازی کن (sanitize_option_*).

الگوی ثبت فیلتر:

add_filter( 'the_content', 'my_plugin_append_signature', 15 );

function my_plugin_append_signature( $content ) {
    // تغییر داده و برگرداندن
    if ( is_singular( 'post' ) ) {
        $content .= '<p class="signature">با تشکر از مطالعه شما</p>';
    }
    return $content;
}

سه نکته حیاتی در استفاده صحیح از فیلتر: یک — همیشه return کنید. اگر برنگردانید، وردپرس مقدار null می‌گیرد و صفحه سفید یا محتوای خالی می‌شود. دو — همیشه مقدار را بگیرید و برگردانید، حتی اگر تغییری نکرده باشد. الگوی اشتباه: if ( $condition ) return $content; و اگر $condition برقرار نبود، چیزی برنگردانید. سه — پارامترها را دقیق اعلام کنید. بعضی فیلترها چند پارامتر دارند. راهنمای کامل در نحوه استفاده از add_filter، مهم‌ترین فیلتر هوک‌ها، و هوک‌های محتوای نوشته.

فیلتر بدون return، مثل چراغ‌قوه‌ای بدون لامپ است: باتری کار می‌کند ولی نور نمی‌دهد.

جدول مقایسه جامع: اکشن در برابر فیلتر

این جدول، خلاصه‌ای از تمام تفاوت‌ها در یک نگاه:

معیاراکشن (Action)فیلتر (Filter)
هدف اصلیانجام یک کار در لحظه مشخصتغییر یک داده در لحظه مشخص
نوع خروجیبدون خروجی (void)مقدار تغییر یافته (همان نوع ورودی)
تابع ثبتadd_actionadd_filter
تابع اجراdo_actionapply_filters
تابع حذفremove_actionremove_filter
مثال سادهافزودن کد رهگیری به فوترافزودن متن به محتوای نوشته
خطای عدم returnبی‌اثر (چون return نمی‌خواهد)مقدار null، صفحه سفید
قابلیت حذف توسط افزونه دیگربله (با priority درست)بله (با priority درست)
اولویت پیش‌فرض1010
مثال در هسته وردپرسwp_head، wp_footer، initthe_content، the_title، excerpt_length

نکته مهم: قواعدی مثل priority و remove در هر دو یکسان است، فقط نوع callback متفاوت است. راهنمای کامل در هوک‌های وردپرس چیست و کنترل ترتیب اجرای هوک‌ها.

سه روش تشخیص سریع: اکشن یا فیلتر؟

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

روش اول، جستجو در مستندات رسمی: در سایت developer.wordpress.org، هر هوک صفحه اختصاصی دارد که مشخص می‌کند اکشن است یا فیلتر. سریع‌ترین و معتبرترین روش.

روش دوم، سؤال از خود کد: اگر هوک، داده‌ای را به callback شما پاس می‌دهد و از شما می‌خواهد که آن را برگردانید، فیلتر است. اگر فقط اجرا می‌شود و چیزی از شما نمی‌خواهد، اکشن است. مثال: the_content محتوا را می‌دهد و می‌خواهد برگردانید → فیلتر. wp_footer فقط اجرا می‌شود → اکشن.

روش سوم، تست با کد: یک callback با return بنویسید و روی هر دو تابع ثبت کنید. اگر روی add_filter خطا داد ولی روی add_action کار کرد (بدون return در خروجی)، اکشن است. راهنمای کامل در تفاوت اکشن و فیلتر و هوک‌های وردپرس.

یک تجربه میدانی: در پروژه‌ای، یک توسعه‌دهنده تازه‌کار می‌خواست به محتوای نوشته‌ها یک خط اضافه کند، ولی روی save_post ثبت کرده بود. چون save_post اکشن است، کد اجرا می‌شد ولی تغییری در محتوا دیده نمی‌شد. سه ساعت وقت گرفت تا تشخیص داده شود که باید روی the_content با add_filter نوشته شود. بعد از آن، روش سوم (تست با کد) به یک عادت تبدیل شد.

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

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

مثال اول، افزودن متن به محتوای نوشته (فیلتر):

add_filter( 'the_content', 'my_plugin_add_source', 12 );

function my_plugin_add_source( $content ) {
    if ( ! is_singular( 'post' ) ) {
        return $content;
    }
    
    $source = get_post_meta( get_the_ID(), '_source', true );
    if ( ! empty( $source ) ) {
        $content .= sprintf(
            '<p class="source">منبع: %s</p>',
            esc_html( $source )
        );
    }
    
    return $content;
}

مثال دوم، لاگ‌گیری از انتشار نوشته (اکشن):

add_action( 'publish_post', 'my_plugin_log_publication', 10, 2 );

function my_plugin_log_publication( $post_id, $post ) {
    error_log( sprintf(
        'نوشته «%s» با شناسه %d منتشر شد',
        $post->post_title,
        $post_id
    ) );
}

مثال سوم، تغییر طول خلاصه (فیلتر):

add_filter( 'excerpt_length', function( $length ) {
    return 25;
} );

مثال چهارم، افزودن اسکریپت به فوتر (اکشن):

add_action( 'wp_footer', 'my_plugin_add_tracking_code' );

function my_plugin_add_tracking_code() {
    if ( is_admin() ) {
        return;
    }
    ?>
    <script>
        // کد رهگیری اینجا
    </script>
    <?php
}

راهنمای تکمیلی در هوک‌های محتوای نوشته، مهم‌ترین اکشن هوک‌ها، مهم‌ترین فیلتر هوک‌ها، و توابع داده‌های نوشته.

اشتباهات رایج در انتخاب بین اکشن و فیلتر

فهرست کوتاه اما گران‌قیمت از اشتباهاتی که در کدهای بازبینی‌شده دیده‌ام:

  • استفاده از add_action روی فیلتر: کد اجرا می‌شود ولی مقدار برگردانده‌شده نادیده گرفته می‌شود. همان اشتباهی که مقاله را با آن شروع کردم.
  • نبود return در callback فیلتر: مقدار null برمی‌گردد و محتوا از بین می‌رود. راهنما در نحوه استفاده از add_filter.
  • استفاده از add_filter برای کارهایی که return نمی‌خواهند: مثل ریدایرکت، ارسال ایمیل. اگر callback فیلتر شما return نداشته باشد و کار انجام دهد، ممکن است مقدار ناخواسته از بین برود.
  • نادیده‌گرفتن تعداد پارامتر: اگر فیلتر سه پارامتر دارد و شما یک پارامتر اعلام کنید، فقط پارامتر اول را می‌بینید. راهنما در پارامترهای هوک.
  • استفاده از echo در فیلتر: فیلتر باید مقدار برگرداند، نه چاپ. با echo، داده در جای اشتباه چاپ می‌شود. راهنما در استفاده درست از هوک‌ها.
  • فراموش‌کردن priority در remove: remove_filter با priority اشتباه، اثر ندارد. راهنما در حذف فیلتر هوک.
  • استفاده از closure در هوک‌هایی که باید حذف شوند: closure قابل حذف نیست. راهنما در حذف اکشن هوک.
  • نادیده‌گرفتن context اجرا: is_singular در init کار نمی‌کند. راهنما در هوک‌های وردپرس.
  • نبود مستندسازی دلیل انتخاب: سه ماه بعد، خودتان هم نمی‌دانید چرا اینجا اکشن است نه فیلتر. راهنما در اصول کدنویسی تمیز.
  • نبود تست در محیط استیجینگ: اشتباه در انتخاب نوع هوک، روی سایت زنده کشف می‌شود. راهنما در بهترین روش تست وردپرس.
در انتخاب بین اکشن و فیلتر، هیچ‌وقت به نام هوک اعتماد نکنید. به ماهیت کاری که انجام می‌دهد اعتماد کنید.

اکشن و فیلتر در معماری حرفه‌ای

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

  1. اکشن برای side effect، فیلتر برای transformation: هرگاه کد شما چیزی خارج از داده را تغییر می‌دهد (ذخیره، ارسال، لاگ)، اکشن. هرگاه داده‌ای را برای استفاده بعدی تغییر می‌دهد، فیلتر. این تفکیک، از روز اول باید روشن باشد.
  2. پرهیز از اکشن‌هایی که data را تغییر می‌دهند: اگر کد شما در اکشن، متغیر سراسری را تغییر می‌دهد، احتمالاً اشتباه است. تغییر داده باید در فیلتر باشد. راهنمای معماری در ساختار هسته وردپرس و اصول کدنویسی تمیز.
  3. هوک سفارشی: اکشن یا فیلتر؟ اگر می‌خواهید افزونه‌های دیگر بتوانند در لحظه‌ای خاص کاری انجام دهند، اکشن سفارشی بسازید. اگر می‌خواهید داده‌ای را تغییر دهند، فیلتر سفارشی. راهنما در ساخت اکشن سفارشی و ساخت فیلتر سفارشی.

یک تجربه میدانی: در پروژه‌ای با چهار توسعه‌دهنده، یک قانون تیمی گذاشتیم: «در callback فیلتر، هیچ‌وقت نباید کاری خارج از return انجام شود.» با این قاعده، نصف باگ‌های عجیب در رفتار سایت حذف شد، چون هر کسی که کد می‌خواند، می‌دانست هر فیلتر چه چیزی را تغییر می‌دهد و هیچ side effect پنهانی ندارد.

دیباگ هوک‌های اشتباه: ابزارها

وقتی مشکوک هستید که یک هوک در جای اشتباه استفاده شده، سه ابزار:

  • Query Monitor: لیست تمام هوک‌های اجرا شده در صفحه را با ترتیب و callback نشان می‌دهد. اگر فیلتری با return نبود، در فهرست callbackها دیده می‌شود. راهنما در توابع دیباگ وردپرس.
  • error_log: در ابتدای callback، error_log( 'reached' ) بگذارید تا مطمئن شوید کد اجرا می‌شود. اگر اجرا می‌شود ولی نتیجه نمی‌دهد، اشتباه بین اکشن و فیلتر است. راهنما در دیباگ کد سفارشی وردپرس.
  • تست با دو تابع: یک callback بنویسید و روی add_action و add_filter ثبت کنید. اگر روی add_filter خطا داد ولی روی add_action خطا نداد، هوک اکشن است. راهنمای کامل در تست و دیباگ پروژه‌های وردپرس.

الگوی حرفه‌ای: جداسازی callback اکشن و فیلتر

در پروژه‌های بزرگ، برای حفظ خوانایی، callback اکشن و فیلتر را در دو کلاس جدا نگه دارید:

class My_Plugin_Actions {
    public static function init() {
        add_action( 'save_post', array( __CLASS__, 'on_save_post' ), 10, 3 );
        add_action( 'user_register', array( __CLASS__, 'on_user_register' ) );
        add_action( 'wp_footer', array( __CLASS__, 'render_tracking' ) );
    }
    
    public static function on_save_post( $post_id, $post, $update ) { /* ... */ }
    public static function on_user_register( $user_id ) { /* ... */ }
    public static function render_tracking() { /* ... */ }
}

class My_Plugin_Filters {
    public static function init() {
        add_filter( 'the_content', array( __CLASS__, 'modify_content' ), 12 );
        add_filter( 'excerpt_length', array( __CLASS__, 'set_excerpt_length' ) );
        add_filter( 'the_title', array( __CLASS__, 'modify_title' ), 10, 2 );
    }
    
    public static function modify_content( $content ) { return $content; }
    public static function set_excerpt_length( $length ) { return 25; }
    public static function modify_title( $title, $post_id ) { return $title; }
}

My_Plugin_Actions::init();
My_Plugin_Filters::init();

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

جمع‌بندی

تفاوت اکشن و فیلتر در وردپرس، در سه جمله خلاصه می‌شود: اکشن، کاری را انجام می‌دهد؛ فیلتر، داده‌ای را تغییر می‌دهد. اکشن، چیزی برنمی‌گرداند؛ فیلتر، همیشه مقدار برمی‌گرداند. اشتباه گرفتن این دو، خطای ساکت تولید می‌کند که ساعت‌ها وقت می‌گیرد. سه اصل را در پایان تاکید می‌کنم: اول، پیش از نوشتن callback، از خود بپرسید «این کد، کاری انجام می‌دهد یا داده‌ای را تغییر می‌دهد؟» دوم، در فیلتر، همیشه مقدار را return کنید. سوم، callback اکشن و فیلتر را در کد جدا نگه دارید تا نگهداری آسان‌تر شود.

اگر امروز یک کار در این مسیر انجام می‌دهید: در آخرین افزونه خود، فهرست callbackهای اکشن و فیلتر را جدا کنید و ببینید آیا کدام callback در جای اشتباه قرار گرفته است. همان یک بازبینی، در پروژه‌های بعدی تبدیل به الگوی ذهنی می‌شود. اگر تجربه‌ای از یک اشتباه اکشن/فیلتر یا یک الگوی موفق در پروژه‌ای دارید، در دیدگاه‌ها بنویسید — همان گزارش‌های واقعی، این راهنما را برای توسعه‌دهنده بعدی دقیق‌تر می‌کند. 🔀