تابع get_terms() در وردپرس ابزار اصلی برای دریافت فهرست دسته‌ها، برچسب‌ها و هر نوع term در taxonomyهای مختلف است. این تابع در کنار WP_Term_Query، لایه‌ای انعطاف‌پذیر برای فیلتر، مرتب‌سازی و شمارش termها فراهم می‌کند و در هر قالب یا افزونه‌ای که با taxonomy سروکار دارد، حضور پررنگی دارد.

تابع get_terms یکی از پرکاربردترین توابع وردپرس برای دریافت ترم‌های taxonomy است. این تابع امکان فیلتر بر اساس taxonomy، والد، شمارش، ترتیب و مخفی کردن ترم‌های خالی را فراهم می‌کند و پایه ساخت هر نوع لیست دسته‌بندی محسوب می‌شود. در این راهنما ساختار کامل، پارامترها، نمونه‌های واقعی، اشتباهات رایج و نکات امنیتی و عملکردی این تابع بررسی می‌شود. همچنین تفاوت آن با get_categories و WP_Term_Query توضیح داده می‌شود. در پایان پرسش‌های پرتکرار و نگاه مهندسی سطح بالای این تابع مرور خواهد شد.

در پروژه‌های واقعی، از ساخت منوهای دسته‌بندی تا فیلترهای پیچیده محصول، تقریباً همیشه این تابع در مرکز تصمیم‌گیری بوده است. آنچه در نگاه اول یک query ساده به‌نظر می‌رسد، در جزئیات خود مسائلی مثل hide_empty، parent، meta_query و cache دارد که بی‌توجهی به آن‌ها در سطح production به افت کارایی و حتی به خطاهای منطقی منجر می‌شود.

چرا get_terms اهمیت دارد

وردپرس از یک سیستم طبقه‌بندی انعطاف‌پذیر به نام taxonomy پشتیبانی می‌کند. دسته‌ها و برچسب‌ها نمونه‌های پیش‌فرض هستند، اما توسعه‌دهندگان می‌توانند taxonomyهای سفارشی نیز بسازند. تمام این‌ها در جدول‌های wp_terms، wp_term_taxonomy و wp_term_relationships ذخیره می‌شوند.

تابع get_terms() یک abstraction روی همین ساختار پیچیده است و به شما اجازه می‌دهد بدون نوشتن JOINهای چندگانه، فهرست termها را با فیلترهای متنوع بدست آورید. از آنجا که این تابع به‌طور پیش‌فرض از cache داخلی وردپرس استفاده می‌کند، در بسیاری از سناریوها عملکرد مناسبی دارد.

برای درک بهتر ساختار taxonomy سفارشی، مطلب تابع register_taxonomy را مطالعه کنید.

ساختار و امضای تابع get_terms

امضای این تابع به شکل زیر است:

get_terms( array|string $args = array(), array|string $deprecated = '' ): array|WP_Error

پارامتر اول یک آرایه انجمنی یا رشته است که شامل تمام تنظیمات query است. پارامتر دوم از نسخه‌های قدیمی باقی مانده و امروزه توصیه نمی‌شود. خروجی یک آرایه از اشیای WP_Term است یا در صورت خطا یک شیء WP_Error.

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

پارامترهای کلیدی و کاربرد هرکدام

پارامترهای این تابع بسیار متنوع هستند. مهم‌ترین آن‌ها را مرور می‌کنیم:

پارامتر taxonomy

نام taxonomy یا آرایه‌ای از نام‌ها. اگر مقدار پیش‌فرض (خالی) را بگذارید، تمام taxonomyهای عمومی بازگردانده می‌شوند که معمولاً هدف مطلوبی نیست:

$categories = get_terms( array(
    'taxonomy'   => 'category',
    'hide_empty' => false,
) );

برای taxonomyهای سفارشی، مقدار همان slug ثبت‌شده در register_taxonomy است.

پارامتر hide_empty

اگر true باشد (پیش‌فرض)، فقط termهایی برگردانده می‌شوند که حداقل یک پست مرتبط دارند. اگر false باشد، تمام termها بدون توجه به تعداد پست برمی‌گردند. در طراحی منوها، استفاده از false رایج است چون ممکن است برخی termها هنوز منتشر نشده باشند:

get_terms( array(
    'taxonomy'   => 'product_cat',
    'hide_empty' => false,
) );

پارامتر orderby و order

مقادیر مجاز برای orderby شامل name، slug، term_id، description، count، include، slug__in و term_group است. برای مرتب‌سازی بر اساس تعداد پست‌های مرتبط، از count استفاده کنید:

get_terms( array(
    'taxonomy'   => 'category',
    'orderby'    => 'count',
    'order'      => 'DESC',
) );

پارامتر parent

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

$children = get_terms( array(
    'taxonomy' => 'category',
    'parent'   => 5,
) );

برای ساخت منوهای درختی، این پارامتر در ترکیب با حلقه بازگشتی (recursive loop) استفاده می‌شود.

پارامتر number

محدود کردن تعداد termهای بازگشتی. مقدار پیش‌فرض 0 یا '' به معنی بدون محدودیت است. برای سایت‌هایی با صدها دسته، محدودسازی توصیه می‌شود:

get_terms( array(
    'taxonomy' => 'post_tag',
    'number'   => 20,
) );

پارامتر fields

شکل خروجی را کنترل می‌کند. مقادیر مجاز شامل all، ids، names، slugs، count، id=>parent، id=>slug و id=>name است. استفاده از fields => 'ids' می‌تواند مصرف حافظه را به‌شدت کاهش دهد:

$term_ids = get_terms( array(
    'taxonomy' => 'category',
    'fields'   => 'ids',
) );

پارامتر meta_query

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

get_terms( array(
    'taxonomy'   => 'product_cat',
    'meta_query' => array(
        array(
            'key'   => 'featured',
            'value' => '1',
        ),
    ),
) );

برای ثبت و مدیریت متادیتای term، مطلب تابع register_meta را ببینید.

پارامتر name__like و slug__in

برای جستجوی نام یا فیلتر بر اساس آرایه‌ای از slugها:

get_terms( array(
    'taxonomy' => 'category',
    'slug'     => array( 'news', 'reviews' ),
) );

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

نمونه‌های عملی در پروژه واقعی

در ادامه چند الگوی عملی که در پروژه‌های واقعی بارها به آن‌ها برخورده‌ام را مرور می‌کنیم:

ساخت منوی دسته‌بندی به‌صورت درختی

function render_term_tree( $parent = 0 ) {
    $terms = get_terms( array(
        'taxonomy'   => 'category',
        'parent'     => $parent,
        'hide_empty' => false,
    ) );
    if ( empty( $terms ) || is_wp_error( $terms ) ) {
        return;
    }
    echo '
    '; foreach ( $terms as $term ) { printf( '
  • %s', esc_url( get_term_link( $term ) ), esc_html( $term->name ) ); render_term_tree( $term->term_id ); echo '
  • '; } echo '
'; }

نکته مهم در این الگو، بررسی is_wp_error و escape کردن خروجی است. بدون این دو، کد در برابر خطا و XSS آسیب‌پذیر خواهد بود.

دریافت پرکاربردترین برچسب‌ها

$top_tags = get_terms( array(
    'taxonomy'   => 'post_tag',
    'orderby'    => 'count',
    'order'      => 'DESC',
    'number'     => 10,
    'hide_empty' => true,
) );

نمایش دسته‌های یک نوشته

برای دریافت دسته‌های مرتبط با یک پست خاص، استفاده از wp_get_post_terms یا get_the_terms راحت‌تر است. اما اگر می‌خواهید این کار را با get_terms انجام دهید، از پارامتر object_ids استفاده کنید:

$terms = get_terms( array(
    'taxonomy'   => 'category',
    'object_ids' => $post_id,
) );

برای الگوی مشابه روی ترم‌های مرتبط با یک آبجکت، مطلب تابع wp_get_object_terms را ببینید.

دریافت termهای یک والد با شمردن فرزندان

$parent_terms = get_terms( array(
    'taxonomy'   => 'product_cat',
    'parent'     => 0,
    'hide_empty' => false,
) );
foreach ( $parent_terms as $parent ) {
    $children = get_terms( array(
        'taxonomy' => 'product_cat',
        'parent'   => $parent->term_id,
    ) );
    // محاسبه تعداد کل
}

بررسی وجود یک term قبل از درج

اگر می‌خواهید قبل از درج یک term جدید، بررسی کنید که آیا از قبل وجود دارد، الگوی زیر مفید است:

$existing = get_terms( array(
    'taxonomy' => 'category',
    'slug'     => 'my-new-slug',
    'hide_empty' => false,
) );
if ( empty( $existing ) ) {
    wp_insert_term( 'نام جدید', 'category', array( 'slug' => 'my-new-slug' ) );
}

برای جزئیات بیشتر در مورد درج و به‌روزرسانی term، مطالب تابع wp_insert_term، تابع wp_update_term و تابع wp_delete_term را مطالعه کنید.

ترکیب با wp_set_object_terms برای اتصال

پس از دریافت term موردنظر، برای اتصال آن به یک پست، از تابع wp_set_object_terms استفاده کنید.

نمایش دسته‌بندی در قالب‌های سفارشی

در ساخت قالب taxonomy سفارشی، مطلب قالب اختصاصی تاکسونومی راهنمای دقیقی است.

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

در بررسی کدبیس پروژه‌های مختلف، این اشتباهات به‌طور تکراری دیده شده است:

نبود بررسی is_wp_error

اگر taxonomy نامعتبر باشد یا خطایی رخ دهد، get_terms() یک شیء WP_Error برمی‌گرداند. اگر مستقیماً روی آن foreach بزنید، خطای PHP رخ می‌دهد. همیشه ابتدا بررسی کنید:

$terms = get_terms( $args );
if ( is_wp_error( $terms ) || empty( $terms ) ) {
    return;
}

نبود escape در خروجی HTML

نام و توضیحات termها می‌تواند توسط مدیر سایت دست‌کاری شود. هنگام چاپ در HTML، همیشه از esc_html() و esc_url() استفاده کنید.

استفاده بدون hide_empty مناسب

در برخی پروژه‌ها فراموش می‌شود که مقدار پیش‌فرض hide_empty برابر true است. اگر می‌خواهید تمام termها را ببینید، صریحاً آن را روی false تنظیم کنید.

نبود محدودیت در سایت‌های پرterm

در فروشگاهی با هزاران دسته محصول، فراخوانی بدون محدودیت می‌تواند حجم زیادی از حافظه PHP را مصرف کند. همیشه number و fields را محدود کنید.

نبود cache برای کوئری‌های پرتکرار

هرچند وردپرس به‌طور داخلی نتیجه را کش می‌کند، اما اگر با پارامترهای متغیر زیاد فراخوانی کنید، cache داخلی مؤثر نیست. برای نتایج پرتکرار، از Transient یا Object Cache استفاده کنید. مطلب تابع wp_cache_set راهنمای این کار است.

نبود تست روی سناریوهای مرزی

تست‌هایی مثل «taxonomy نامعتبر»، «term خالی»، «والد ناموجود» و «term با نام یونیکد» را حتماً بنویسید. در محیط production این سناریوها بدون تست به خطاهای پنهان تبدیل می‌شوند.

امنیت و عملکرد در get_terms

این تابع به‌طور داخلی از $wpdb->prepare استفاده می‌کند و در برابر SQL Injection مقاوم است. اما نکات زیر را رعایت کنید:

  • پارامترهای ورودی را با sanitize_key() یا sanitize_text_field() پاک کنید
  • خروجی HTML را با esc_html() و esc_url() escape کنید
  • در endpointهای عمومی، از افشای taxonomyهای حساس خودداری کنید
  • در پنل مدیریت، سطح دسترسی را با capability بررسی کنید

برای مطالعه جامع مباحث امنیتی، مطلب SQL Injection Prevention در وردپرس مرجع اصلی است.

از نظر عملکرد، چند نکته کلیدی:

  1. در سایت‌های بزرگ، حتماً fields را محدود کنید
  2. در صورت امکان، از cache داخلی وردپرس بهره بگیرید
  3. کوئری‌های تکراری را در Transient ذخیره کنید
  4. در حلقه‌های بازگشتی، تعداد فراخوانی‌ها را کنترل کنید

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

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

تفاوت get_terms با get_categories چیست؟

get_categories() فقط برای taxonomy پیش‌فرض دسته‌بندی است و به‌طور خودکار برخی پارامترهای خاص مثل hide_empty را تنظیم می‌کند. get_terms() عمومی‌تر است و روی هر taxonomy سفارشی کار می‌کند.

آیا get_terms از cache استفاده می‌کند؟

بله، وردپرس به‌طور داخلی نتایج را در wp_cache گروه terms ذخیره می‌کند. اگر query پارامترهای متغیر داشته باشد، cache داخلی کمتر مؤثر خواهد بود.

چرا get_terms ترم‌های خالی را برنمی‌گرداند؟

پیش‌فرض hide_empty = true است. برای دریافت تمام termها، این مقدار را روی false تنظیم کنید.

آیا می‌توان بر اساس متادیتای term فیلتر کرد؟

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

آیا get_terms روی Multisite کار می‌کند؟

بله، اما فقط روی سایت جاری. برای دریافت termهای شبکه، باید با switch_to_blog به هر سایت سوئیچ کنید.

آیا می‌توان ترتیب termها را بر اساس فیلدهای سفارشی تنظیم کرد؟

ترتیب پیش‌فرض بر اساس name، slug، term_id و count است. برای ترتیب سفارشی، معمولاً باید از meta_key و orderby => 'meta_value' استفاده کنید.

آیا get_terms در نسخه‌های جدید وردپرس تغییر کرده است؟

بله، از نسخه 4.5 امضای پارامترها کاملاً آرایه‌ای شد. همچنین از نسخه 4.4 امکان meta_query اضافه شد. در نسخه‌های جدیدتر، پشتیبانی از object_ids و slug__in نیز اضافه شده است.

نگاه مهندسی سطح بالا

در سطح معماری، get_terms() یک wrapper روی WP_Term_Query است. در واقع این تابع ابتدا یک شیء query می‌سازد، سپس آن را اجرا می‌کند و صرفاً آرایه termها را برمی‌گرداند. اگر به آمار کوئری، شمارش دقیق یا فیلترهای پیشرفته‌تر نیاز دارید، باید مستقیماً از WP_Term_Query استفاده کنید چون propertyهای اضافی مثل total_terms و sql_clauses را در اختیار شما می‌گذارد.

نکته ظریف اول، اثر orderby => 'count' روی کوئری SQL است. وردپرس برای این کار از یک subquery استفاده می‌کند که روی جداول بزرگ می‌تواند کند باشد. اگر مرتب‌سازی پرتکرار بر اساس count دارید، بهتر است مقدار count را در متادیتای term ذخیره کنید و با meta_query مرتب‌سازی کنید.

نکته دوم، مسئله hide_empty است. برخلاف تصور عمومی، مقدار true باعث حذف termهای بدون پست می‌شود اما کوئری SQL همچنان روی جدول wp_term_relationships JOIN می‌زند. در دیتابیس‌هایی با میلیون‌ها ردیف در این جدول، این JOIN هزینه قابل توجهی دارد.

نکته سوم، cache داخلی وردپرس است. اگر از Redis یا Memcached استفاده می‌کنید، نتایج این تابع به‌طور خودکار در cache ذخیره می‌شود. اما اگر پارامترهای query شما متنوع و پویا باشند، ضریب hit cache به‌شدت افت می‌کند و در عمل هر بار یک کوئری جدید اجرا می‌شود. راهکار استاندارد، hash کردن پارامترها و ذخیره نتایج در گروهی جداگانه است.

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

اگر در پروژه‌ای با مشکل کندی این تابع یا ناسازگاری taxonomyهای سفارشی مواجه شده‌اید، برای من جالب است بدانید کدام راهکار (کش، بازطراحی کوئری، متادیتای اضافه) عملاً مؤثر بوده است. تجربه خود را در دیدگاه‌ها بنویسید تا برای سایر توسعه‌دهندگان هم مفید باشد.