PHP获取对象属性的三种方法实例分析

发布时间:2026/10/10 3:41:17
PHP获取对象属性的三种方法实例分析 前言三种方法通常指的是直接访问-、get_object_vars()、(array)强制类型转换。此外还有foreach遍历对象、反射Reflection读取以及只做存在性判断的property_exists()后面会一并对照。这三者真正的差别不在写法而在两个问题上能不能看到 protected 和 private 属性以及非 public 属性的键名长什么样。搞不清这两点就会写出明明有密码字段却打印不出来或数组键名怎么带了个乱码前缀这类困惑。三个常见误解先纠正。误解一get_object_vars()能拿到私有属性。它是按调用作用域返回的从类外部调用只返回 public 属性只有在类的方法内部调用才能看到该类自己的 protected 和 private 属性。这是手册明确写过的行为自 PHP 5.0.3 起不是实现细节。误解二(array)转换和get_object_vars()等价。不等价。(array)会返回全部属性含 private、protected但非 public 属性的键名里会被塞进 NUL 字节前缀看起来像乱码。误解三isset()能判断属性是否存在。isset()判断的是存在且不为 null。属性确实存在但值是null时它返回false判断声明了没有要用property_exists()。一、直接访问从句点到可见性规则$obj-prop是最直接的方式受可见性约束只能访问 public 属性。访问不存在的属性、或者在类外部访问非 public 属性行为在几个版本间变化过值得记牢。情况PHP 7.xPHP 8.0 起读取不存在的属性Notice: Undefined propertyWarning: Undefined property类外访问 private/protected致命错误抛出可捕获的Error给不存在属性赋值动态属性允许允许但 PHP 8.2 起废弃PHP 8.0 引入的 nullsafe 操作符?-可以省掉一串判空它在链条中任一环为null时直接整体返回null?php // 需要 PHP 8.0 及以上declare(strict_types1);final class Address{public function __construct(public string $city){}}final class User{public function __construct(public string $name,public ?Address $address null,) {}}$user new User(Ada);// PHP 7.x 的写法逐层判断$city null;if ($user-address ! null) {$city $user-address-city;}// PHP 8.0 的写法echo $user?-address?-city ?? 未填写, \n; // 未填写需要提醒的是nullsafe 只对null起作用属性不存在或不可访问仍会报错它并不是属性不存在也不报错的语法糖。二、get_object_vars()作用域决定你能看到多少get_object_vars()的签名是get_object_vars(object $object): array返回该对象在当前作用域下可见的属性。同一个对象在类内部调用和类外部调用结果完全不同。?php // 适用于 PHP 8.0declare(strict_types1);class User{public string $name Ada;public ?string $email null;public int $age 30;protected string $role admin;private string $token tk_9f2c;public static string $table users;/** return arraystring, mixed */public function varsInside(): array{// 在类内部调用protected 与 private 都可见return get_object_vars($this);}}$user new User();echo 类外部调用\n;print_r(get_object_vars($user));echo 类内部调用\n;print_r($user-varsInside());输出类外部调用Array([name] Ada[email] [age] 30)类内部调用Array([name] Ada[email] [age] 30[role] admin[token] tk_9f2c)这里有三个要点。第一静态属性不会出现。public static string $table users;在两次输出里都没有。静态属性属于类而不是实例get_object_vars()只看实例属性。第二__get()不会被触发。如果你的类用魔术方法动态提供属性get_object_vars()不会去问__get()它只返回真实存在的属性。想拿到魔术属性只能显式逐个访问。第三它会把null值正常带出来。上面email的值就是null键也在。这正是它和isset()的分水岭isset($user-email)返回false但email确实存在。另外要注意声明了类型但尚未初始化的属性不是一个可读的值直接访问会抛错?php // 适用于 PHP 7.4declare(strict_types1);final class Profile{public string $nickname; // 声明了类型但没有默认值}$p new Profile();var_dump(isset($p-nickname)); // bool(false)未初始化var_dump(property_exists($p, nickname)); // bool(true)声明确实存在$ref new ReflectionProperty(Profile::class, nickname);var_dump($ref-isInitialized($p)); // bool(false)如果直接取$p-nickname会得到Error: Typed property Profile::$nickname must not be accessed before initialization。所以判断一个类型化属性有没有值正确的组合是property_exists()看声明、isset()看是否已初始化且非 null。其余几种取值方式get_object_vars()、(array)转换对未初始化属性的处理在各版本上有过调整实际项目里以下发版本的实测为准更稳妥。三、(array) 转换全部属性与带前缀的键名把对象强制转换成数组会得到所有属性包括非 public 的。代价是键名被改写了protected 属性前面加\0*\0NUL、星号、NULprivate 属性前面加\0类名\0。这里的\0是真正的 NUL 字节echo出来看不见在浏览器里可能显示成一个小方框用编辑器打开也是一堆乱码非常容易被当成编码问题。?php // 适用于 PHP 8.0declare(strict_types1);// 沿用上面的 User 类$user new User();// 把 NUL 字节替换成可见的 \0 两个字方便观察$visible static fn(string $key): string str_replace(\0, \0, $key);foreach ((array) $user as $key $value) {printf([%s] %s\n, $visible($key), var_export($value, true));}输出[name] Ada[email] NULL[age] 30[\0*\0role] admin[\0User\0token] tk_9f2c可以看到role和token都被带出来了键名前缀分别是\0*\0和\0User\0。这个机制的存在意义是同一个类里可能有多个父类声明的同名 private 属性加前缀才能互不冲突。知道了前缀规则就能按需把它们还原成正常名字?php // 适用于 PHP 7.4/** return arraystring, mixed */function objectToArray(object $object): array{$result [];foreach ((array) $object as $key $value) {// 去掉 \0*\0 与 \0类名\0 前缀只保留真正的属性名// 用双引号字符串写 \x00避免正则里 NUL 的转义歧义$clean preg_replace(/^\x00.*\x00/, , (string) $key);$result[$clean ?? (string) $key] $value;}return $result;}注意这个还原是有损的如果父类声明了protected $role子类又声明了同名的private $role不同层级各自的声明两者前缀不同、本来能共存去前缀后就会互相覆盖。绝大多数情况不会这样写但知道这个边界比盲目相信它更安全。其余几种遍历方式的差别可以用一张表统一对照以上面的User对象、从类外部的视角为例方式拿到的属性键名说明$obj-name单个 public——非 public 会抛Errorget_object_vars($obj)当前作用域可见原名类外只有 public不含静态属性(array) $obj全部非 public 带 NUL 前缀含静态属性吗不含静态属性属于类foreach ($obj as $k $v)当前作用域可见原名与类外get_object_vars()范围一致json_encode($obj)仅 public原名想定制需实现JsonSerializable反射ReflectionClass可全都拿到原名需要按属性名逐个取值反射是唯一能既要全量、又要干净名字的通用手段?php // 适用于 PHP 8.1User 类沿用上面的定义declare(strict_types1);$user new User();$ref new ReflectionClass(User::class);foreach ($ref-getProperties() as $prop) {if ($prop-isStatic()) {continue; // 静态属性属于类跳过}// PHP 8.1 起 setAccessible(true) 已不再必要8.0 及更早需要先调用$value var_export($prop-getValue($user), true);printf(%-8s %s\n, $prop-getName(), $value);}ReflectionProperty::isInitialized()存在、getValue()存在、isStatic()存在这些都是稳定的公开 API。相比(array)转换反射的好处是键名干净、可以按可见性过滤代价是代码更长性能上也比数组转换重——所以在只读几个公开属性的场景里直接-才是对的选择。常见坑点❌ 在类外部调用get_object_vars()却期望拿到 private 属性✅ 它按调用作用域返回类外只有 public需要全量就改用反射或(array)转换。❌ 用isset($obj-prop)判断属性是否声明✅ 值为null时isset()返回false判断声明要用property_exists()。❌ 用(array) $obj后把结果直接json_encode()或塞进接口返回键名里的 NUL 字节导致输出奇怪甚至报错✅ 先按前缀规则清洗键名或改用get_object_vars()、反射。❌ 看到(array)结果里出现\0*\0之类的前缀以为是编码问题去改文件编码✅ 那是 PHP 标记 protected 和 private 属性的固定前缀属于正常行为。❌ 用empty($obj-prop)判断属性有没有值✅empty()把0、0、、null、false全判为空要区分开就用isset()或直接比较。❌ 在类外给对象写$obj-newField 1;当作扩展机制✅ PHP 8.2 起对未声明属性动态赋值已废弃需要在类上标#[AllowDynamicProperties]更好的做法是显式声明属性。❌ 直接读取声明了类型但没赋值的属性遇到Error: Typed property ... must not be accessed before initialization却不知道原因✅ 先用isset()或ReflectionProperty::isInitialized()判断是否已初始化。❌ 以为json_encode($obj)会输出全部属性✅ 它只序列化 public 属性需要控制输出内容时实现JsonSerializable接口。总结需求推荐方式版本提示读一个已知的公开属性$obj-prop——链式判空$obj?-a?-bPHP 8.0取当前作用域可见的属性数组get_object_vars($obj)按调用作用域返回取全部属性含非 public(array) $obj键名带 NUL 前缀需清洗取干净名字的全部属性反射 getValue()setAccessible()在 8.1 起已无必要判断属性是否声明property_exists()——判断是否有非 null 值isset()类型化属性未初始化时返回 false控制 JSON 输出JsonSerializable——选哪种方式取决于两件事你要不要看非 public 属性以及你需不需要干净的键名。只想读几个公开字段就用-最直白也最省事需要在类内部做序列化就用get_object_vars($this)它天然带作用域要一次性导出全部状态例如调试、缓存、数据迁移就用反射或(array)转换但记得(array)的键名需要额外处理。最后提醒一句任何把对象转成数组再写进日志或接口的做法都要先确认里面有没有不该外泄的字段密码、token对象转数组并不会自动帮你脱敏。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询