__get和__set仅在属性不可见或未声明且无同名public属性时触发,非属性重载;需配合__isset和__unset才能完整支持isset/empty/unset操作。

__get 和 __set 不是“属性重载”,而是不可访问属性的兜底拦截器——只在读写失败时才介入,不替代常规属性访问。
__get/__set 什么时候真正被调用?
它们只在以下全部条件满足时触发:
- 访问的属性名在当前作用域不可见(
private或protected),或根本未声明; - 类中没有同名的
public属性; -
__get或__set方法存在、且为public; - 不是通过反射、
var_dump或序列化等绕过访问控制的场景。
常见误判:给 public $data 赋值却期待触发 __set——不会触发,因为 PHP 直接操作了可见属性本身。同样,isset($obj->email) 对私有属性永远返回 false,除非你实现了 __isset。
为什么直接 $this->$name = $value 在 __set 里会崩?
这是最典型的无限递归陷阱:__set 内部若执行 $this->$name = $value,而 $name 又恰好是未声明/不可见属性,PHP 会再次尝试调用 __set,形成死循环。
立即学习“PHP免费学习笔记(深入)”;
安全做法是用中间容器存值:
private $data = [];<br>public function __set($name, $value) {<br> if (!in_array($name, ['title', 'slug'])) {<br> throw new \InvalidArgumentException("Cannot set property '{$name}'");<br> }<br> $this->data[$name] = trim((string)$value);<br>}
注意:__get 必须返回值,不能留空;返回 null 虽合法,但在 strict_types=1 下容易引发 TypeError,建议显式校验并抛异常或返回默认值。
__get/__set 单独用够吗?
不够。仅靠这两个方法,isset($obj->email) 和 unset($obj->email) 仍无法正常工作:
-
isset()会跳过__get,直接查属性是否存在,对私有/未声明属性恒返回false; -
unset()对不可访问属性静默失败,不报错也不触发任何方法; - 必须同时实现
__isset($name)和__unset($name)才能完整支持这四个操作。
例如:__isset 应检查 $this->data 是否含该键,而非依赖 isset($this->$name)——后者又可能触发 __get,造成逻辑混乱。
真正难的不是写出来,而是判断“这个属性到底该不该走魔术方法”。一旦在 public 属性和魔术方法之间混用,或者忘记补全 __isset/__unset,问题就藏得深、报错不明显、调试成本陡增。



















