هر بار که در جلسه‌ای مشترک با تیم توسعه یک افزونه را بازبینی می‌کنیم، یک الگوی تکراری می‌بینم: کدی که از نظر syntax درست است، ولی رفتارش در عمل سرِ ناهماهنگی دارد. تابعی که روی the_content کار می‌کند و گاهی محتوا را می‌خورد، هوکی که در محیط تست درست اجرا می‌شود ولی در سایت مشتری نه، یا افزونه‌ای که چند ساعت پس از نصب، مصرف CPU سرور را بالا می‌برد. ریشهٔ بیشتر این پرونده‌ها در یک چیز مشترک است: اشتباهات رایج هنگام استفاده از هوک‌ها. اگر مفهوم پایهٔ هوک برایتان روشن است ولی می‌خواهید از تله‌های عملی دوری کنید، این مقاله فهرست اشتباهاتی است که در طول سال‌ها کار روی ده‌ها افزونه و سایت واقعی، بیشترین زمان دیباگ را به خود اختصاص داده‌اند. پیش از ادامه، اگر تازه با مفهوم آشنا شده‌اید، هوک‌های وردپرس چیستند و چگونه کار می‌کنند نقطهٔ شروع بهتری است.

چرا اشتباه در هوک‌ها این‌قدر گران تمام می‌شود؟

سه ویژگی ذاتی هوک‌ها، دامنهٔ اثر اشتباهات را بزرگ می‌کند. اول، هوک‌ها در هر درخواست اجرا می‌شوند؛ یک خط کد نادرست در init یا wp_loaded، به‌ازای هر بازدید یک بار هزینه می‌سازد. دوم، هوک‌ها در تعامل با افزونه‌های دیگر کار می‌کنند؛ یک اشتباه کوچک در انتخاب اولویت، می‌تواند رفتار افزونه‌های دیگر را بی‌سروصدا تغییر دهد. سوم، هوک‌ها اغلب بدون خطای صریح شکست می‌خورند؛ PHP به‌طور پیش‌فرض در بسیاری از سناریوها فقط یک warning خفیف می‌دهد یا هیچ پیامی نمی‌دهد، و نتیجه، رفتارِ «گاهی درست، گاهی غلط» است که سخت‌ترین نوع دیباگ را می‌سازد.

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

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

اشتباه اول: فراموشی return در Filter

پرتکرارترین اشتباه، و در عین حال ساده‌ترین برای اصلاح. در وردپرس، هر Filter باید مقدار پارامتر اولش را برگرداند. اگر این کار را نکنید، وردپرس خروجی آن فیلتر را null در نظر می‌گیرد و در نهایت، محتوای آن بخش از سایت خالی می‌شود. من این الگو را در پروژه‌ای دیده‌ام که در آن، پس از افزودن یک add_filter برای تغییر خروجی خلاصه، کل فهرست آرشیوها ناپدید شد. ساعتی طول کشید تا کشف کنیم یک return جا افتاده است.

// نادرست: مقدار ورودی برگردانده نمی‌شود
add_filter( 'the_excerpt', 'wphk_bad_excerpt' );

function wphk_bad_excerpt( $excerpt ) {
    $excerpt .= ' ...';  // تغییر اعمال می‌شود ولی برگردانده نمی‌شود
}

// درست:
add_filter( 'the_excerpt', 'wphk_good_excerpt' );

function wphk_good_excerpt( $excerpt ) {
    return $excerpt . ' ...';
}

قاعدهٔ سرانگشتی من: هر add_filter که بلافاصله در تابع callback آن یک return نمی‌بینید، یک کاندیدای جدی برای باگ است. اگر تابع مسیرهای شرطی دارد، در انتهای همهٔ مسیرها مقدار ورودی برگردانده شود. ترفند عملی من: نوشتن خط return $input; در ابتدای تابع و اضافه‌کردن منطق بالای آن. به این ترتیب، حتی اگر در وسط کار کدی اضافه شود، همیشه یک مسیر بازگشت وجود دارد. نحوۀ استفادهٔ درست از این الگو در نحوه استفاده از add_filter در وردپرس با جزئیات بیشتری آمده است.

اشتباه دوم: نادیده‌گرفتن پارامتر تعداد آرگومان

اشتباهی که شاید بیشترین سهم را در باگ‌های پنهان دارد: فراموش‌کردن پارامتر چهارم add_action و add_filter. اگر callback شما سه پارامتر می‌گیرد ولی این پارامتر را روی مقدار پیش‌فرض بگذارید، فقط اولین پارامتر به تابع شما می‌رسد و بقیه مقدار null می‌گیرند. نتیجه، منطقی است که در بهترین حالت کار نمی‌کند و در بدترین حالت، دادهٔ نادرست تولید می‌کند.

// نادرست: پارامتر چهارم پیش‌فرض است (1)
add_action( 'save_post', 'wphk_bad_save' );

function wphk_bad_save( $post_id, $post, $update ) {
    if ( $update ) {  // $update همیشه null است
        // این بلاک هیچ‌وقت اجرا نمی‌شود
    }
}

// درست:
add_action( 'save_post', 'wphk_good_save', 10, 3 );

function wphk_good_save( $post_id, $post, $update ) {
    if ( $update ) {
        // منطق درست اجرا می‌شود
    }
}

کشف این اشتباه، بدون آگاهی از ساختار پارامترها سخت است؛ چون هیچ خطایی ظاهر نمی‌شود و فقط نتیجه، نادرست است. الگوهای کشف این پارامترها در چگونه پارامترهای هوک وردپرس را بشناسیم و لایه‌های دقیق‌تر در مهم‌ترین Action Hook های وردپرس و مهم‌ترین Filter Hook های وردپرس فهرست شده‌اند.

اشتباه سوم: اتکا به اولویت پیش‌فرض بدون آگاهی

وقتی به یک هوک متصل می‌شوید و اولویت را صریح تعیین نمی‌کنید، وردپرس مقدار پیش‌فرض ۱۰ را در نظر می‌گیرد. اگر افزونهٔ دیگری هم روی همان هوک با اولویت ۱۰ کار کند، ترتیب اجرای شما به ترتیب نصب افزونه‌ها وابسته می‌شود — و این یعنی رفتاری که ممکن است از این سایت به سایت دیگر متفاوت باشد. در پروژه‌ای که چند افزونه فعال داشت، یک متن پایین نوشته‌ها گاهی نمایش داده می‌شد و گاهی نه؛ علت، همین هم‌اولویتی بود.

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

اشتباه چهارم: حلقه‌های بازگشتی در هوک‌های ذخیره

این اشتباه، یکی از ناخوشایندترین تجربه‌های دیباگ را می‌سازد. سناریو: یک add_action روی save_post می‌نویسید و داخل آن، برای به‌روزرسانی محتوا از wp_update_post استفاده می‌کنید. wp_update_post به‌طور خودکار save_post را دوباره اجرا می‌کند، callback شما دوباره اجرا می‌شود، و این چرخه بدون توقف ادامه پیدا می‌کند تا سرور از پا بیفتد.

// نادرست: حلقهٔ بازگشتی
add_action( 'save_post', 'wphk_bad_recursive_save' );

function wphk_bad_recursive_save( $post_id ) {
    wp_update_post( array(
        'ID'           => $post_id,
        'post_content' => 'متن افزوده شده',
    ) );
}

// درست: استفاده از پرچم محافظت
add_action( 'save_post', 'wphk_safe_save', 10, 1 );

function wphk_safe_save( $post_id ) {
    if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
        return;
    }
    if ( wp_is_post_revision( $post_id ) ) {
        return;
    }
    if ( get_post_meta( $post_id, '_wphk_already_processed', true ) ) {
        return;
    }

    // ثبت وضعیت پیش از به‌روزرسانی
    update_post_meta( $post_id, '_wphk_already_processed', 1 );

    wp_update_post( array(
        'ID'           => $post_id,
        'post_content' => 'متن افزوده شده',
    ) );
}

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

اشتباه پنجم: نبود شروط زمینه‌ای

هوک‌های عمومی مثل the_content، wp_head، init در همهٔ صفحات اجرا می‌شوند. اگر کد شما در ابتدای خود بررسی نکند که در کدام زمینه اجرا می‌شود، در صفحاتی که نباید، اثر می‌گذارد. نمونهٔ کلاسیک: افزودن یک متن به the_content که در پیشخوان، در ویجت‌ها، در آر‌اس‌اس و در پیش‌نمایش‌ها هم ظاهر می‌شود.

// نادرست: بدون شرط زمینه
add_filter( 'the_content', 'wphk_no_condition' );

function wphk_no_condition( $content ) {
    return $content . '<p>متن اضافه</p>';
}

// درست: با شروط زمینه‌ای
add_filter( 'the_content', 'wphk_with_conditions' );

function wphk_with_conditions( $content ) {
    if ( is_admin() || ! is_singular( 'post' ) ) {
        return $content;
    }
    if ( ! in_the_loop() || ! is_main_query() ) {
        return $content;
    }
    if ( is_feed() ) {
        return $content;
    }
    return $content . '<p>متن اضافه</p>';
}

سه شرط کلیدی که در هر callback عمومی باید بررسی شوند: is_admin()، in_the_loop()، و is_main_query(). بدون این‌ها، رفتار افزونهٔ شما به‌طور غیرقابل پیش‌بینی در بخش‌های مختلف سایت ظاهر می‌شود. الگوهای مشابه این بررسی‌ها در مقالات مربوط به نحوه حذف یک Filter Hook در وردپرس و نحوه حذف یک Action Hook در وردپرس به‌عنوان بخشی از کد حرفه‌ای دیده می‌شود.

اشتباه ششم: کار سنگین در مسیر حساس

هر hook handler روی مسیر کاربر (صفحهٔ محصول، سبد خرید، تسویه‌حساب، صفحهٔ ورود) که کوئری سنگین یا فراخوانی سرویس بیرونی انجام دهد، تأخیر مستقیم برای کاربر می‌سازد. این اشتباه در فروشگاه‌های ووکامرسی بسیار شایع است و بیشترین شکایت «سایت کند» را می‌سازد.

// نادرست: کوئری سنگین در هر بازدید
add_action( 'woocommerce_before_cart', 'wphk_bad_heavy_cart' );

function wphk_bad_heavy_cart() {
    $args = array(
        'post_type'      => 'product',
        'posts_per_page' => -1,
        'meta_query'     => array( /* شرط پیچیده */ ),
    );
    $all = get_posts( $args );
    // پردازش داده‌ها
}

// درست: کش با ترنزینت
add_action( 'woocommerce_before_cart', 'wphk_safe_cart' );

function wphk_safe_cart() {
    $cached = get_transient( 'wphk_cart_data' );
    if ( false === $cached ) {
        $args = array(
            'post_type'      => 'product',
            'posts_per_page' => 20,
        );
        $cached = get_posts( $args );
        set_transient( 'wphk_cached_data_placeholder', $cached, HOUR_IN_SECONDS );
    }
    // استفاده از دادهٔ کش‌شده
}

سه تکنیک که در پروژه‌های خودم به‌کار می‌برم: کش با transient، انتقال کار سنگین به هوک دیرهنگام‌تر مثل wp_loaded، و انتقال داده به cron یا صف پس‌زمینه. تفاوت تأثیر این تکنیک‌ها روی شاخص‌های Core Web Vitals را در Core Web Vitals چیست و چرا گوگل بر آن تاکید دارد با عدد سنجیده‌ام.

اشتباه هفتم: نام‌گذاری بدون پیشوند و بدون namespace

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

// نادرست:
function init_settings() { /* ... */ }
add_action( 'init', 'init_settings' );

// درست:
function wphk_custom_init_settings() { /* ... */ }
add_action( 'init', 'wphk_custom_init_settings' );

// یا در قالب کلاس:
namespace WPHKCustom;

class Settings {
    public function init() { /* ... */ }
}
add_action( 'init', array( new Settings(), 'init' ) );

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

اشتباه هشتم: حذف هوک در زمان اشتباه

حذف یک hook handler که افزونهٔ دیگری ثبت کرده، به یک زمان‌بندی دقیق نیاز دارد: بعد از ثبت آن هوک و پیش از اجرای آن. اگر در زمان اشتباه این کار را انجام دهید، حذف موفق نمی‌شود و حتی متوجه نمی‌شوید چرا کد شما اثر ندارد.

// نادرست: در زمان اشتباه (پیش از ثبت هوک افزونهٔ دیگر)
add_action( 'init', 'wphk_bad_remove' );

function wphk_bad_remove() {
    remove_action( 'the_content', 'other_plugin_banner' );
}

// درست: در wp_loaded، پس از همهٔ ثبت‌ها
add_action( 'wp_loaded', 'wphk_good_remove' );

function wphk_good_remove() {
    remove_action( 'the_content', 'other_plugin_banner', 10 );
}

دو نکتهٔ کلیدی: اول، در remove_action و remove_filter، باید همان اولویتی که افزونهٔ اصلی استفاده کرده را بدهید؛ در غیر این صورت حذف انجام نمی‌شود. دوم، در پروژه‌های واقعی، پیشنهاد می‌کنم پیش از حذف، با یک error_log تأیید کنید که تابع موردنظر واقعاً در آن اولویت ثبت شده است. جزئیات این دو تابع در نحوه حذف یک Action Hook در وردپرس و نحوه حذف یک Filter Hook در وردپرس آمده است.

اشتباه نهم: انتخاب نادرست خود هوک

گاهی مشکل در انتخاب هوک است، نه در پارامتر یا اولویت. مثال کلاسیک: کدی که باید فقط پس از بارگذاری کامل وردپرس اجرا شود، روی init قرار می‌گیرد و به همین دلیل به توابعی که در مراحل بعدی مقدار می‌گیرند، دسترسی ندارد. یا کدی که باید در front اجرا شود، در admin_init قرار می‌گیرد و فقط در پیشخوان کار می‌کند.

نیازهوک نادرست رایجهوک درست
دسترسی به کوئری اصلیinitpre_get_posts یا wp
ثبت نوع نوشتهwp_loadedinit
افزودن متا به headwp_footerwp_head
ذخیرهٔ دادهٔ فرم پیشخوانinitadmin_post_* یا admin_init
اجرای منطق پس از بارگذاری همهٔ افزونه‌هاplugins_loadedwp_loaded

اشتباه در انتخاب هوک، معادل انتخاب ابزار اشتباه برای یک کار است: تابع ممکن است درست نوشته شده باشد ولی هیچ‌وقت به آن دادهٔ لازم نرسد. مطالعهٔ جامع چرخهٔ هوک‌ها در هوک‌های وردپرس در توسعه افزونه چه کاربردی دارند راهنمای خوبی است.

اشتباه دهم: دست‌زدن مستقیم به ابرسراسری‌ها

دسترسی به $_POST، $_GET و $_SERVER بدون پاک‌سازی، یکی از رایج‌ترین منابع آسیب‌پذیری در افزونه‌های وردپرس است. در هوک‌های ثبت‌نام، پردازش فرم، و ذخیرهٔ تنظیمات، این اشتباه می‌تواند مسیر ورود داده‌های آلوده به دیتابیس باز کند.

// نادرست:
function wphk_bad_save_field() {
    update_option( 'wphk_custom_field', $_POST['wphk_field'] );
}

// درست:
function wphk_good_save_field() {
    if ( ! current_user_can( 'manage_options' ) ) {
        return;
    }
    if ( ! isset( $_POST['wphk_nonce'] ) ||
         ! wp_verify_nonce( $_POST['wphk_nonce'], 'wphk_save' ) ) {
        return;
    }
    $value = sanitize_text_field( wp_unslash( $_POST['wphk_field'] ) );
    update_option( 'wphk_custom_field', $value );
}

سه لایهٔ محافظت در مثال درست: بررسی دسترسی، بررسی nonce، و پاک‌سازی ورودی. بدون این‌ها، افزونهٔ شما یک درِ باز برای مهاجم است. توضیح جامع این الگو در هوک‌های وردپرس و افزایش امنیت کد آمده است. برای درک چرایی این سختگیری، حملات XSS چیست و چگونه دفع می‌شود و CSRF چیست و چگونه از آن جلوگیری کنیم را یک نگاه بیندازید.

اشتباه یازدهم: نبود مستندسازی و نسخه‌بندی

این اشتباه، زمان حال را نابود نمی‌کند ولی هزینهٔ بزرگی برای آینده می‌سازد. کدی که با یک عدد اولویت خاص نوشته شده ولی دلیلش در هیچ کامنتی نیست، در بازبینی بعدی به یک راز تبدیل می‌شود. توسعه‌دهندهٔ جدید نمی‌داند آن عدد را تغییر دهد یا نه، و در نهایت کد دست‌نخورده می‌ماند و ضعف‌هایش انباشته می‌شود.

// نادرست:
add_filter( 'the_content', 'wphk_add_notice', 25 );

// درست:
// اولویت 25: پس از افزونهٔ سئو (10) و پیش از افزونهٔ نمایشی (50).
// این تابع به خروجی سئو وابسته نیست و باید پیش از لایهٔ نمایش اجرا شود.
add_filter( 'the_content', 'wphk_add_notice', 25 );

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

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

نقشهٔ پیشگیری و گام بعدی عملی

یازده اشتباه بالا را می‌توان در سه قاعدهٔ عملی جمع کرد که در پروژه‌های خودم آویزهٔ گوش کرده‌ام. قاعدهٔ اول: هر Filter باید return داشته باشد، در همهٔ مسیرها. اگر شک دارید، ابتدای تابع یک return $input; بگذارید و منطق را بالای آن اضافه کنید. قاعدهٔ دوم: هر هوک عمومی باید شرط زمینه داشته باشد. is_admin()، in_the_loop() و is_main_query() سه شرط ابتدایی هر callback عمومی‌اند. قاعدهٔ سوم: هر عدد اولویت باید کامنت توضیحی داشته باشد. این سه قاعده، شاید هفتاد درصد باگ‌های هوکی را از پیش جلوگیری کنند.

گام بعدی عملی که پیشنهاد می‌کنم: فایل functions.php چایلد تم یا افزونهٔ اختصاصی خود را باز کنید و فهرست تمام add_action و add_filterها را مرور کنید. برای هرکدام از سه سؤال زیر پاسخ بدهید: آیا callback من return دارد؟ آیا شروط زمینه در ابتدای آن هست؟ آیا اولویت صریح با کامنت دارد؟ این بازبینی نیم‌ساعته، تجربه‌ای می‌سازد که در پروژهٔ بعدی، صدها ساعت دیباگ را ذخیره می‌کند. اگر در پروژه‌ای با یکی از این اشتباهات روبه‌رو شده‌اید و راه‌حل جالبی پیدا کرده‌اید، برای من جالب است بدانید کدام مورد بود و چگونه کشفش کردید — تجربهٔ خودتان را در دیدگاه‌ها بنویسید؛ به‌ویژه اگر روشی پیدا کرده‌اید که به‌جای اصلاح تک‌تک موارد، ساختار کد را طوری بازطراحی می‌کند که این دسته از اشتباهات از ابتدا رخ ندهند. 🛠️