__unset 方法仅在对对象的可访问属性显式调用 unset($obj->prop) 且该属性为 private/protected 或通过 __get/__set 模拟时触发;public 属性直接删除,不调用它。

PHP __unset 方法什么时候会被调用
它只在你对一个对象的**可访问属性**显式使用 unset() 时触发,且该对象必须定义了 __unset 魔术方法。不是所有 unset 操作都会进这里——比如 unset 局部变量、数组元素、或对不可访问属性(private/protected 且无魔术方法)的操作,完全绕过它。
常见错误现象:写了 __unset 却没反应,大概率是因为你在 unset 一个不存在的属性名、或该属性本就不可写(比如没定义 __set 又试图先 set 再 unset)、又或者对象是 stdClass 这类原生类,不支持自定义魔术方法。
- 必须是
unset($obj->prop)这种形式;unset($obj['prop'])走的是ArrayAccess或__get/__set,和__unset无关 - 如果属性本身存在且 public,PHP 默认直接删除,不会触发
__unset—— 除非你把属性声明为 private/protected,或用__get/__set模拟了“动态属性”逻辑 - 类中未定义
__unset,但执行unset($obj->xxx),PHP 不报错也不调用任何东西(静默忽略),这点容易误以为“生效了”
为什么 __unset 里不能直接用 unset($this->$name)
因为这会再次触发 __unset,造成无限递归。PHP 在内部做了保护,第二次进入时直接跳过,但行为不可靠,且 PHP 8.0+ 会抛出 Fatal error: Uncaught Error: Cannot unset property 类似错误。
典型使用场景是代理属性、缓存清理、或记录日志。你需要把“真实存储”和“对外接口”分开,比如用私有数组存数据:
立即学习“PHP免费学习笔记(深入)”;
class Container {
private $data = [];
public function __set($name, $value) {
$this->data[$name] = $value;
}
public function __get($name) {
return $this->data[$name] ?? null;
}
public function __unset($name) {
unset($this->data[$name]); // ✅ 安全:操作的是私有数组,不触发魔术方法
}
}
- 别在
__unset里碰$this->$name,尤其是 public 属性,哪怕它看起来“只是个变量” - 如果你用
__set/__get做属性代理,__unset必须对应操作同一底层容器(如数组、SplObjectStorage) - PHP 7.4+ 支持属性类型声明,但
__unset对 typed property 无效——unset 一个 typed property 会直接报Cannot unset typed property错误,根本进不了__unset
__unset 和 __set/__get 的配合陷阱
三者不配对使用时,行为会割裂。比如只定义 __set 和 __unset 但没写 __get,那 isset($obj->x) 或 empty($obj->x) 就可能返回意外结果,因为它们底层依赖 __get 是否返回非 null 值。
-
isset($obj->x)在有__get时,会调用__get并检查返回值是否为 null;没__get则直接查属性是否存在 → 所以__unset删除后,isset行为取决于有没有__get - 如果你用
__unset清理缓存,但忘了同步更新__get中的判断逻辑(比如缓存键还在但值已删),就会返回 stale 数据 - PHP 8.2 引入了
__unserialize,但它和__unset无直接关系;反倒是反序列化后若立刻unset某属性,仍会正常触发__unset
兼容性与性能注意点
__unset 自 PHP 5.1 起可用,所有现代版本都支持,但它的开销比直接操作 public 属性高一截——每次调用都涉及方法查找、栈帧创建、参数绑定。高频场景(如循环内 unset)建议避免。
- 它不会被
foreach遍历时的引用赋值触发;foreach($obj as &$v)中修改$v不等于 unset 属性 - JSON 序列化(
json_encode)默认只序列化 public 属性,不调用__unset;但如果你重写了JsonSerializable,就得自己控制哪些字段该被排除 - 测试时容易漏掉:用
property_exists($obj, 'x')查属性存在性,它只看类定义和实际属性,不走__get/__set,所以即使__unset已清理数据,property_exists仍可能返回 true(如果属性在类中声明过)
真正难搞的不是怎么写 __unset,而是搞清你到底想“删数据”还是“删访问入口”,以及上下游代码是否依赖属性存在性判断。稍不注意,unset 就变成“假装删了”。











