PHP递归转对象为数组需分三层:基础版仅处理公有属性;增强版用Reflection读取私有/保护属性并清理键名;生产版加spl_object_id防循环引用,支持引用缓存与回溯释放。

<p>PHP中递归将对象转为数组,核心是判断属性是否为对象或数组,再逐层展开。关键要处理循环引用、私有/保护属性、不可访问属性等边界情况。</p>
<h3>基础递归函数(支持公有属性)</h3>
<p>最简版本只处理公有属性,适合标准对象:</p>
<font color="#888">
<pre class="brush:php;toolbar:false;">
function objectToArray($obj) {
if (!is_object($obj) && !is_array($obj)) {
return $obj;
}
if (is_object($obj)) {
$obj = (array)$obj;
}
return array_map('objectToArray', $obj);
}
说明:
• (array)$obj 强制转换会把公有属性转为键值对,私有属性变成 \0Classname\0prop 格式,保护属性变成 \0*\0prop;
• array_map 保证嵌套结构也递归处理;
• 不处理资源、闭包、循环引用,仅作快速原型用。
增强版(兼容私有/保护属性 + 过滤非法键名)
使用反射(ReflectionObject)安全读取所有属性,同时清理特殊键名:
function objectToArray($obj, $recursive = true) {
if (!is_object($obj)) {
return $obj;
}
$arr = [];
$reflection = new ReflectionObject($obj);
foreach ($reflection->getProperties() as $prop) {
$prop->setAccessible(true); // 允许读取私有/保护属性
$key = $prop->getName();
$value = $prop->getValue($obj);
// 清理 PHP 自动添加的不可见字符(如 \0ClassName\0prop)
$key = preg_replace('/^\x00.*?\x00/', '', $key);
if ($recursive && (is_object($value) || is_array($value))) {
$arr[$key] = objectToArray($value, true);
} else {
$arr[$key] = $value;
}
}
return $arr;
}
说明:
• ReflectionProperty::setAccessible(true) 是读取非公有属性的必要步骤;
• preg_replace 去掉序列化键名中的空字节前缀,让结果更干净;
• 支持传入 $recursive = false 关闭深层递归,用于调试首层结构。
生产环境推荐:防循环引用 + 类型过滤
真实项目需避免无限递归(如对象 A → B → A),可用引用缓存控制:
function objectToArray($obj, &$visited = []) {
if (!is_object($obj)) {
return $obj;
}
$objId = spl_object_id($obj);
if (isset($visited[$objId])) {
return null; // 或 throw new Exception('Circular reference detected');
}
$visited[$objId] = true;
$arr = [];
$reflection = new ReflectionObject($obj);
foreach ($reflection->getProperties() as $prop) {
$prop->setAccessible(true);
$key = preg_replace('/^\x00.*?\x00/', '', $prop->getName());
$value = $prop->getValue($obj);
if (is_object($value)) {
$arr[$key] = objectToArray($value, $visited);
} elseif (is_array($value)) {
$arr[$key] = array_map(function($v) use (&$visited) {
return is_object($v) ? objectToArray($v, $visited) : $v;
}, $value);
} else {
$arr[$key] = $value;
}
}
unset($visited[$objId]); // 回溯时释放,允许多路径访问同一对象
return $arr;
}
说明:
• spl_object_id() 提供轻量唯一标识,比 serialize() 更高效;
• &$visited 按引用传递,记录已遍历对象,防止死循环;
• unset($visited[$objId]) 在返回前清除,支持类似树形结构中重复对象被多次引用的场景;
• 对数组元素中的对象也做递归,保持一致性。
使用示例与注意事项
测试代码:
class User {
public $name = 'Tom';
protected $age = 25;
private $token = 'abc123';
public $profile = null;
public function __construct() {
$this->profile = (object)['city' => 'Beijing'];
}
}
$user = new User();
print_r(objectToArray($user));
输出包含 name、age、token、profile(已转为数组),且 profile.city 可达。
注意:
• 静态属性不会被反射获取,该函数不处理;
• 资源(resource)、闭包(Closure)、DOMDocument 等特殊类型建议提前判断并跳过或转字符串;
• Laravel 的 toArray() 或 Symfony 的 Normalizer 更适合复杂场景,自定义函数适合轻量需求。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











