خطای Cannot use object as array در PHP وردپرس، یکی از آن خطاهایی است که در نگاه اول ساده به نظر می‌رسد ولی در عمل، در پروژه‌های واقعی می‌تواند ساعت‌ها وقت دیباگ بگیرد. اولین باری که این خطا را در یک پروژه پیچیده دیدم، در یک افزونه اختصاصی بود که داده‌های API بیرونی را در متادیتا ذخیره می‌کرد و بعد از یک تغییر نسخه، ساختار داده از آرایه به آبجکت تغییر کرده بود. نتیجه این تغییر، شکست همه بخش‌هایی بود که با سینتکس آرایه به داده دسترسی داشتند. درس آن روز این بود که در PHP، تشخیص دقیق ساختار داده، نه یک توصیه سلیقه‌ای بلکه پیش‌نیاز است.

اگر با مفاهیم پایه PHP در وردپرس آشنایی کمتری دارید، پیش از ادامه PHP در وردپرس: از مبتدی تا حرفه‌ای را بخوانید. این نوشته، لایه عیب‌یابی همان بحث است: از آناتومی دقیق خطا و تفاوت آن با خطای مشابه خطای Object could not be converted to string شروع می‌کنم و تا الگوهای رفع و پیشگیری پیش می‌روم. برای درک عمومی روش‌های دیباگ در وردپرس، دیباگ کردن کدهای سفارشی وردپرس مرجع مکمل این نوشته است.

خطای Cannot use object as array دقیقاً چه می‌گوید؟

این خطا در PHP یعنی: کد شما در جایی که یک آرایه (array) انتظار داشته، یک آبجکت (object) دریافت کرده و تلاش کرده با سینتکس آرایه به آن دسترسی پیدا کند. سینتکس آرایه در PHP با براکت انجام می‌شود: $variable['key']. اگر $variable آبجکت باشد و نه آرایه، PHP این خطا را برمی‌گرداند. اهمیت این خطا در این است که در نسخه‌های جدید PHP، این خطا به Fatal Error تبدیل شده و اجرای اسکریپت را کاملاً متوقف می‌کند.

پیام دقیق این خطا معمولاً به شکل زیر است:

PHP Fatal error:  Uncaught Error: Cannot use object of type stdClass as array
in /path/to/plugin/file.php:57
Stack trace:
#0 /path/to/wordpress/wp-includes/class-wp-hook.php(324): myplugin_process_data()
#1 /path/to/wordpress/wp-includes/plugin.php(205): WP_Hook->apply_filters()
...

سه چیز در این پیام مهم است. اول، نام دقیق کلاس آبجکت: stdClass، WP_User، WP_Post، WP_Term یا یک کلاس سفارشی. دوم، نام فایل و شماره خط که در آن دسترسی شکست خورده. سوم، Stack Trace که مسیر اجرای کد را نشان می‌دهد. هر سه این‌ها ابزار تشخیص هستند و در ادامه به آن‌ها برمی‌گردم.

نکته مهم: این خطا در دو سناریو ظاهر می‌شود. سناریوی اول، دسترسی به یک آبجکت با سینتکس آرایه است: $obj['key'] به‌جای $obj->key. سناریوی دوم، استفاده از توابع آرایه روی آبجکت است: array_key_exists()، isset($obj['key'])، یا foreach با آرایه‌ای که در واقع آبجکت است. هر دو سناریو یک ریشه دارند: ناهماهنگی بین ساختار داده واقعی و آن‌چه کد انتظار دارد.

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

آناتومی تفاوت آرایه و آبجکت در PHP

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

سینتکس دسترسی

آرایه در PHP با براکت به عناصر دسترسی می‌دهد: $arr['key']. آبجکت با فلش دسترسی می‌دهد: $obj->key. اگر سینتکس اشتباه استفاده شود، خطای مورد بحث رخ می‌دهد. مفهوم آرایه در مرجع فنی وب با عنوان Associative array شناخته می‌شود و در PHP هم به‌طور مستقیم پشتیبانی می‌شود.

رفتار کپی و ارجاع

در PHP، آرایه‌ها به‌طور پیش‌فرض کپی می‌شوند (copy on write) ولی آبجکت‌ها با ارجاع (reference) منتقل می‌شوند. این تفاوت رفتاری، در پروژه‌های واقعی باعث رفتارهای غیرمنتظره می‌شود: اگر آبجکتی را به یک تابع بدهید و آن را تغییر دهید، آبجکت اصلی هم تغییر می‌کند، ولی این برای آرایه‌ها صادق نیست. این تفاوت، دلیل دیگری است که در پروژه‌های حرفه‌ای، استفاده از آرایه یا آبجکت یک تصمیم معماری است نه سلیقه.

توابع مربوطه

توابع PHP برای آرایه‌ها و آبجکت‌ها جداگانه هستند. توابعی مثل array_key_exists، array_push، array_map فقط روی آرایه کار می‌کنند. توابعی مثل property_exists، get_object_vars فقط روی آبجکت. اشتباه در انتخاب تابع، منبع رایج خطای مورد بحث است.

ویژگیآرایهآبجکت
سینتکس دسترسی$arr['key']$obj->key
رفتار کپیcopy on writeby reference
تابع بررسی کلیدarray_key_existsproperty_exists
حلقه استانداردforeachforeach
مثال وردپرسآرایه گزینه‌هاWP_Post، WP_User

در وردپرس، هر دو ساختار داده به‌طور گسترده استفاده می‌شوند. توابعی مثل get_post آبجکت WP_Post برمی‌گردانند و توابعی مثل get_post_meta با پارامتر سوم false، آرایه برمی‌گردانند. اشتباه در تشخیص این تفاوت، منبع اصلی خطای مورد بحث در پروژه‌های وردپرسی است.

رفتار خطا در نسخه‌های مختلف PHP

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

PHP 5.x و PHP 7.0

در نسخه‌های قدیمی‌تر PHP، تلاش برای دسترسی به آبجکت با سینتکس آرایه معمولاً یک Notice یا Warning تولید می‌کرد و مقدار null برمی‌گرداند. این رفتار، در ظاهر بی‌خطر بود ولی در عمل، باگ‌های پنهان زیادی می‌ساخت که در محیط production ظاهر می‌شدند بدون این‌که توسعه‌دهنده متوجه هشدارها شود.

PHP 7.4 و PHP 8.0 به بعد

از PHP 7.4، این خطا در بسیاری از سناریوها به Warning تبدیل شد. از PHP 8.0، این خطا در همه سناریوها به Fatal Error تبدیل شد که اجرای اسکریپت را کاملاً متوقف می‌کند. این تغییر، دلیل اصلی این است که سایت‌هایی که روی PHP 7.4 پایدار بودند، بعد از ارتقا به PHP 8.0 با خطای صفحه سفید مواجه شدند.

جدول تفاوت رفتار در نسخه‌های مختلف:

نسخه PHPرفتار خطاپیامد روی سایت
PHP 5.xNotice با مقدار nullادامه اجرا با هشدار
PHP 7.0 تا 7.3Notice یا Warningادامه اجرا با مقدار پیش‌فرض
PHP 7.4Warning در بیشتر مواردادامه اجرا با هشدار
PHP 8.0 به بعدFatal Error در همه سناریوهاتوقف اجرا و صفحه سفید

نتیجه عملی: اگر افزونه یا قالب شما در PHP 7.4 کار می‌کرد ولی در PHP 8.0 از کار افتاد، این خطا یکی از مظنون‌های اصلی است. این تفاوت رفتاری، به این دلیل به وجود آمده که PHP 8 تصمیم گرفته جدی‌تر با نوع‌های نامناسب برخورد کند، چون این نوع دسترسی نشانه‌ای از باگ منطقی در کد است. اصول دقیق مهاجرت امن PHP در تست و دیباگ پروژه‌های توسعه وردپرس آمده است.

هفت علت ریشه‌ای در پروژه‌های وردپرسی

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

علت اول: داده JSON decode شده با پارامتر دوم اشتباه

شایع‌ترین علت. تابع json_decode به‌طور پیش‌فرض یک آبجکت stdClass برمی‌گرداند. اگر پارامتر دوم ($associative) روی true تنظیم نشود، خروجی آبجکت خواهد بود:

// اشتباه - خروجی stdClass است
$data = json_decode( $response_body );
echo $data['name']; // خطا رخ می‌دهد

// درست با پارامتر دوم
$data = json_decode( $response_body, true );
echo $data['name'];

یا اگر قصد استفاده از آبجکت دارید، سینتکس را درست کنید:

$data = json_decode( $response_body );
echo $data->name;

این علت در پروژه‌هایی که با REST API کار می‌کنند، بسیار رایج است چون توسعه‌دهنده فراموش می‌کند که json_decode به‌طور پیش‌فرض آبجکت برمی‌گرداند. اصول دقیق کار با داده‌های خارجی در پاک‌سازی داده‌ها در کدنویسی وردپرس آمده است.

علت دوم: متادیتای ذخیره‌شده به‌عنوان آبجکت

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

// اشتباه در ذخیره‌سازی
update_post_meta( $post_id, 'my_data', $object_value );

// بازیابی اشتباه
$data = get_post_meta( $post_id, 'my_data', true );
echo $data['field']; // اگر آبجکت ذخیره شده بود، خطا رخ می‌دهد

راه‌حل، تبدیل صریح به آرایه قبل از ذخیره‌سازی یا بعد از بازیابی:

// ذخیره به‌صورت آرایه
update_post_meta( $post_id, 'my_data', (array) $object_value );

// بازیابی با بررسی نوع
$data = get_post_meta( $post_id, 'my_data', true );
if ( is_object( $data ) ) {
    $data = (array) $data;
}
echo $data['field'] ?? '';

این علت در پروژه‌های متاباکس سفارشی زیاد دیده می‌شود. اصول دقیق کار با متادیتا در کار با متاباکس‌ها در کدنویسی وردپرس و «کار با User Meta در کدنویسی وردپرس» آمده است.

علت سوم: خروجی توابع وردپرس که آبجکت برمی‌گردانند

بعضی توابع وردپرس آبجکت برمی‌گردانند و توسعه‌دهنده فرض می‌کند آرایه برمی‌گردانند. سه نمونه شایع در پروژه‌های واقعی:

  • get_post() که آبجکت WP_Post برمی‌گرداند.
  • get_user_by() که آبجکت WP_User برمی‌گرداند.
  • get_term() که آبجکت WP_Term برمی‌گرداند.

اگر کد شما به‌جای $post->ID از $post['ID'] استفاده کند، خطا رخ می‌دهد. این علت در کدهایی که از پروژه دیگر کپی شده‌اند، بسیار رایج است.

علت چهارم: حلقه روی نتیجه wpdb بدون بررسی نوع

خروجی $wpdb->get_results به‌طور پیش‌فرض آرایه‌ای از آبجکت‌ها است. اگر توسعه‌دهنده فرض کند آرایه‌ای از آرایه‌ها است، خطا رخ می‌دهد:

global $wpdb;
$results = $wpdb->get_results( 'SELECT * FROM my_table' );

foreach ( $results as $row ) {
    echo $row['column_name']; // خطا - row یک آبجکت است
}

// راه‌حل درست
foreach ( $results as $row ) {
    echo $row->column_name;
}

// یا با پارامتر دوم
$results = $wpdb->get_results( 'SELECT * FROM my_table', ARRAY_A );
foreach ( $results as $row ) {
    echo $row['column_name'];
}

پارامتر دوم $wpdb->get_results یکی از سه مقدار می‌تواند باشد: OBJECT (پیش‌فرض)، ARRAY_A (آرایه انجمنی) و ARRAY_N (آرایه عددی). فراموش کردن این پارامتر، در پروژه‌های کوئری‌ساز، زیاد دیده می‌شود.

علت پنجم: بازگشت WP_Error و نبود بررسی نوع

توابع وردپرس در صورت خطا، به‌جای داده مورد انتظار، آبجکت WP_Error برمی‌گردانند. اگر کد بدون بررسی نوع، به داده دسترسی پیدا کند، خطا رخ می‌دهد:

// اشتباه
$response = wp_remote_get( $url );
$body = $response['body']; // خطا اگر WP_Error باشد

// درست
$response = wp_remote_get( $url );

if ( is_wp_error( $response ) ) {
    return;
}

$body = wp_remote_retrieve_body( $response );

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

علت ششم: ساختار داده API که بین نسخه‌ها تغییر کرده

وقتی یک API بیرونی نسخه خود را به‌روزرسانی می‌کند و ساختار داده از آرایه به آبجکت (یا برعکس) تغییر می‌کند، کد قدیمی با خطا مواجه می‌شود. این علت در پروژه‌های یکپارچه‌سازی با سرویس‌های خارجی زیاد دیده می‌شود. راه‌حل، بررسی دقیق مستندات API پیش از هر به‌روزرسانی و استفاده از تست خودکار برای تشخیص تغییرات است.

علت هفتم: کد کپی-شده بدون تطبیق ساختار داده

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

در میان این هفت علت، سه علت اول (JSON decode، متادیتا، خروجی توابع وردپرس) بیشترین سهم را در پروژه‌های واقعی دارند. اگر فقط این سه را در پروژه خود بررسی کنید، احتمالاً در ۸۰ درصد موارد به علت اصلی می‌رسید.

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

مرحله تشخیص: از لاگ تا var_dump

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

ابزار اول: WP_DEBUG و debug.log

اولین قدم، فعال‌سازی حالت دیباگ در wp-config.php است:

define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', true );
define( 'WP_DEBUG_DISPLAY', false );
@ini_set( 'display_errors', 0 );

با این تنظیمات، پیام‌های خطا در فایل wp-content/debug.log ثبت می‌شوند و به مرورگر کاربر نمایش داده نمی‌شوند. Stack Trace موجود در لاگ، مسیر دقیق خطا را نشان می‌دهد. در این Stack Trace، سه چیز را جستجو می‌کنم: نام فایل و شماره خط، نام تابعی که خطا در آن رخ داده، و پارامترها یا آرگومان‌های آن تابع. اصول دقیق در دیباگ کردن کدهای سفارشی وردپرس آمده است.

ابزار دوم: var_dump و print_r

گاهی Stack Trace به‌تنهایی کافی نیست چون مقدار متغیر در لحظه خطا را نشان نمی‌دهد. در این سناریو، از var_dump یا print_r استفاده می‌کنم:

var_dump( $data );
// یا در لاگ
 error_log( print_r( $data, true ) );

خروجی var_dump هم نوع و هم مقدار متغیر را نشان می‌دهد و در تشخیص سریع کمک می‌کند. یک نکته عملی: در محیط production، استفاده از var_dump مستقیم توصیه نمی‌شود چون می‌تواند ساختار داده را برای کاربران نمایش دهد. به‌جای آن، از error_log استفاده کنید.

ابزار سوم: Xdebug برای تحلیل عمیق

در پروژه‌های پیچیده که Stack Trace طولانی است، Xdebug ابزار قابل اتکایی است. با Xdebug می‌توانید در IDE با یک breakpoint، مقدار دقیق همه متغیرها در لحظه خطا را ببینید و مسیر اجرای کد را گام‌به‌گام طی کنید. این رویکرد، در پروژه‌های بزرگ که چندین لایه کد بین تابع اصلی و خطا وجود دارد، تفاوت محسوسی در زمان تشخیص می‌سازد. اصول دقیق در «دیباگ کردن کدهای سفارشی وردپرس» آمده است.

ابزار چهارم: بررسی نوع با توابع PHP

در بعضی سناریوها، قبل از خطا، می‌توانید با توابع بررسی نوع، ساختار واقعی داده را تشخیص دهید:

if ( is_object( $data ) ) {
    error_log( 'Data is object, class: ' . get_class( $data ) );
} elseif ( is_array( $data ) ) {
    error_log( 'Data is array with keys: ' . implode( ',', array_keys( $data ) ) );
}

سه تابع اصلی که در این تشخیص استفاده می‌کنم: is_object، is_array و gettype. علاوه بر این‌ها، get_class نام دقیق کلاس را برمی‌گرداند که در تشخیص منبع خطا مفید است.

نکته امنیتی در تشخیص

در سایت production، هرگز WP_DEBUG را برای مدت طولانی فعال نگه ندارید چون سه مشکل ایجاد می‌کند: اول، فایل debug.log می‌تواند به‌سرعت به چند گیگابایت برسد. دوم، اطلاعات حساس ممکن است در لاگ‌ها ثبت شوند. سوم، نمایش خطاها به کاربران، سطح امنیتی سایت را پایین می‌آورد. اصول کامل را در نوشتن کد PHP امن برای وردپرس آورده‌ام.

الگوهای رفع برای هر علت

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

رفع برای JSON decode

اگر کد شما با json_decode کار می‌کند و می‌خواهید با آرایه کار کنید، پارامتر دوم را true قرار دهید:

$data = json_decode( $body, true );

if ( ! is_array( $data ) ) {
    return;
}

$field = $data['name'] ?? '';

اگر می‌خواهید با آبجکت کار کنید، سینتکس را درست کنید:

$data = json_decode( $body );

if ( ! is_object( $data ) ) {
    return;
}

$field = $data->name ?? '';

رفع برای متادیتای ذخیره‌شده

الگوی درست، ذخیره همیشه به‌صورت آرایه و بررسی نوع در بازیابی است:

// ذخیره
update_post_meta( $post_id, 'my_data', (array) $value );

// بازیابی با بررسی نوع
$data = get_post_meta( $post_id, 'my_data', true );

if ( is_object( $data ) ) {
    $data = (array) $data;
}

if ( ! is_array( $data ) ) {
    $data = [];
}

$field = $data['field'] ?? '';

این الگو، هم در ذخیره‌سازی و هم در بازیابی، امن است. اصول دقیق در کار با User Meta در کدنویسی وردپرس آمده است.

رفع برای خروجی توابع وردپرس

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

$post = get_post( $post_id );

if ( ! $post instanceof WP_Post ) {
    return;
}

// دسترسی با فلش
$title = $post->post_title;
$content = $post->post_content;
$id = $post->ID;

استفاده از instanceof برای بررسی نوع آبجکت، یک عادت حرفه‌ای است که در همه پروژه‌ها رعایت می‌کنم.

رفع برای کوئری wpdb

الگوی درست، انتخاب صریح نوع بازگشتی در پارامتر دوم است:

global $wpdb;

// برای آرایه انجمنی
$results = $wpdb->get_results( 'SELECT * FROM my_table', ARRAY_A );

foreach ( $results as $row ) {
    echo esc_html( $row['column_name'] );
}

// برای آبجکت
$results = $wpdb->get_results( 'SELECT * FROM my_table' );

foreach ( $results as $row ) {
    echo esc_html( $row->column_name );
}

انتخاب نوع بازگشتی، یک تصمیم سلیقه‌ای نیست؛ در کدهای خودم همیشه یکی از دو روش را انتخاب می‌کنم و در همه کوئری‌ها یکسان اعمال می‌کنم. این انسجام، خوانایی کد را بالا می‌برد.

رفع برای WP_Error برگشتی

الگوی درست، بررسی WP_Error قبل از دسترسی به داده است:

$response = wp_remote_get( $url, [ 'timeout' => 10 ] );

if ( is_wp_error( $response ) ) {
    error_log( $response->get_error_message() );
    return;
}

$body = wp_remote_retrieve_body( $response );
$data = json_decode( $body, true );

if ( ! is_array( $data ) ) {
    return;
}

این الگو در همه درخواست‌های HTTP باید رعایت شود. اصول دقیق در PHP در وردپرس: از مبتدی تا حرفه‌ای آمده است.

رفع با تابع کمکی برای تبدیل نوع

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

function myplugin_safe_array( $value ): array {
    if ( is_array( $value ) ) {
        return $value;
    }

    if ( is_object( $value ) ) {
        return get_object_vars( $value );
    }

    return [];
}

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

داده‌های JSON و سریالایز: منبع اصلی خطا

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

JSON: استاندارد داده‌های وب

JSON که در مرجع فنی با عنوان JSON شناخته می‌شود، استاندارد تبادل داده در وب است و در پروژه‌های وردپرسی هم به‌طور گسترده استفاده می‌شود. تابع json_decode در PHP، به‌طور پیش‌فرض آبجکت برمی‌گرداند که این تصمیم طراحی، منبع اصلی خطا در پروژه‌های وردپرسی است.

سه نکته کلیدی در کار با JSON که در پروژه‌های واقعی رعایت می‌کنم:

  • همیشه پارامتر دوم json_decode را صریح تعیین کنید — یا true برای آرایه یا false برای آبجکت.
  • بعد از decode، نوع داده را بررسی کنید: is_array یا is_object.
  • در دسترسی به داده، سینتکس مناسب با نوع را استفاده کنید.

داده‌های سریالایز در وردپرس

وردپرس از توابع serialize و unserialize برای ذخیره داده‌های پیچیده در دیتابیس استفاده می‌کند. تفاوت سریالایز با JSON در این است که سریالایز، نوع اصلی داده را حفظ می‌کند. یعنی اگر یک آبجکت سریالایز شود و بعد بازگردانده شود، همچنان آبجکت است. این ویژگی هم مزیت است و هم منبع خطا، چون ممکن است کد انتظار آرایه داشته باشد ولی داده واقعی آبجکت باشد.

الگوی درست در خواندن داده سریالایز:

$data = get_option( 'my_plugin_settings' );

if ( is_object( $data ) ) {
    $data = (array) $data;
}

if ( ! is_array( $data ) ) {
    $data = [];
}

$field = $data['field'] ?? 'default';

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

داده‌های REST API وردپرس

در REST API وردپرس، پاسخ‌ها به‌صورت JSON برگردانده می‌شوند. در سمت کلاینت PHP، بعد از json_decode، باید نوع داده را بررسی کنید. الگوی امن:

$response = wp_remote_get( rest_url( 'wp/v2/posts' ), [
    'headers' => [ 'Authorization' => 'Bearer ' . $token ],
] );

if ( is_wp_error( $response ) ) {
    return;
}

$posts = json_decode( wp_remote_retrieve_body( $response ), true );

if ( ! is_array( $posts ) ) {
    return;
}

foreach ( $posts as $post ) {
    $title = $post['title']['rendered'] ?? '';
}

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

پیشگیری با type-check و بازبینی کد

بهترین راه‌حل برای خطای Cannot use object as array، پیشگیری است. در پروژه‌های واقعی، تجربه‌ام این است که اگر در پنج لایه پیشگیری انجام شود، این خطا تقریباً هرگز ظاهر نمی‌شود.

لایه اول: type-hint در توابع

در PHP 7.4 و بالاتر، استفاده از type-hint الزامی را توصیه می‌کنم:

function myplugin_process_data( array $data ): void {
    $field = $data['field'] ?? '';
}

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

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

هرگاه مقدار برگشتی از یک تابع مشکوک است، قبل از استفاده، نوع آن را بررسی کنید:

if ( ! is_array( $data ) ) {
    $data = (array) $data;
}

if ( ! isset( $data['field'] ) ) {
    return;
}

$field = $data['field'];

این الگو، در پروژه‌های بزرگ که داده‌ها از منابع مختلف می‌آیند، بسیار مؤثر است. اصول دقیق در اعتبارسنجی داده‌ها در کدنویسی وردپرس آمده است.

لایه سوم: تابع کمکی برای ساختاردهی

در پروژه‌های بزرگ، یک تابع کمکی برای یکنواخت‌سازی ساختار داده می‌سازم:

function myplugin_ensure_array( $data ): array {
    if ( is_array( $data ) ) {
        return $data;
    }

    if ( is_object( $data ) ) {
        return get_object_vars( $data );
    }

    return [];
}

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

لایه چهارم: بازبینی کد با static analysis

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

لایه پنجم: تست‌های واحد برای سناریوهای داده

نوشتن تست‌های واحد برای توابعی که با داده‌های پیچیده کار می‌کنند، یکی از مؤثرترین راه‌های پیشگیری است. اگر با تست‌نویسی در وردپرس آشنایی کمتری دارید، تست و دیباگ پروژه‌های توسعه وردپرس مسیر را توضیح می‌دهد.

لایه ششم: بررسی سازگاری با PHP جدید قبل از ارتقا

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

نگاه مهندسی به مدیریت ساختار داده

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

در پروژه‌های واقعی، سه رویکرد برای مدیریت این بدهی مشاهده کرده‌ام. رویکرد اول: تعریف قرارداد صریح داده‌ها با استفاده از DTO یا کلاس‌های اختصاصی که ساختار داده را در یک نقطه متمرکز می‌کنند. رویکرد دوم: استفاده از declare(strict_types=1) در فایل‌های PHP که PHP را مجبور می‌کند در فراخوانی توابع، نوع‌ها را دقیق بررسی کند. رویکرد سوم: نوشتن تست‌های پوشش‌دهنده که همه مسیرهای داده را تست می‌کنند.

نگاه بلندمدت این است که پروژه‌های وردپرسی، به‌تدریج به سمت نوع‌دهی دقیق‌تر حرکت می‌کنند. در پروژه‌های خودم، سه اصل را رعایت می‌کنم: اول، هیچ کدی نباید به ساختار داده‌ای که مطمئن نیست، مستقیماً دسترسی داشته باشد. دوم، همه توابعی که داده پیچیده دریافت می‌کنند، باید type-hint صریح داشته باشند. سوم، در هر نقطه‌ای که داده از منبع خارجی می‌آید (API، دیتابیس، فایل)، باید بررسی نوع انجام شود.

یک نکته عملی در این حوزه: زمانی که از declare(strict_types=1) استفاده می‌کنید، توجه داشته باشید که این دستور فقط در همان فایل اعمال می‌شود. یعنی اگر فایل شما تابعی را فراخوانی می‌کند که در فایل دیگری تعریف شده و در آن فایل strict types فعال نیست، تبدیل نوع در تابع مقصد همچنان می‌تواند رخ دهد. این نکته، در پروژه‌های بزرگ که فایل‌های زیادی دارند، اهمیت دارد.

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

پرسش‌های پرتکرار درباره خطای Object as array

خطای Cannot use object as array چه معنایی دارد؟ این خطا در PHP یعنی کد شما تلاش کرده با سینتکس آرایه (براکت) به یک آبجکت دسترسی پیدا کند. آبجکت‌ها با سینتکس فلش دسترسی دارند. این خطا معمولاً زمانی رخ می‌دهد که ساختار داده واقعی با آن‌چه کد انتظار دارد، هماهنگ نیست.

تفاوت این خطا با خطای Object could not be converted to string چیست؟ این دو خطا ریشه‌های متفاوتی دارند. خطای Object as array وقتی رخ می‌دهد که آبجکت با سینتکس آرایه دسترسی می‌شود، درحالی‌که خطای Object to string وقتی رخ می‌دهد که آبجکت در جایی که رشته انتظار می‌رود استفاده شود. راه‌حل ریشه‌ای هر دو، تقویت نوع‌دهی است، ولی الگوهای رفع متفاوتند. اگر با خطای اول مواجه شدید، خطای Object could not be converted to string راهنمای مکملی است.

چرا این خطا در PHP 8 بیشتر از PHP 7.4 دیده می‌شود؟ چون PHP 8 رفتار دسترسی به نوع را سختگیرانه‌تر کرده است. در PHP 7.4، این خطا معمولاً یک Warning بود که اجرای اسکریپت را متوقف نمی‌کرد، ولی در PHP 8 به Fatal Error تبدیل شده که اجرای اسکریپت را کاملاً متوقف می‌کند. این تغییر، باعث شده سایت‌هایی که روی PHP 7.4 پایدار بودند، بعد از ارتقا با خطای صفحه سفید مواجه شوند.

آیا راه‌حل سریع وجود دارد؟ اگر بخواهید فقط خطا را از بین ببرید، تبدیل صریح آبجکت به آرایه با (array) $value یا get_object_vars( $value ) در بعضی سناریوها کار می‌کند. ولی این راه‌حل موقت است چون اگر ساختار داده در آینده تغییر کند، خطا برمی‌گردد. راه‌حل ریشه‌ای، بررسی دقیق نوع قبل از دسترسی یا تعریف قرارداد صریح داده است.

چطور بفهمم کجا خطا رخ می‌دهد؟ فعال‌سازی WP_DEBUG و بررسی فایل debug.log، اولین قدم است. Stack Trace موجود در لاگ، مسیر دقیق خطا را نشان می‌دهد. اگر Stack Trace پیچیده بود، استفاده از Xdebug تشخیص را ساده‌تر می‌کند. اگر می‌خواهید مقدار دقیق متغیر را در لحظه خطا ببینید، از var_dump یا error_log( print_r( $data, true ) ) استفاده کنید. اصول دقیق در دیباگ کردن کدهای سفارشی وردپرس آمده است.

آیا این خطا روی سرعت سایت اثر دارد؟ در حالت Fatal Error، اجرای اسکریپت متوقف می‌شود و صفحه سفید نمایش داده می‌شود که خودش یک افت محسوس است. در حالت Warning یا Notice (در نسخه‌های قدیمی PHP)، تأثیر مستقیم روی سرعت کمتر است ولی به‌دلیل وقفه در پردازش و تولید پیام‌های متعدد، بار اضافه روی سرور ایجاد می‌کند. اگر با گلوگاه‌های سرعت سایت آشنا نیستید، تاثیر هاست بر سرعت سایت چقدر است تحلیل دقیقی دارد.

چرا این خطا در افزونه‌های REST API زیاد دیده می‌شود؟ چون در REST API، داده‌ها به‌صورت JSON دریافت می‌شوند و json_decode به‌طور پیش‌فرض آبجکت برمی‌گرداند. اگر توسعه‌دهنده فراموش کند پارامتر دوم را true قرار دهد، همه دسترسی‌ها با سینتکس آرایه شکست می‌خورند. راه‌حل، تعیین صریح پارامتر دوم و بررسی نوع داده بعد از decode است. اصول دقیق در REST API در وردپرس آمده است.

چطور از این خطا در آینده جلوگیری کنم؟ پنج لایه پیشگیری: استفاده از type-hint در توابع، بررسی نوع قبل از دسترسی، استفاده از تابع کمکی برای یکنواخت‌سازی، بازبینی کد با ابزارهای static analysis، و نوشتن تست‌های واحد. این پنج لایه، در پروژه‌های واقعی، تقریباً این خطا را حذف می‌کنند.

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

آیا خطای Cannot use object as array همیشه ناشی از کد است؟ نه همیشه. در بعضی سناریوها، این خطا از داده‌های ناسازگار در دیتابیس می‌آید. مثلاً اگر یک سریالایز ناقص در دیتابیس ذخیره شده باشد و در بازخوانی، ساختار ناقصی برگرداند، خطا رخ می‌دهد. در این سناریو، علاوه بر رفع کد، باید داده‌های دیتابیس هم تمیز شوند. اگر با مکانیزم داده‌های سریالایز آشنا نیستید، کار با Options API در کدنویسی وردپرس راهنمای مکملی است.

تفاوت get_object_vars و (array) cast چیست؟ هر دو آبجکت را به آرایه تبدیل می‌کنند ولی با تفاوت‌های ظریف. (array) $obj همه خصوصیات آبجکت را به آرایه تبدیل می‌کند، حتی خصوصیات خصوصی و محافظت‌شده (با پیشوندهای خاص). get_object_vars( $obj ) فقط خصوصیات عمومی را برمی‌گرداند. در اکثر سناریوهای وردپرسی، استفاده از (array) $obj رایج‌تر است ولی در بعضی موارد، get_object_vars مناسب‌تر است.

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

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

تفاوت stdClass با کلاس‌های وردپرس مثل WP_Post چیست؟ stdClass کلاس پایه PHP است که وقتی هیچ کلاس اختصاصی تعریف نشده، استفاده می‌شود. کلاس‌های وردپرس مثل WP_Post، WP_User و WP_Term ساختار دقیق‌تری دارند و خصوصیات مشخص. هر دو آبجکت هستند و با سینتکس فلش دسترسی دارند، ولی بعضی کلاس‌های وردپرس متدهای اختصاصی دارند که استفاده از آن‌ها را راحت‌تر می‌کند.

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

آیا این خطا با خطای Call to a member function on null یکی است؟ نه، این دو خطا متفاوتند. خطای Cannot use object as array وقتی رخ می‌دهد که به آبجکت با سینتکس آرایه دسترسی می‌شود. خطای Call to a member function on null وقتی رخ می‌دهد که روی یک متغیر null، متد فراخوانی می‌شود. ریشه هر دو مشکل مشابه است — ناهماهنگی نوع — ولی راه‌حل دقیق، متفاوت است. اگر با خطای دوم مواجه هستید، خطای Call to a member function on null راهنمای مکملی است.

الگویی برای فردا

در پایان این مسیر، یک حقیقت را باید پذیرفت: خطای Cannot use object as array، یک خطای ساده نیست؛ نشانه‌ای از یک الگوی ناهماهنگ در مدیریت ساختار داده است. اگر این خطا را فقط با cast صریح رفع کنید، ریشه مشکل همچنان باقی می‌ماند و در آینده، در نسخه‌های جدید PHP یا در سناریوهای پیچیده‌تر، دوباره ظاهر می‌شود. راه‌حل بلندمدت، تقویت قراردادهای داده و بررسی دقیق نوع‌ها در پروژه است.

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

اگر در ابتدای مسیر یادگیری هستید، سه تمرین را پیشنهاد می‌کنم. اول، در یک نصب تستی وردپرس، یک صفحه ساده بسازید که در آن، به‌طور عمدی این خطا رخ دهد و آن را با روش‌های این نوشته رفع کنید. دوم، در یک پروژه واقعی، فایل debug.log را برای این خطا جستجو کنید و ببینید چند نقطه از کد شما آسیب‌پذیر است. سوم، در کد افزونه خود، یک تابع کمکی برای بررسی و تبدیل نوع بسازید و در همه جاهای کد از آن استفاده کنید — این تجربه، درک عمیقی از اهمیت قراردادهای داده به شما می‌دهد که هیچ مقاله‌ای جایگزینش نمی‌شود. 🛠️