تابع add_filter در وردپرس (WordPress) یکی از دو هوک (Hook) بنیادین این سیستم است که به توسعه‌دهندگان امکان می‌دهد بدون دست زدن به کد هسته، رفتار داده‌ها و خروجی‌ها را در لحظه تغییر دهند. این تابع یک callback را به یک فیلتر (Filter) مشخص متصل می‌کند و هر بار که آن فیلتر با استفاده از apply_filters فراخوانی شود، callback اجرا می‌شود و مقدار ورودی را پردازش و بازمی‌گرداند. درک دقیق نحوه کار add_filter، ترتیب اجرای اولویت‌ها (Priority)، پارامترهای ارسالی و دام‌های امنیتی، پیش‌نیاز جدی برای هر توسعه‌دهنده قالب و افزونه است. اگر add_filter را به‌درستی نفهمید، کد شما ممکن است بی‌صدا شکست بخورد، اولویت‌ها را اشتباه اعمال کند یا امنیت سایت را به خطر بیندازد. این نوشتار تلاش می‌کند لایه‌های داخلی این تابع، الگوهای کاربردی و اشتباهات رایج را با نگاه فنی و تجربی بررسی کند.

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

تابع add_filter چیست و چه مسئله‌ای را حل می‌کند؟

add_filter() یک تابع PHP در هسته وردپرس است که یک callback (تابع بازگشتی) را به یک فیلتر مشخص متصل می‌کند. هر فیلتر یک نقطه توسعه (Extension Point) در کد است که به توسعه‌دهندگان اجازه می‌دهد داده‌ای را قبل از نهایی شدن، تغییر دهند.

مسئله‌ای که add_filter حل می‌کند، ساده اما عمیق است: چگونه می‌توان رفتار یک سیستم را بدون تغییر کد هسته آن تغییر داد؟ اگر هر توسعه‌دهنده‌ای برای تغییر یک متن یا یک آرایه، کد هسته را ویرایش کند، در به‌روزرسانی بعدی همه تغییرات از بین می‌رود. هوک‌ها این مشکل را حل می‌کنند. add_filter یکی از دو نوع هوک است (نوع دیگر Action است) و برای تغییر داده‌ها طراحی شده است.

تفاوت بنیادین add_filter با add_action در این است که add_filter همیشه یک مقدار را دریافت می‌کند، آن را پردازش می‌کند و یک مقدار بازمی‌گرداند. این مقدار بازگشتی، جایگزین مقدار قبلی می‌شود. اگر callback شما مقداری برنگرداند، فیلتر بعدی مقدار null دریافت می‌کند و زنجیره شکسته می‌شود. این نکته ساده، یکی از پرتکرارترین اشتباهات در استفاده از add_filter است.

برای درک بنیادین هوک‌ها، مطالعه مقاله هوک‌های وردپرس چیستند و چگونه کار می‌کنند پیشنهاد می‌شود. همچنین، مفهوم Hooking در برنامه‌نویسی، ریشه این معماری را روشن می‌کند.

add_filter یک قرارداد است، نه یک دستور. callback شما متعهد می‌شود مقدار ورودی را پردازش کند و مقدار خروجی را بازگرداند. اگر این تعهد را نشکنید، سیستم پایدار می‌ماند.

تفاوت Filter و Action در معماری هوک‌های وردپرس

وردپرس دو نوع هوک دارد: Action و Filter. تفاوت این دو، یکی از پرتکرارترین پرسش‌های توسعه‌دهندگان است. جدول زیر این تفاوت‌ها را روشن می‌کند:

ویژگیFilter (فیلتر)Action (اکشن)
هدفتغییر دادهاجرای عملیات جانبی
مقدار بازگشتیاجباریاختیاری و نادیده گرفته می‌شود
تابع اتصالadd_filteradd_action
تابع اجراapply_filtersdo_action
مثالتغییر متن دکمهارسال ایمیل پس از ثبت سفارش

نکته کلیدی این است که add_filter و add_action در سطح داخلی، هر دو یک تابع هستند. در واقع، add_action در هسته وردپرس به‌صورت زیر تعریف شده است:

function add_action($tag, $function_to_add, $priority = 10, $accepted_args = 1) {
    return add_filter($tag, $function_to_add, $priority, $accepted_args);
}

این یعنی add_action فقط یک پوشش (Wrapper) برای add_filter است. تفاوت آن‌ها در نحوه فراخوانی است: apply_filters مقدار را پاس می‌دهد، do_action فقط اجرا می‌کند. برای مطالعه بیشتر درباره این تفاوت، مقاله تفاوت Action و Filter در وردپرس چیست را ببینید.

ساختار و پارامترهای add_filter

ساختار رسمی add_filter به‌صورت زیر است:

add_filter(
    string   $tag,
    callable $callback,
    int      $priority = 10,
    int      $accepted_args = 1
): bool

هر پارامتر نقش مشخصی دارد و درک آن‌ها برای استفاده درست ضروری است. در ادامه، هر پارامتر با جزئیات بررسی می‌شود.

پارامتر $tag

پارامتر $tag نام فیلتری است که می‌خواهید به آن متصل شوید. این نام یک رشته (String) است و باید دقیقاً با نام فیلتری که در کد هسته یا افزونه با apply_filters() فراخوانی می‌شود، مطابقت داشته باشد. اگر نام را اشتباه بنویسید، callback شما هرگز اجرا نمی‌شود و هیچ خطایی هم دریافت نمی‌کنید.

add_filter('the_content', 'my_content_filter');

در این مثال، 'the_content' نام فیلتری است که قبل از نمایش محتوای نوشته اجرا می‌شود. این یکی از پرکاربردترین فیلترهاست. فهرست مهم‌ترین فیلترهای وردپرس در مقاله مهم‌ترین Filter Hook های وردپرس بررسی شده است.

پارامتر $callback

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

  • نام تابع: یک رشته که نام تابع سراسری است. مثال: 'my_filter_function'
  • متد شیء: یک آرایه با دو عنصر: شیء و نام متد. مثال: array($object, 'method_name')
  • متد استاتیک: یک آرایه با نام کلاس و نام متد. مثال: array('My_Class', 'static_method')
  • تابع بی‌نام: یک Closure. مثال: function($value) { return $value; }
  • تابع پیکانی (PHP 7.4+): مثال: fn($value) => $value

callback شما باید یک پارامتر دریافت کند (یا بیشتر، بسته به مقدار $accepted_args) و یک مقدار بازگرداند. اگر callback مقداری برنگرداند، مقدار null به فیلتر بعدی پاس داده می‌شود و زنجیره شکسته می‌شود.

پارامتر $priority

پارامتر $priority تعیین می‌کند که callback شما در چه ترتیبی نسبت به سایر callbackهای همان فیلتر اجرا شود. مقدار پیش‌فرض ۱۰ است. callbackهایی که priority کمتری دارند، زودتر اجرا می‌شوند.

add_filter('the_content', 'first_filter', 5);
add_filter('the_content', 'second_filter', 10);
add_filter('the_content', 'third_filter', 20);

در این مثال، first_filter اول اجرا می‌شود، سپس second_filter و در نهایت third_filter. اگر دو callback priority یکسان داشته باشند، ترتیب ثبت آن‌ها تعیین‌کننده است. برای مطالعه بیشتر، مقاله Priority در هوک‌های وردپرس چیست را ببینید.

پارامتر $accepted_args

پارامتر $accepted_args تعداد آرگومان‌هایی است که callback شما دریافت می‌کند. مقدار پیش‌فرض ۱ است. اگر فیلتر چند آرگومان پاس می‌دهد و شما می‌خواهید به آن‌ها دسترسی داشته باشید، باید این مقدار را افزایش دهید.

add_filter('the_content', 'my_filter', 10, 2);

function my_filter($content, $post_id) {
    // $content محتوای نوشته
    // $post_id شناسه نوشته
    return $content;
}

اگر $accepted_args را کمتر از تعداد آرگومان‌های واقعی تنظیم کنید، callback شما فقط به تعدادی که مشخص کرده‌اید دسترسی دارد. برای مطالعه بیشتر، مقاله چگونه پارامترهای هوک وردپرس را بشناسیم را ببینید.

مکانیزم داخلی add_filter و WP_Hook

درک مکانیزم داخلی add_filter، برای دیباگ کردن و بهینه‌سازی ضروری است. این مکانیزم در طول نسخه‌های وردپرس تکامل یافته و امروز بسیار کارآمدتر از گذشته است.

متغیر سراسری $wp_filter

وقتی add_filter را فراخوانی می‌کنید، callback شما در یک متغیر سراسری به نام $wp_filter ذخیره می‌شود. این متغیر یک آرایه بزرگ است که کلیدهای آن نام فیلترها و مقادیر آن آرایه‌ای از callbackها هستند. ساختار آن به‌صورت زیر است:

global $wp_filter;
$wp_filter['the_content'][10]['my_filter'] = array(
    'function'      => 'my_filter',
    'accepted_args' => 1
);

این ساختار در نسخه‌های قدیمی وردپرس (پیش از ۴.۷) استفاده می‌شد. در این ساختار، هر callback با priority مشخص و نام یکتا ذخیره می‌شد.

کلاس WP_Hook و بازطراحی وردپرس ۴.۷

در وردپرس ۴.۷ (دسامبر ۲۰۱۶)، یک بازطراحی بنیادین در مکانیزم هوک‌ها انجام شد. ساختار آرایه‌ای ساده جای خود را به یک کلاس به نام WP_Hook داد. این بازطراحی مزایای متعددی داشت:

  • بهبود عملکرد با استفاده از ساختار داده بهینه‌تر
  • امکان iterating بهتر روی callbackها
  • مدیریت دقیق‌تر حذف و افزودن در زمان اجرا
  • پشتیبانی از ترتیب‌های پیچیده‌تر

ساختار داخلی WP_Hook به‌صورت خلاصه:

class WP_Hook {
    public $callbacks = array();
    public $iterations = array();
    public $nest_level = 0;
    public $current_priority = array();
    
    public function add_filter($tag, $function_to_add, $priority, $accepted_args) {
        // ...
    }
    
    public function apply_filters($value, $args) {
        // ...
    }
}

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

نقش apply_filters در اجرای زنجیره

تابع apply_filters() قلب اجرای فیلتر است. وقتی کد هسته یا افزونه‌ای می‌خواهد داده‌ای را قابل تغییر کند، آن را از طریق apply_filters پاس می‌دهد:

$content = apply_filters('the_content', $content, $post_id);

در این لحظه، وردپرس به $wp_filter['the_content'] نگاه می‌کند، همه callbackهای ثبت‌شده را به ترتیب priority اجرا می‌کند و مقدار نهایی را بازمی‌گرداند. اگر هیچ callbackی ثبت نشده باشد، مقدار ورودی بدون تغییر بازگردانده می‌شود.

apply_filters یک پرسش است: «آیا کسی می‌خواهد این داده را تغییر دهد؟» add_filter پاسخ می‌دهد: «بله، این callback من است.»

رفتار Priority و ترتیب اجرای callbackها

priority یک عدد صحیح است و می‌تواند هر مقداری داشته باشد. callbackها به ترتیب صعودی priority اجرا می‌شوند. اگر دو callback priority یکسان داشته باشند، ترتیب ثبت آن‌ها تعیین‌کننده است. اما نکته مهم این است که ترتیب ثبت در وردپرس مدرن، به‌طور تضمینی پایدار نیست.

در WP_Hook، callbackها با استفاده از تابع spl_object_hash() یا مشابه آن ذخیره می‌شوند. این یعنی ترتیب واقعی ممکن است در نسخه‌های مختلف PHP متفاوت باشد. بنابراین، اگر ترتیب دقیق برای شما حیاتی است، باید priorityهای متفاوت اختصاص دهید.

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

add_filter('the_content', 'sanitize_content', 5);
add_filter('the_content', 'add_signature', 10);
add_filter('the_content', 'wrap_in_div', 20);

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

نکته مهم دیگر این است که اگر priority یک callback را بعد از ثبت تغییر دهید، باید ابتدا آن را حذف و سپس دوباره با priority جدید اضافه کنید. خودِ افزودن دوباره با priority متفاوت، نسخه قبلی را حذف نمی‌کند.

پارامتر accepted_args و کنترل آرگومان‌ها

پارامتر $accepted_args به callback شما می‌گوید چند آرگومان از apply_filters دریافت کند. مقدار پیش‌فرض ۱ است. اگر apply_filters سه آرگومان پاس بدهد اما accepted_args شما ۱ باشد، callback فقط به آرگومان اول دسترسی دارد.

// در کد هسته
$value = apply_filters('my_custom_filter', $data, $context, $user_id);

// در کد شما
add_filter('my_custom_filter', 'my_callback', 10, 3);

function my_callback($data, $context, $user_id) {
    // دسترسی به هر سه آرگومان
    return $data;
}

اگر $accepted_args را بیشتر از تعداد واقعی آرگومان‌ها تنظیم کنید، آرگومان‌های اضافی مقدار null خواهند داشت. این کار خطا ایجاد نمی‌کند، اما اگر callback به آن‌ها وابسته باشد، ممکن است به رفتار نادرست منجر شود.

در برخی فیلترها، تعداد آرگومان‌ها در نسخه‌های مختلف وردپرس تغییر کرده است. برای مثال، فیلتر the_content در نسخه‌های قدیمی فقط یک آرگومان داشت، اما در نسخه‌های جدید دو آرگومان ($content و $post_id) پاس می‌دهد. این نکته در توسعه افزونه‌هایی که باید با نسخه‌های مختلف وردپرس کار کنند، حیاتی است.

نقش مقدار بازگشتی در add_filter

مهم‌ترین نکته در استفاده از add_filter، مقدار بازگشتی است. callback شما باید همیشه یک مقدار بازگرداند. اگر این کار را نکنید، مقدار null به فیلتر بعدی پاس داده می‌شود و کل زنجیره شکسته می‌شود.

// اشتباه: عدم بازگشت مقدار
add_filter('the_content', function($content) {
    $content = str_replace('foo', 'bar', $content);
    // مقدار بازگردانده نمی‌شود
});

// صحیح: بازگشت مقدار
add_filter('the_content', function($content) {
    return str_replace('foo', 'bar', $content);
});

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

نکته دیگر این است که مقدار بازگشتی باید همان نوع داده‌ای باشد که apply_filters انتظار دارد. اگر فیلتری یک رشته پاس می‌دهد، callback شما باید یک رشته بازگرداند. اگر یک آرایه پاس می‌دهد، باید آرایه بازگردانید. بازگرداندن نوع اشتباه می‌تواند به خطاهای پنهان منجر شود.

حذف فیلتر با remove_filter

تابع remove_filter() برای حذف یک callback ثبت‌شده استفاده می‌شود. ساختار آن:

remove_filter(
    string   $tag,
    callable $callback,
    int      $priority = 10
): bool

برای حذف موفق، باید نام فیلتر، callback دقیق و priority را مشخص کنید. اگر هر یک از این مقادیر اشتباه باشند، حذف انجام نمی‌شود و هیچ خطایی دریافت نمی‌کنید.

// حذف یک تابع سراسری
remove_filter('the_content', 'my_content_filter', 10);

// حذف یک متد شیء
remove_filter('the_content', array($object, 'method_name'), 10);

// حذف یک متد استاتیک
remove_filter('the_content', array('My_Class', 'static_method'), 10);

نکته مهم: توابع بی‌نام را نمی‌توان با remove_filter حذف کرد، زیرا هیچ نامی برای ارجاع ندارند. اگر نیاز به حذف دارید، باید تابع را در یک متغیر ذخیره کنید یا از متد شیء استفاده کنید.

// اشتباه: تابع بی‌نام قابل حذف نیست
add_filter('the_content', function($content) {
    return $content . 'ساخته شده';
});

// صحیح: استفاده از متغیر
$my_callback = function($content) {
    return $content . 'ساخته شده';
};

add_filter('the_content', $my_callback);
remove_filter('the_content', $my_callback);

برای مطالعه بیشتر، مقاله چگونه یک Filter هوک وردپرس را حذف کنیم را ببینید.

ساخت فیلتر سفارشی با apply_filters

علاوه بر استفاده از فیلترهای موجود، می‌توانید فیلترهای سفارشی خود را بسازید. این کار با استفاده از apply_filters() انجام می‌شود:

function my_get_price($product_id) {
    $price = get_post_meta($product_id, '_price', true);
    
    /**
     * فیلتر قیمت محصول
     *
     * @param float $price      قیمت محصول
     * @param int   $product_id شناسه محصول
     */
    return apply_filters('my_product_price', $price, $product_id);
}

پس از این تعریف، سایر توسعه‌دهندگان می‌توانند قیمت را تغییر دهند:

add_filter('my_product_price', function($price, $product_id) {
    if ($product_id === 123) {
        return $price * 0.9; // ۱۰٪ تخفیف
    }
    return $price;
}, 10, 2);

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

یک افزونه حرفه‌ای، برای هر تصمیم مهم، یک فیلتر سفارشی ارائه می‌دهد. این کار به کاربران امکان می‌دهد بدون تغییر کد افزونه، رفتار آن را تغییر دهند.

استفاده از متدهای شیء و کلاس‌ها در callback

در پروژه‌های شیء‌گرا، استفاده از متدهای شیء در add_filter رایج است. الگوی صحیح:

class My_Plugin {
    public function __construct() {
        add_filter('the_content', array($this, 'filter_content'), 10, 1);
    }
    
    public function filter_content($content) {
        return $content . '

ساخته شده

'; } } new My_Plugin();

نکته مهم این است که $this در زمان ساخت شیء استفاده می‌شود. اگر متد را به‌صورت استاتیک صدا بزنید، باید نام کلاس را پاس دهید:

add_filter('the_content', array('My_Class', 'static_filter'), 10, 1);

در PHP 7.4 و بالاتر، می‌توان از تابع پیکانی (Arrow Function) استفاده کرد:

add_filter('the_content', fn($content) => $content . 'ساخته شده');

تابع پیکانی به‌طور خودکار متغیرهای بیرونی را جذب می‌کند و برای callbackهای کوتاه مناسب است. اما برای callbackهای پیچیده، استفاده از متد شیء توصیه می‌شود.

توابع بی‌نام و دام‌های حذف فیلتر

توابع بی‌نام (Anonymous Functions) و Closureها در add_filter رایج هستند، اما دام‌های خاص خود را دارند:

  • قابل حذف نیستند: همان‌طور که اشاره شد، توابع بی‌نام را نمی‌توان با remove_filter حذف کرد.
  • دسترسی به متغیرهای بیرونی: برای دسترسی به متغیرهای بیرون از Closure، باید از use استفاده کنید.
  • حافظه: هر Closure یک شیء است که حافظه مصرف می‌کند. در سایت‌هایی با هزاران فیلتر، این می‌تواند به مصرف بالای حافظه منجر شود.
$prefix = 'سایت من: ';

add_filter('the_content', function($content) use ($prefix) {
    return $prefix . $content;
});

استفاده از use مقدار متغیر را در زمان تعریف Closure جذب می‌کند، نه در زمان اجرا. اگر مقدار متغیر بعداً تغییر کند، Closure همچنان مقدار قدیمی را می‌بیند.

add_filter در قالب و child theme

در قالب‌ها، add_filter معمولاً در فایل functions.php قرار می‌گیرد. نکات مهم:

  • زمان اجرا: functions.php در زمان بارگذاری وردپرس اجرا می‌شود، بنابراین add_filter به‌موقع ثبت می‌شود.
  • Child Theme: در child theme، فایل functions.php والد و فرزند هر دو اجرا می‌شوند. بنابراین، child theme می‌تواند فیلترهای والد را حذف یا تغییر دهد.
  • ترتیب: فایل functions.php والد اول اجرا می‌شود، سپس فرزند. این یعنی child theme می‌تواند فیلترهای والد را با remove_filter حذف کند.
// در child theme
add_action('after_setup_theme', function() {
    remove_filter('the_content', 'parent_theme_filter', 10);
    add_filter('the_content', 'child_theme_filter', 10);
});

نکته کلیدی این است که remove_filter باید پس از ثبت فیلتر والد اجرا شود. استفاده از هوک after_setup_theme این تضمین را فراهم می‌کند. برای مطالعه بیشتر درباره child theme، مقاله قالب وردپرس چایلد چیست و چه زمانی به آن نیاز داریم را ببینید.

add_filter در توسعه افزونه

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

  • استفاده از کلاس: ثبت فیلترها در متد __construct یا یک متد init مشخص.
  • استفاده از هوک init: ثبت فیلترها در هوک init یا plugins_loaded برای اطمینان از ترتیب صحیح.
  • جدا کردن منطق: منطق فیلتر در متدهای جداگانه، نه در Closure.
  • مستندسازی: مستندسازی فیلترهای سفارشی با PHPDoc.
class My_Plugin {
    public function __construct() {
        add_action('init', array($this, 'register_filters'));
    }
    
    public function register_filters() {
        add_filter('the_content', array($this, 'filter_content'), 10, 1);
        add_filter('the_title', array($this, 'filter_title'), 10, 2);
    }
    
    public function filter_content($content) {
        return $content;
    }
    
    public function filter_title($title, $post_id) {
        return $title;
    }
}

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

دیباگ کردن فیلترها

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

  • استفاده از Query Monitor: این افزونه همه فیلترهای اجراشده را با priority و زمان اجرا نمایش می‌دهد.
  • استفاده از WP-CLI: دستور wp hook list همه فیلترهای ثبت‌شده را نمایش می‌دهد.
  • ثبت لاگ دستی: افزودن error_log() در callback برای مشاهده زمان اجرا.
  • بررسی مقدار بازگشتی: اطمینان از اینکه callback مقدار درست را بازمی‌گرداند.
add_filter('the_content', function($content) {
    error_log('the_content filter executed');
    error_log('Content length: ' . strlen($content));
    return $content;
}, 999);

استفاده از priority بالا (مانند ۹۹۹) تضمین می‌کند که این callback آخرین اجرا شود و مقدار نهایی را ببیند. برای مطالعه بیشتر، مقاله دیباگ کردن Action و Filter در وردپرس را ببینید.

ملاحظات امنیتی در استفاده از add_filter

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

  • پاک‌سازی ورودی: هر داده‌ای که از کاربر می‌آید، باید پاک‌سازی شود.
  • اعتبارسنجی خروجی: داده‌ای که بازمی‌گردانید، باید برای نمایش امن باشد.
  • عدم اعتماد به فیلترهای ناشناخته: افزونه‌های ناشناخته می‌توانند فیلترها را تغییر دهند.
  • اجتناب از eval و create_function: این توابع خطرناک هستند و در PHP 7.2 به بعد منسوخ شده‌اند.
  • استفاده از nonce: در فیلترهایی که به ورودی کاربر پاسخ می‌دهند، استفاده از nonce توصیه می‌شود.
add_filter('the_content', function($content) {
    // پاک‌سازی قبل از بازگشت
    return wp_kses_post($content);
}, 10, 1);

تابع wp_kses_post() تگ‌های HTML مجاز را نگه می‌دارد و بقیه را حذف می‌کند. این کار از حملات XSS جلوگیری می‌کند. برای مطالعه بیشتر، مقاله هوک‌های وردپرس و افزایش امنیت کد را ببینید.

تأثیر add_filter بر عملکرد سایت

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

  • تعداد فیلترها: هر افزونه فیلترهای متعددی ثبت می‌کند. در سایت‌هایی با ۵۰ افزونه، ممکن است بیش از ۱۰۰۰ فیلتر فعال باشد.
  • زمان اجرای callback: callbackهای سنگین می‌توانند زمان پاسخ را افزایش دهند.
  • تعداد آرگومان‌ها: پاس دادن آرگومان‌های زیاد، هزینه حافظه دارد.
  • استفاده از Closure: Closureها حافظه بیشتری مصرف می‌کنند.

برای بهینه‌سازی:

  • فیلترهای غیرضروری را حذف کنید.
  • callbackها را تا حد امکان سبک نگه دارید.
  • از priorityهای مناسب استفاده کنید تا callbackهای سنگین دیرتر اجرا شوند.
  • در سایت‌های پرترافیک، از object cache برای ذخیره نتایج فیلترهای سنگین استفاده کنید.
add_filter('the_content', function($content) {
    $cache_key = 'filtered_content_' . md5($content);
    $cached = wp_cache_get($cache_key, 'my_filters');
    
    if (false !== $cached) {
        return $cached;
    }
    
    $result = expensive_processing($content);
    wp_cache_set($cache_key, $result, 'my_filters', 3600);
    return $result;
});

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

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

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

  1. فراموش کردن return: رایج‌ترین اشتباه که به ناپدید شدن محتوا منجر می‌شود.
  2. اشتباه در نام فیلتر: اگر نام را اشتباه بنویسید، callback هرگز اجرا نمی‌شود.
  3. priority اشتباه: اجرای callback قبل یا بعد از زمانی که انتظار می‌رود.
  4. accepted_args کم: دسترسی نداشتن به آرگومان‌های مورد نیاز.
  5. حذف فیلتر با priority اشتباه: remove_filter بدون تطابق دقیق priority کار نمی‌کند.
  6. استفاده از Closure بدون امکان حذف: توابع بی‌نام قابل حذف نیستند.
  7. ثبت فیلتر در زمان نامناسب: ثبت فیلتر قبل از تعریف تابع یا کلاس.
  8. عدم پاک‌سازی خروجی: بازگرداندن داده ناامن به کاربر.
  9. استفاده از add_action به‌جای add_filter: اگر callback مقداری بازمی‌گرداند، باید از add_filter استفاده کرد.
  10. نادیده گرفتن ترتیب اجرا در سایت‌های چندافزونه‌ای: دو افزونه ممکن است فیلتر یکسان را با priority متفاوت تغییر دهند.

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

پرسش‌های پرتکرار درباره تابع add_filter

در این بخش، به پرسش‌های متداول درباره add_filter پاسخ داده می‌شود. این ساختار برای بهینه‌سازی محتوا برای موتورهای پاسخگو (Answer Engines) نیز مفید است.

تابع add_filter در وردپرس دقیقاً چه کاری انجام می‌دهد؟

add_filter یک callback را به یک فیلتر مشخص متصل می‌کند. هر بار که آن فیلتر با apply_filters فراخوانی شود، callback اجرا می‌شود و مقدار ورودی را پردازش و بازمی‌گرداند. این امکان را می‌دهد که بدون تغییر کد هسته، داده‌ها را تغییر دهید.

تفاوت add_filter و add_action چیست؟

add_filter برای تغییر داده طراحی شده و callback باید یک مقدار بازگرداند. add_action برای اجرای عملیات جانبی طراحی شده و مقدار بازگشتی نادیده گرفته می‌شود. در سطح داخلی، add_action یک پوشش برای add_filter است.

پارامتر priority در add_filter چه نقشی دارد؟

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

چرا فیلتر من اجرا نمی‌شود؟

دلایل احتمالی: نام فیلتر اشتباه است، priority اشتباه است، فیلتر ثبت نشده است، یا افزونه‌ای فیلتر شما را حذف کرده است. برای بررسی، از Query Monitor یا WP-CLI استفاده کنید.

چگونه یک فیلتر را حذف کنم؟

با تابع remove_filter. باید نام فیلتر، callback دقیق و priority را مشخص کنید. توابع بی‌نام قابل حذف نیستند.

آیا add_filter بر سرعت سایت اثر می‌گذارد؟

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

چگونه فیلتر سفارشی بسازم؟

با استفاده از apply_filters در کد خود. سپس سایر توسعه‌دهندگان می‌توانند با add_filter آن را تغییر دهند.

آیا می‌توانم به یک فیلتر چند callback متصل کنم؟

بله. هر تعداد callback می‌تواند به یک فیلتر متصل شود. آن‌ها به ترتیب priority اجرا می‌شوند و هر کدام مقدار خروجی callback قبلی را دریافت می‌کند.

چگونه مقدار بازگشتی فیلتر را ببینم؟

با افزودن error_log() در callback یا استفاده از Query Monitor. همچنین می‌توانید با priority بالا (مانند ۹۹۹) مقدار نهایی را ثبت کنید.

آیا add_filter در child theme کار می‌کند؟

بله. در child theme می‌توانید فیلترهای والد را حذف یا اضافه کنید. برای حذف، باید remove_filter را پس از ثبت فیلتر والد اجرا کنید.

تفاوت add_filter با add_action در استفاده از return چیست؟

در add_filter، return اجباری است و مقدار بازگشتی جایگزین مقدار قبلی می‌شود. در add_action، return اختیاری است و مقدار بازگشتی نادیده گرفته می‌شود.

آیا می‌توانم add_filter را در زمان اجرا حذف کنم؟

بله، با استفاده از remove_filter. این کار معمولاً در هوک‌هایی مانند init یا after_setup_theme انجام می‌شود تا از ترتیب صحیح اطمینان حاصل شود.

چگونه ترتیب اجرای فیلترها را کنترل کنم؟

با استفاده از priority. callbackهایی که priority کمتری دارند، زودتر اجرا می‌شوند. اگر دو callback priority یکسان داشته باشند، ترتیب ثبت آن‌ها تعیین‌کننده است، اما این ترتیب همیشه پایدار نیست.

آیا add_filter با PHP 8 سازگار است؟

بله. وردپرس مدرن با PHP 8 سازگار است. اما در استفاده از توابع بی‌نام و پارامترهای نوع‌دار باید دقت کرد.

چگونه فیلترهای یک افزونه را غیرفعال کنم؟

با remove_filter. باید نام فیلتر، callback و priority را از کد افزونه استخراج کنید. در موارد پیچیده، ممکن است نیاز به بازنویسی کد افزونه باشد.

نکات پیشرفته برای مهندسان ارشد

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

تحلیل عمیق WP_Hook و ساختار داده

در وردپرس ۴.۷ به بعد، کلاس WP_Hook مدیریت فیلترها را بر عهده دارد. ساختار داخلی آن:

class WP_Hook {
    public $callbacks = array();
    public $iterations = array();
    public $nest_level = 0;
    public $current_priority = array();
    public $doing_action = false;
}

خواص کلیدی:

  • $callbacks: آرایه‌ای از callbackها که با priority گروه‌بندی شده‌اند.
  • $iterations: آرایه‌ای از priorityهای پیمایش‌شده برای مدیریت تودرتویی.
  • $nest_level: عمق تودرتویی فعلی، برای مدیریت فیلترهایی که درون فیلترها اجرا می‌شوند.
  • $current_priority: priority فعلی در حال اجرا.
  • $doing_action: نشان می‌دهد که آیا در حال اجرای یک Action هستیم یا Filter.

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

فیلترهای تودرتو و مدیریت nest_level

یکی از مسائل پیشرفته، فیلترهایی هستند که درون فیلترهای دیگر اجرا می‌شوند. برای مثال، یک callback فیلتر ممکن است خودش apply_filters را فراخوانی کند. WP_Hook از خاصیت $nest_level برای مدیریت این وضعیت استفاده می‌کند.

add_filter('outer_filter', function($value) {
    // این فیلتر داخلی را فراخوانی می‌کند
    $value = apply_filters('inner_filter', $value);
    return $value;
}, 10);

در چنین مواقعی، WP_Hook از خاصیت $iterations برای پیگیری priorityهای پیمایش‌شده استفاده می‌کند. این مکانیزم از اجرای بی‌پایان جلوگیری می‌کند.

استفاده از did_filter و current_filter

وردپرس توابع کمکی برای بررسی وضعیت فیلتر ارائه می‌دهد:

// بررسی اینکه آیا فیلتر خاصی در حال اجراست
if (doing_filter('the_content')) {
    // ...
}

// دریافت نام فیلتر فعلی
$current = current_filter();

// بررسی تعداد اجرای یک فیلتر
$count = did_filter('the_content');

تابع did_filter() (معرفی‌شده در وردپرس ۶.۱) تعداد دفعاتی که یک فیلتر اجرا شده را برمی‌گرداند. این ابزار برای جلوگیری از اجرای چندباره یک callback مفید است:

add_filter('the_content', function($content) {
    if (did_filter('the_content') > 1) {
        return $content; // در اجرای دوم، کاری نکن
    }
    return expensive_processing($content);
});

مدیریت حافظه در سایت‌های چندافزونه‌ای

در سایت‌هایی با ۱۰۰ افزونه یا بیشتر، تعداد فیلترهای فعال می‌تواند به چند هزار برسد. هر فیلتر یک ورودی در $wp_filter دارد که حافظه مصرف می‌کند. برای پایش:

add_action('shutdown', function() {
    global $wp_filter;
    $total_callbacks = 0;
    foreach ($wp_filter as $tag => $hook) {
        $total_callbacks += count($hook->callbacks, COUNT_RECURSIVE);
    }
    error_log('Total callbacks: ' . $total_callbacks);
    error_log('Memory usage: ' . memory_get_peak_usage());
});

این کد تعداد کل callbackها و حافظه مصرفی را در error log ثبت می‌کند. اگر تعداد callbackها از ۵۰۰۰ فراتر رود، ممکن است به بهینه‌سازی نیاز باشد.

اولویت‌بندی و زمان‌بندی حرفه‌ای

در پروژه‌های حرفه‌ای، انتخاب priority درست حیاتی است. قاعده‌های توصیه‌شده:

محدوده priorityکاربرد
۱-۴آماده‌سازی داده اولیه، اعتبارسنجی
۵-۹پردازش اصلی
۱۰ (پیش‌فرض)تغییرات معمول
۱۱-۵۰تغییرات وابسته به پردازش قبلی
۵۰-۱۰۰نهایی‌سازی، فرمت‌دهی
۱۰۰+دیباگ، لاگ‌گیری، بررسی نهایی

این جدول یک راهنمای کلی است. در پروژه‌های واقعی، انتخاب priority باید بر اساس وابستگی‌های منطقی انجام شود.

جلوگیری از حلقه‌های بی‌پایان

یک فیلتر می‌تواند به‌طور ناخواسته حلقه بی‌پایان ایجاد کند اگر callback خودش فیلتر را دوباره فراخوانی کند. مثال:

add_filter('the_content', function($content) {
    // اشتباه: این کار حلقه بی‌پایان ایجاد می‌کند
    return apply_filters('the_content', $content . 'اضافه');
});

در چنین مواقعی، باید از remove_filter موقت استفاده کرد یا از یک flag برای جلوگیری از اجرای دوباره:

add_filter('the_content', function($content) {
    static $running = false;
    if ($running) {
        return $content;
    }
    $running = true;
    $result = apply_filters('my_custom_filter', $content);
    $running = false;
    return $result;
});

تست خودکار فیلترها

در پروژه‌های حرفه‌ای، فیلترها باید تست شوند. نمونه تست با PHPUnit:

class My_Filter_Test extends WP_UnitTestCase {
    public function test_content_filter() {
        add_filter('the_content', function($content) {
            return $content . ' تست';
        });
        
        $result = apply_filters('the_content', 'محتوا');
        $this->assertEquals('محتوا تست', $result);
    }
}

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

الگوهای طراحی پیشرفته

در پروژه‌های بزرگ، از الگوهای طراحی خاص برای مدیریت فیلترها استفاده می‌شود:

  • Registry Pattern: ثبت فیلترها در یک کلاس مرکزی برای مدیریت بهتر.
  • Strategy Pattern: استفاده از فیلترها برای انتخاب استراتژی در زمان اجرا.
  • Decorator Pattern: استفاده از زنجیره فیلترها برای افزودن قابلیت‌ها.

نمونه Registry Pattern:

class Filter_Registry {
    private $filters = array();
    
    public function register($tag, $callback, $priority = 10, $args = 1) {
        $this->filters[] = compact('tag', 'callback', 'priority', 'args');
    }
    
    public function apply() {
        foreach ($this->filters as $f) {
            add_filter($f['tag'], $f['callback'], $f['priority'], $f['args']);
        }
    }
}

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

نکات کلیدی برای پایداری بلندمدت

در پایان، چند نکته کلیدی که در استفاده از add_filter باید در خاطر بماند:

  • همیشه return کنید: حتی اگر مقدار را تغییر نمی‌دهید.
  • priority را آگاهانه انتخاب کنید: نه به‌صورت تصادفی.
  • accepted_args را درست تنظیم کنید: به تعداد آرگومان‌های واقعی.
  • از remove_filter با دقت استفاده کنید: باید نام، callback و priority دقیق باشند.
  • توابع بی‌نام را در متغیر ذخیره کنید: اگر نیاز به حذف دارید.
  • خروجی را پاک‌سازی کنید: به‌ویژه اگر داده از کاربر می‌آید.
  • از کش استفاده کنید: برای فیلترهای سنگین.
  • فیلترهای سفارشی بسازید: برای افزونه‌هایی که می‌خواهید توسعه‌پذیر باشند.
  • تست خودکار بنویسید: برای فیلترهای حیاتی.
  • تعداد فیلترها را پایش کنید: به‌ویژه در سایت‌های چندافزونه‌ای.

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

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