__get和__set仅在访问未声明的public属性时触发;访问已定义public属性、private/protected属性不经过它们;二者非动态属性注册器,需手动存储值;不应抛异常以免破坏isset()等行为;性能较低且削弱类型与IDE支持,适用于外部数据映射等特定场景。

__get 和 __set 什么时候会被触发
它们只在访问**不存在的 public 属性**时才被调用。不是所有属性访问都会进这里,private/protected 属性直接报错或走其他魔术方法,已定义的 public 属性则完全绕过这两个方法。
- 写
$obj->undefined_prop→ 触发__get('undefined_prop') - 写
$obj->undefined_prop = 'val'→ 触发__set('undefined_prop', 'val') -
$obj->exists_public(该属性已声明)→ 不触发,直接读值 -
$obj->private_prop→ 致命错误:Cannot access private property
__get/__set 不能替代属性声明
它们只是“兜底”,不是动态属性注册器。PHP 不会因为你用了 __set 就自动把变量挂到对象上;不显式保存,下次 __get 还是拿不到。
- 常见错误:在
__set里只做校验或日志,却没存值 → 后续__get返回null或报 notice - 正确做法:通常配合一个私有容器数组,比如
$this->data['name'] = $value - 注意键名大小写:如果类里有
$name和$Name,__get('name')和__get('Name')是两个不同调用
别在 __get/__set 里 throw Exception
PHP 的属性访问语法本身不承诺异常安全。一旦 __get 抛出异常,你无法用 isset() 或 empty() 安全判断——它们会直接中断执行,而不是返回 false。
-
isset($obj->missing)→ 如果__get抛异常,整个脚本崩,不是返回false - 更稳妥的做法:在
__get里返回null或默认值,在业务层用空值判断逻辑 - 真要报错,建议用
trigger_error(..., E_USER_NOTICE),至少不打断控制流
性能和可读性代价容易被低估
每次访问都多一层函数调用 + 字符串键查找,比直接属性访问慢 3–5 倍(简单基准测试可见)。更麻烦的是调试时 IDE 找不到属性定义,类型提示失效,重构困难。
立即学习“PHP免费学习笔记(深入)”;
- 适合场景:封装外部数据源(如 JSON 结构映射)、字段级权限控制、惰性加载
- 不适合场景:普通业务模型字段、高频访问的计数器、需要严格类型约束的地方
- 替代思路:用具体 getter/setter 方法(如
getName()),IDE 可跳转、类型系统可覆盖、调用意图明确
真正难的不是写对这两个方法,而是想清楚:这个属性到底该“存在”还是“假装存在”。一旦开始用 __get,你就放弃了 PHP 对属性的静态语义支持。



















