تابع get_posts() در وردپرس یک راه سریع و سبک برای دریافت آرایه‌ای از نوشته‌ها بدون نیاز به پیاده‌سازی حلقه کامل است. این تابع در واقع یک wrapper نازک روی کلاس WP_Query محسوب می‌شود که تنظیمات پیش‌فرض را برای کاربردهای سریع تغییر می‌دهد.

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

در بسیاری از پروژه‌هایی که نیاز به لیست سریع، بدون حلقه اصلی و بدون تغییر وضعیت global داشتند، این تابع گزینه اول بوده است. هرچند ساده به‌نظر می‌رسد، تفاوت‌های ظریف آن با WP_Query در تنظیمات پیش‌فرض می‌تواند اثر جدی روی رفتار قالب داشته باشد.

چرا get_posts در توسعه وردپرس اهمیت دارد

وردپرس برای هر نوع لیست محتوا یک ابزار متفاوت دارد: WP_Query برای حلقه اصلی و کوئری‌های پیچیده، get_posts برای دریافت سریع آرایه‌ای از پست‌ها، get_pages برای برگه‌ها و wp_get_recent_posts برای موارد خاص. این تنوع باعث می‌شود برای هر سناریو، ابزار مناسب‌تر انتخاب شود.

get_posts() در سناریوهایی که به حلقه نیازی نیست، به‌شدت کارآمد است. مثلاً وقتی می‌خواهید فقط شناسه پست‌ها را بگیرید یا در یک ابزار مدیریتی، یک لیست ساده نمایش دهید، استفاده از WP_Query و حلقه کامل، پیچیدگی غیرضروری ایجاد می‌کند.

نکته مهم دیگر این است که get_posts() به‌طور پیش‌فرض متغیر سراسری $post را دست‌کاری نمی‌کند چون حلقه‌ای اجرا نمی‌شود. این یعنی نیازی به wp_reset_postdata() نیست. این رفتار برای کوئری‌های درون قالب‌ها مزیت مهمی است.

برای درک ساختار پایه‌ای این کلاس، مطلب کلاس WP_Query چطور کار می‌کند را مطالعه کنید.

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

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

get_posts( array $args = null ): array

پارامتر ورودی یک آرایه انجمنی از پارامترهاست. خروجی همیشه یک آرایه است — چه خالی و چه پر — و هیچ‌گاه null یا WP_Error برنمی‌گرداند. همین موضوع کار با آن را ساده‌تر می‌کند اما خطاهای پنهان را سخت‌تر می‌سازد.

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

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

پارامترهای این تابع تقریباً با WP_Query یکسان هستند، اما چند تنظیم پیش‌فرض مهم دارد:

مقادیر پیش‌فرض مهم

برخلاف WP_Query، این تابع این مقادیر را به‌طور پیش‌فرض تنظیم می‌کند:

  • post_type => 'post'
  • post_status => 'publish'
  • posts_per_page => 5
  • orderby => 'date'
  • order => 'DESC'
  • suppress_filters => true
  • no_found_rows => true

مقدار posts_per_page برابر 5 به‌صورت پیش‌فرض ممکن است غافلگیرکننده باشد. اگر به تعداد بیشتری نیاز دارید، باید صریحاً آن را تنظیم کنید:

$latest = get_posts( array(
    'posts_per_page' => 10,
) );

پارامتر post_type

مشخص می‌کند چه نوع محتوایی دریافت شود. برای post type سفارشی، نام ثبت‌شده را بدهید:

$products = get_posts( array(
    'post_type'      => 'product',
    'posts_per_page' => 20,
) );

برای آشنایی کامل با ثبت post type سفارشی، مطلب تابع register_post_type را ببینید.

پارامتر fields

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

$ids = get_posts( array(
    'fields'         => 'ids',
    'posts_per_page' => 100,
) );

مقادیر مجاز: all، ids، id=>parent و id=>name. استفاده از ids در سایت‌های بزرگ می‌تواند مصرف حافظه را چند برابر کاهش دهد.

پارامتر category و tag

فیلتر بر اساس دسته و برچسب:

$news = get_posts( array(
    'category_name'  => 'news',
    'posts_per_page' => 5,
) );

برای دسته‌بندی سفارشی، از tax_query استفاده کنید و برای دریافت termها، مطلب تابع get_terms را مطالعه کنید.

پارامتر meta_query

فیلتر بر اساس متادیتا:

$featured = get_posts( array(
    'meta_query' => array(
        array(
            'key'   => 'featured',
            'value' => '1',
        ),
    ),
) );

در سایت‌های بزرگ، هر شرط اضافه در meta_query یک JOIN به wp_postmeta اضافه می‌کند. الگوهای بهینه در مطلب بهینه‌سازی WP_Query پوشش داده شده است.

پارامتر include و exclude

برای دریافت یا حذف شناسه‌های خاص:

$related = get_posts( array(
    'post__in'  => array( 5, 12, 27 ),
    'orderby'   => 'post__in',
) );

ترکیب post__in با orderby => 'post__in' باعث می‌شود ترتیب آرایه در خروجی حفظ شود. این الگو در بخش محصولات مرتبط و مقالات پیشنهادی بسیار پرکاربرد است.

پارامتر numberposts

یک نام قدیمی برای posts_per_page. هر دو کار می‌کنند، اما توصیه می‌شود از posts_per_page استفاده شود چون یکدست‌تر است.

پارامتر suppress_filters

به‌طور پیش‌فرض در این تابع true است. این یعنی فیلترهایی که افزونه‌ها روی کوئری اعمال می‌کنند، در get_posts نادیده گرفته می‌شوند. اگر به رفتار مشابه حلقه اصلی نیاز دارید، باید آن را false کنید:

$posts = get_posts( array(
    'suppress_filters' => false,
) );

این تنظیم در برخی افزونه‌های چندزبانه مثل WPML تفاوت رفتاری ایجاد می‌کند، چون آن‌ها از فیلترها برای محدود کردن زبان استفاده می‌کنند.

تفاوت get_posts با WP_Query

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

ویژگیget_postsWP_Query
خروجیآرایه اشیاشیء query
حلقهندارددارد
wp_reset_postdataنیاز نیستالزامی
total_postsندارددارد
suppress_filtersپیش‌فرض trueپیش‌فرض false
no_found_rowsپیش‌فرض trueپیش‌فرض false

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

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

آخرین نوشته‌های یک نویسنده

$author_posts = get_posts( array(
    'author'         => 5,
    'posts_per_page' => 10,
) );

برای الگوهای مرتبط با کاربران و نویسندگان، مطلب تابع get_users را ببینید.

دریافت شناسه محصولات یک دسته

$product_ids = get_posts( array(
    'post_type'      => 'product',
    'posts_per_page' => -1,
    'fields'         => 'ids',
    'tax_query'      => array(
        array(
            'taxonomy' => 'product_cat',
            'field'    => 'slug',
            'terms'    => 'electronics',
        ),
    ),
) );

استفاده از fields => 'ids' در این الگو، مصرف حافظه را بسیار کم‌تر می‌کند. برای taxonomy سفارشی، مطلب تابع register_taxonomy مرجع خوبی است.

گرفتن چند پست مشخص با ترتیب دلخواه

$handpicked = get_posts( array(
    'post__in'  => array( 10, 25, 42 ),
    'orderby'   => 'post__in',
    'post_type' => 'post',
) );

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

استفاده در ابزار مدیریتی سفارشی

در صفحات پنل مدیریت، معمولاً نیازی به حلقه نیست و get_posts انتخاب سریع‌تر است. فقط یادتان باشد که خروجی را با esc_html() و esc_url() در HTML escape کنید.

درج یا به‌روزرسانی پس از کوئری

اگر پس از دریافت پست‌ها نیاز به درج یا به‌روزرسانی دارید، از تابع wp_insert_post و تابع wp_update_post استفاده کنید. این دو تابع به‌طور خودکار hookها را اجرا می‌کنند.

ترکیب با wpdb برای گزارش‌های سنگین

در گزارش‌های پیچیده که به aggregation و JOINهای دستی نیاز دارید، از get_posts عبور کنید و به کدنویسی کوئری‌های سفارشی روی بیاورید. در این مسیر، امنیت کوئری را با SQL Injection Prevention در وردپرس تضمین کنید.

دریافت پست برای نمایش در ویجت

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

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

نبود بررسی آرایه خالی

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

$posts = get_posts( $args );
if ( empty( $posts ) ) {
    echo '<p>' . esc_html__( 'موردی یافت نشد.', 'my-textdomain' ) . '</p>';
    return;
}

نبود محدودیت در posts_per_page

مقدار پیش‌فرض 5 است، اما اگر آن را به -1 تغییر دهید، تمام رکوردها بارگذاری می‌شوند. این کار در سایت‌های بزرگ به خطای 500 منجر می‌شود.

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

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

عدم تنظیم suppress_filters در سایت‌های چندزبانه

در سایت‌هایی که از WPML، Polylang یا افزونه‌های مشابه استفاده می‌کنند، suppress_filters => true باعث می‌شود فیلتر زبان اعمال نشود و نتیجه ترکیبی از همه زبان‌ها برگردد. برای رفع این مشکل، مقدار را false بگذارید.

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

هرچند no_found_rows => true هزینه شمارش را حذف می‌کند، اما اجرای مکرر کوئری‌های سنگین همچنان هزینه‌بر است. برای نتایج ثابت، از تابع wp_cache_set و تابع wp_cache_get استفاده کنید.

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

اگر نتیجه این تابع را در یک فرم سفارشی نمایش می‌دهید، حتماً Nonce در وردپرس را در آن فرم قرار دهید.

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

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

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

این تابع به‌طور داخلی از prepared statement استفاده می‌کند و در برابر SQL Injection مقاوم است. اما همچنان نکات زیر را رعایت کنید:

  • پارامترهای ورودی از URL یا فرم را با sanitize_text_field() و absint() پاک کنید
  • خروجی HTML را با esc_html() و esc_url() escape کنید
  • سطح دسترسی کاربر را با current_user_can() بررسی کنید
  • در endpointهای عمومی، از افشای محتوای خصوصی خودداری کنید

از نظر عملکرد، این تابع ذاتاً سریع‌تر از WP_Query در موارد بدون صفحه‌بندی است چون شمارش کل رکوردها را انجام نمی‌دهد. اما در کوئری‌های دارای meta_query یا tax_query پیچیده، همان مسائل بهینه‌سازی مطرح است.

  1. تعداد شرط‌ها را به حداقل برسانید
  2. در صورت امکان از fields => 'ids' استفاده کنید
  3. نتایج ثابت را در cache ذخیره کنید

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

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

تفاوت get_posts با query_posts چیست؟

query_posts() یک تابع قدیمی و توصیه‌نشده است که حلقه اصلی را بازنویسی می‌کند و به مشکلات جدی منجر می‌شود. هرگز از آن استفاده نکنید. برای تغییر حلقه اصلی از هوک pre_get_posts و برای لیست سفارشی از get_posts یا WP_Query استفاده کنید.

آیا get_posts فیلترهای افزونه‌ها را اعمال می‌کند؟

به‌طور پیش‌فرض خیر، چون suppress_filters => true تنظیم شده است. اگر به رفتار مشابه حلقه اصلی نیاز دارید، آن را false کنید.

چرا get_posts بیشتر از 5 پست برنمی‌گرداند؟

چون مقدار پیش‌فرض posts_per_page در این تابع برابر 5 است. برای تعداد بیشتر، مقدار را صریحاً تنظیم کنید.

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

بله، با ترکیب meta_key و orderby => 'meta_value_num'. اما توجه داشته باشید که در سایت‌های بزرگ این الگو می‌تواند کند باشد چون روی wp_postmeta.meta_value ایندکس پیش‌فرض وجود ندارد.

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

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

چرا get_posts از WP_Query سریع‌تر است؟

چون به‌طور پیش‌فرض no_found_rows => true تنظیم شده و کوئری SELECT COUNT(*) جداگانه اجرا نمی‌شود. اگر این پارامتر را در WP_Query هم بگذارید، تفاوت عملکردی ناچیز می‌شود.

آیا می‌توان از get_posts برای ساخت حلقه استفاده کرد؟

فنی ممکن است اما توصیه نمی‌شود چون به setup_postdata() و wp_reset_postdata() نیاز دارد و پیچیدگی اضافه ایجاد می‌کند. برای حلقه، WP_Query انتخاب درست است.

نگاه فنی عمیق به get_posts

در سطح پیاده‌سازی، get_posts() در هسته وردپرس تنها چند خط کد است: یک شیء WP_Query می‌سازد و مقدار posts را برمی‌گرداند. تمام پردازش‌های واقعی در همان کلاس انجام می‌شود. تفاوت واقعی در تنظیمات پیش‌فرض است که در بخش parse_query اعمال می‌شود.

نکته ظریف اول این است که get_posts مقدار suppress_filters => true می‌گذارد. اگرچه این تصمیم برای اهداف historical بوده، اما در اکوسیستم امروزی وردپرس که اکثر افزونه‌های مهم با فیلترهای کوئری کار می‌کنند، این رفتار می‌تواند باعث ناسازگاری شود. به همین دلیل بسیاری از توسعه‌دهندگان حرفه‌ای، get_posts را برای سناریوهای حساس توصیه نمی‌کنند و مستقیماً از WP_Query با تنظیمات صریح استفاده می‌کنند.

نکته دوم این است که خروجی get_posts یک آرایه ساده از اشیای WP_Post است و هیچ property اضافی مثل total_posts ندارد. این یعنی اگر به شمارش کل نتایج نیاز دارید، باید حتماً از WP_Query استفاده کنید یا یک کوئری جداگانه با get_var بنویسید.

نکته سوم، مسئله cache داخلی وردپرس است. هرچند get_posts از همان cache WP_Query استفاده می‌کند، اما پارامترهای پیش‌فرض خاص آن باعث می‌شود برای کوئری‌های مشابه با WP_Query، کلید cache متفاوتی استفاده شود. یعنی اگر یک کوئری را با get_posts و دیگری را با WP_Query بزنید، احتمال cache hit نزدیک به صفر است.

در نهایت، در پروژه‌های Enterprise توصیه می‌کنم برای هر سناریو یک تابع wrapper اختصاصی بنویسید که پارامترهای پیش‌فرض مناسب آن سناریو را تنظیم کند. این کار از تکرار تصمیم‌ها جلوگیری می‌کند و وضوح کد را افزایش می‌دهد. برای مطالعه بیشتر در مورد ساختارهای حرفه‌ای کوئری، مطلب توابع وردپرس برای کوئری سفارشی و دیباگ عملکرد WP_Query مفید است. همچنین برای درک عمیق‌تر لایه دیتابیس، زبان SQL در ویکی‌پدیا نقطه شروع خوبی است.

اگر در پروژه‌ای با تفاوت رفتار بین get_posts و WP_Query مواجه شده‌اید یا سناریوی خاصی داشته‌اید که انتخاب شما را تغییر داده، برای ما جالب است بدانید چه چیزی در تصمیم‌گیری مؤثر بوده است. تجربه خود را در دیدگاه‌ها بنویسید تا برای سایر توسعه‌دهندگان هم مفید باشد.