在使用 ThinkPHP/Laravel 等框架對接第三方 API(如京東服務市場)時,回傳資料常為 stdClass 物件陣列,需轉為純陣列才能配合 addAll() 批量寫入資料庫。本文提供健壯的遞迴轉換函數,支援多層巢狀物件、陣列混合結構,並解析核心原理與進階應用。
<?php
/**
* 將 stdClass 物件(含巢狀)遞迴轉為純陣列
* @param mixed $data 物件、陣列或標量值
* @return array|mixed 轉換後的純陣列或原值
*/
function stdClassToArray($data) {
// 1. 非物件/陣列直接返回
if (!is_object($data) && !is_array($data)) {
return $data;
}
// 2. 物件轉陣列(get_object_vars 獲取可見屬性)
if (is_object($data)) {
$data = get_object_vars($data);
}
// 3. 遞迴處理陣列每個元素
$result = [];
foreach ($data as $key => $value) {
$result[$key] = stdClassToArray($value);
}
return $result;
}
// 別名函數(語義更清晰)
function objectToArray($data) {
return stdClassToArray($data);
}
?>
<?php
// 模擬京東 API 回傳:物件陣列
$apiResponse = (object)[
'code' => 0,
'data' => [
(object)[
'sku_id' => 1001,
'title' => '商品A',
'price' => 99.00,
'specs' => (object)[
'color' => '紅色',
'size' => 'L',
'tags' => ['熱銷', '新品'] // 陣列中的字串
]
],
(object)[
'sku_id' => 1002,
'title' => '商品B',
'price' => 199.00,
'specs' => (object)[
'color' => '藍色',
'size' => 'XL'
]
]
]
];
// 1. 提取資料層
$items = $apiResponse->data; // 這是 stdClass 陣列
// 2. 轉換為純陣列
$itemsArray = stdClassToArray($items);
/*
結果結構:
[
0 => [
'sku_id' => 1001,
'title' => '商品A',
'price' => 99.00,
'specs' => [
'color' => '紅色',
'size' => 'L',
'tags' => ['熱銷', '新品']
]
],
1 => [...]
]
*/
// 3. 直接批量寫入
$model = M('Product');
$model->addAll($itemsArray); // ThinkPHP 批量插入
echo "成功寫入 " . count($itemsArray) . " 筆資料";
?>
<?php
function advancedObjectToArray($data, $options = []) {
$defaultOptions = [
'datetime_format' => 'Y-m-d H:i:s', // DateTime 輸出格式
'preserve_keys' => true, // 保留數字鍵
'max_depth' => 50, // 防無限遞迴
];
$opts = array_merge($defaultOptions, $options);
return _recursiveConvert($data, $opts, 0);
}
function _recursiveConvert($data, $opts, $depth) {
if ($depth > $opts['max_depth']) {
return '*MAX_DEPTH_REACHED*';
}
// 標量值直接返回
if (is_scalar($data) || is_null($data)) {
return $data;
}
// DateTime 物件格式化
if ($data instanceof DateTime) {
return $data->format($opts['datetime_format']);
}
// 實作 Traversable(集合、生成器、資料庫結果集)
if ($data instanceof Traversable) {
$data = iterator_to_array($data, $opts['preserve_keys']);
}
// 物件轉陣列
if (is_object($data)) {
// 避免遞迴:若已處理過(用 spl_object_id 標記)
static $visited = [];
$oid = spl_object_id($data);
if (isset($visited[$oid])) {
return '*CIRCULAR_REF*';
}
$visited[$oid] = true;
$data = get_object_vars($data);
}
// 陣列遞迴
if (is_array($data)) {
$result = [];
foreach ($data as $key => $value) {
$result[$key] = _recursiveConvert($value, $opts, $depth + 1);
}
return $result;
}
return $data;
}
?>
| 框架/庫 | 方法 | 特點 |
|---|---|---|
| Laravel | collect($obj)->toArray() / $obj->toArray() (Model) | Collection 強大、支援鏈式操作、自動處理關聯 |
| Symfony | PropertyAccess::createPropertyAccessor() | 屬性存取器、支援 getter/setter、序列化器整合 |
| ThinkPHP 5/6 | collection($obj)->toArray() / model->toArray() | 內建集合類、模型自動轉換 |
| Yii 2 | ArrayHelper::toArray($obj) | 支援自定義轉換規則、關聯展開控制 |
| 原生 PHP | json_decode(json_encode($obj), true) | 簡潔但效能較差、DateTime 變字串、private 屬性丟失 |
<?php
$obj = new stdClass();
$obj->public = 'ok';
$obj->private = 'secret'; // 實際上 PHP 物件無 private 動態屬性,這裡指類別定義的 private
class Test {
public $pub = 'pub';
private $pri = 'pri';
protected $pro = 'pro';
}
$t = new Test();
// json_encode 只能獲取 public 屬性!
echo json_encode($t); // {"pub":"pub"} → private/protected 丟失!
// get_object_vars 同樣只能獲取可見屬性(呼叫上下文決定)
// 類別內部可獲取 private/protected,外部只能獲取 public
?>
結論: 簡單場景用 json_decode(json_encode($obj), true) 最快;複雜/生產環境請用框架內建或上文遞迴函數,確保完整性與型別正確。
->toArray()、ThinkPHP Db::query())。