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

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

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

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

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

  • یک مقدار منحصر (وقتی single = true)
  • یک آرایه از مقادیر (وقتی single = false)
  • یک رشته خالی (اگر کلید وجود نداشته باشد و single = true)
  • یک آرایه خالی (اگر کلید وجود نداشته باشد و single = false)

این تابع به‌طور داخلی از Object Cache استفاده می‌کند. یعنی اولین خواندن یک کوئری به دیتابیس می‌زند اما در خواندن‌های بعدی، از حافظه می‌خواند. همین رفتار، آن را به یکی از سریع‌ترین توابع تبدیل می‌کند.

برای مطالعه توابع مرتبط، مطالب تابع update_post_meta، تابع delete_post_meta و تابع register_meta را ببینید.

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

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

get_post_meta( int $post_id, string $key = '', bool $single = false ): mixed

خروجی این تابع بسته به پارامترها متفاوت است:

  • اگر single = true: یک مقدار منحصر (string یا هر نوع داده ذخیره‌شده)
  • اگر single = false: یک آرایه از مقادیر
  • اگر key خالی باشد: آرایه‌ای از تمام متادیتای پست

پارامترها و نقش single

پارامتر post_id

شناسه پستی که متادیتا به آن تعلق دارد. باید یک عدد صحیح مثبت باشد. اگر post_id معتبر نباشد یا پست وجود نداشته باشد، تابع مقدار خالی یا آرایه خالی برمی‌گرداند:

$price = get_post_meta( 42, 'myplugin_price', true );

پارامتر key

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

$all_meta = get_post_meta( $post_id );
// نتیجه: آرایه‌ای از تمام meta_keyها

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

پارامتر single

مهم‌ترین پارامتر رفتار این تابع. مقدار پیش‌فرض false است:

// single = false (پیش‌فرض)، آرایه برمی‌گرداند
$values = get_post_meta( $post_id, 'myplugin_field', false );
// نتیجه: array( 0 => 'value' )

// single = true، یک مقدار منحصر
$value = get_post_meta( $post_id, 'myplugin_field', true );
// نتیجه: 'value'

در بیشتر سناریوها، single = true راحت‌تر است چون مستقیماً مقدار را برمی‌گرداند. اما اگر یک کلید چند مقدار داشته باشد، باید از single = false استفاده کنید.

تفاوت رفتار با مقادیر چندگانه

اگر با add_post_meta چند مقدار برای یک کلید ذخیره کرده باشید:

add_post_meta( $post_id, 'myplugin_tag', 'tag1' );
add_post_meta( $post_id, 'myplugin_tag', 'tag2' );

// با single = true، فقط مقدار اول برمی‌گردد
$first = get_post_meta( $post_id, 'myplugin_tag', true );
// نتیجه: 'tag1'

// با single = false، تمام مقادیر برمی‌گردد
$all = get_post_meta( $post_id, 'myplugin_tag', false );
// نتیجه: array( 'tag1', 'tag2' )

این رفتار در برخی سناریوها باعث باگ‌های پنهان می‌شود. اگر مطمئن نیستید که کلید یک مقدار دارد یا چند مقدار، همیشه از single = false استفاده کنید.

نقش escape در نمایش ایمن داده

تابع get_post_meta مقدار را همان‌طور که در دیتابیس ذخیره شده برمی‌گرداند. اگر مقدار شامل HTML یا JavaScript مخرب باشد، همان محتوا بازگردانده می‌شود. به همین دلیل، هنگام نمایش در HTML، همیشه باید escape کنید:

$value = get_post_meta( $post_id, 'myplugin_field', true );
echo esc_html( $value );

توابع escape بر اساس زمینه استفاده:

  • esc_html: نمایش متن داخل HTML
  • esc_attr: نمایش مقدار در attribute
  • esc_url: نمایش URL
  • esc_js: نمایش داخل JavaScript
  • wp_kses_post: اجازه HTML محدود

برای مطالعه کامل، مطلب Output Escaping در وردپرس راهنمای کامل است.

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

نمایش قیمت محصول

$price = get_post_meta( get_the_ID(), 'myplugin_price', true );
if ( ! empty( $price ) ) {
    printf( '<p>قیمت: %s تومان</p>', esc_html( number_format_i18n( $price ) ) );
}

خواندن آرایه تنظیمات

$options = get_post_meta( $post_id, 'myplugin_options', true );
if ( is_array( $options ) ) {
    $color = isset( $options['color'] ) ? $options['color'] : '#000';
    printf( '<span style="color: %s;">متن</span>', esc_attr( $color ) );
}

خواندن متادیتای چندمقداری

$tags = get_post_meta( $post_id, 'myplugin_tag', false );
if ( ! empty( $tags ) ) {
    echo '<ul>';
    foreach ( $tags as $tag ) {
        printf( '<li>%s</li>', esc_html( $tag ) );
    }
    echo '</ul>';
}

بررسی وجود متادیتا

$value = get_post_meta( $post_id, 'myplugin_field', true );
if ( '' !== $value ) {
    echo esc_html( $value );
} else {
    echo esc_html__( 'مقداری تنظیم نشده است.', 'my-plugin' );
}

نکته مهم: بررسی با ! empty() ممکن است برای مقدار 0 اشتباه باشد. برای دقت بیشتر، از '' !== $value استفاده کنید.

خواندن متادیتا در حلقه پست‌ها

$query = new WP_Query( array( 'post_type' => 'product', 'posts_per_page' => 10 ) );
while ( $query->have_posts() ) {
    $query->the_post();
    $price = get_post_meta( get_the_ID(), 'myplugin_price', true );
    printf( '<li>%s - %s</li>', esc_html( get_the_title() ), esc_html( $price ) );
}
wp_reset_postdata();

برای مطالعه دقیق WP_Query، مطلب کلاس WP_Query راهنماست.

خواندن متادیتای محصول ووکامرس

$product = wc_get_product( $product_id );
$custom_field = get_post_meta( $product_id, 'myplugin_field', true );

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

خواندن متادیتا در REST API

register_rest_field( 'post', 'myplugin_price', array(
    'get_callback' => function ( $post ) {
        return get_post_meta( $post['id'], 'myplugin_price', true );
    },
    'schema'       => array( 'type' => 'string' ),
) );

برای مطالعه کامل REST API، مطلب تابع register_rest_field راهنماست.

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

نبود escape در نمایش

شایع‌ترین اشتباه امنیتی. اگر مقدار را بدون escape در HTML چاپ کنید، ممکن است به XSS منجر شود. همیشه از esc_html، esc_attr و esc_url بر اساس زمینه استفاده کنید.

نبود بررسی وجود متادیتا

اگر کلید وجود نداشته باشد و single = true، تابع رشته خالی برمی‌گرداند. اگر مقدار خالی نمایش دهید، ممکن است طرح قالب به‌هم بریزد. همیشه بررسی کنید:

$value = get_post_meta( $post_id, 'key', true );
if ( '' !== $value ) {
    echo esc_html( $value );
}

استفاده از empty برای مقدار صفر

تابع empty() برای مقدار 0، '0' و '0.0' مقدار true برمی‌گرداند. اگر انتظار دارید مقدار صفر معتبر باشد، از مقایسه دقیق استفاده کنید:

if ( '' !== $value ) { /* ... */ }

نبود بررسی is_array برای مقادیر پیچیده

اگر مقدار به‌صورت آرایه ذخیره شده باشد و شما فرض کنید رشته است، ممکن است خطای PHP رخ دهد. همیشه بررسی کنید:

$options = get_post_meta( $post_id, 'options', true );
if ( is_array( $options ) && isset( $options['key'] ) ) {
    // استفاده ایمن
}

استفاده از single اشتباه

اگر کلید چند مقدار دارد و شما single = true استفاده کنید، فقط مقدار اول برگردانده می‌شود و بقیه از دست می‌روند. اگر نمی‌دانید چند مقدار وجود دارد، از single = false استفاده کنید.

نبود cache invalidation

تابع get_post_meta از Object Cache استفاده می‌کند. اگر مقدار را با کوئری مستقیم تغییر دهید، cache به‌طور خودکار پاک نمی‌شود. برای پاک‌سازی، از delete_post_meta یا update_post_meta استفاده کنید نه کوئری مستقیم.

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

تست‌هایی مثل «مقدار خالی»، «مقدار صفر»، «آرایه بزرگ»، «مقدار با HTML» و «کلید ناموجود» را حتماً بنویسید.

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

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

  • escape کامل خروجی با توابع مناسب زمینه
  • بررسی نوع داده با is_array، is_numeric و ...
  • در REST API، بررسی capability کاربر برای افشای داده حساس
  • در قالب‌ها، اجتناب از نمایش مقادیر حساس مثل کلید API

برای مطالعه جامع، مطلب راهنمای Sanitization در وردپرس مرجع است.

از نظر عملکرد، اولین خواندن یک کلید یک کوئری به دیتابیس می‌زند و نتیجه در Object Cache ذخیره می‌شود. خواندن‌های بعدی از حافظه انجام می‌شود:

  1. در حلقه‌های بزرگ، به‌جای خواندن تکی، می‌توانید از update_post_meta_cache استفاده کنید
  2. مقدار single = false یک آرایه برمی‌گرداند که در حلقه‌های بزرگ حافظه بیشتری مصرف می‌کند
  3. در کوئری‌های سفارشی، پارامتر update_post_meta_cache را false بگذارید تا کوئری اضافه اجرا نشود
$query = new WP_Query( array(
    'post_type'           => 'post',
    'update_post_meta_cache' => false,
) );

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

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

تفاوت get_post_meta با get_post_custom چیست؟

get_post_custom() تمام متادیتای پست را به‌صورت آرایه برمی‌گرداند، در حالی که get_post_meta() یک کلید مشخص را برمی‌گرداند و سریع‌تر است.

چرا get_post_meta مقدار خالی برمی‌گرداند؟

معمولاً به دو دلیل: کلید برای آن پست ذخیره نشده، یا post_id نامعتبر است. برای بررسی دقیق، از metadata_exists( 'post', $post_id, $key ) استفاده کنید.

تفاوت single = true و single = false چیست؟

single = true یک مقدار منحصر برمی‌گرداند، در حالی که single = false یک آرایه از تمام مقادیر همان کلید.

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

بله، اولین خواندن از دیتابیس و خواندن‌های بعدی از Object Cache انجام می‌شود. برای پاک‌سازی cache، از delete_post_meta یا update_post_meta استفاده کنید نه کوئری مستقیم.

آیا این تابع روی Multisite رفتار خاصی دارد؟

خیر، هر سایت جدول postmeta خودش را دارد. برای مطالعه بیشتر، مطلب مدیریت Multisite وردپرس را ببینید.

آیا می‌توان متادیتا را با WP-CLI خواند؟

بله، با دستور wp post meta get. مطلب راهنمای WP-CLI راهنماست.

آیا get_post_meta در REST API قابل استفاده است؟

بله، می‌توانید با register_rest_field مقدار متادیتا را در پاسخ REST اضافه کنید. مطلب تابع register_rest_field راهنماست.

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

در سطح معماری، get_post_meta() یک لایه نازک روی get_metadata است که در فایل wp-includes/meta.php تعریف شده. این تابع ابتدا cache را بررسی می‌کند و اگر نتیجه‌ای نباشد، یک کوئری به دیتابیس می‌زند.

نکته ظریف اول، مسئله prefetch متادیتا است. تابع WP_Query به‌طور پیش‌فرض متادیتای تمام پست‌های نتیجه را از قبل بارگذاری می‌کند (از طریق update_post_meta_cache). این یعنی در حلقه‌های اصلی، خواندن متادیتا تقریباً بدون کوئری انجام می‌شود. اما در کوئری‌های سفارشی با update_post_meta_cache = false، هر خواندن یک کوئری جداگانه است.

نکته دوم، مسئله serialize و unserialize است. اگر مقدار به‌صورت آرایه یا شیء ذخیره شده باشد، وردپرس از maybe_unserialize استفاده می‌کند. این تابع بررسی می‌کند که رشته serialize شده است یا نه. اگر داده مخرب در دیتابیس باشد، ممکن است به unserialize ناامن منجر شود. برای همین توصیه می‌شود در ذخیره، از sanitize دقیق استفاده کنید.

مسئله سوم، تعامل با WooCommerce HPOS است. در نسخه‌های جدید ووکامرس، سفارش‌ها به‌جای postmeta در جدول‌های اختصاصی ذخیره می‌شوند. اگر افزونه شما از get_post_meta برای خواندن متادیتای سفارش استفاده می‌کند، در HPOS ممکن است داده‌ها خالی برگردند. برای مطالعه بیشتر، مطلب WooCommerce HPOS در برابر Legacy Storage راهنماست.

در نهایت، در پروژه‌های Enterprise توصیه می‌شود یک لایه Repository بسازید که خواندن متادیتا را انتزاعی کند. به‌جای فراخوانی مستقیم در چند نقطه، یک متد اختصاصی در کلاس Domain بنویسید که هم cache را مدیریت کند و هم تبدیل نوع را انجام دهد. برای مطالعه بیشتر، مباحث استانداردهای PSR و تابع register_meta مفید هستند. برای مطالعه بیشتر درباره خود وردپرس، WordPress در ویکی‌پدیا نقطه شروع خوبی است.

اگر در پروژه‌ای با مشکل خواندن مقادیر سریالایز یا رفتار غیرمنتظره در single مواجه شده‌اید، برای ما جالب است بدانید کدام راهکار عملاً به حل مسئله کمک کرده است. تجربه خود را در دیدگاه‌ها بنویسید تا برای سایر توسعه‌دهندگان هم مفید باشد.