__get和__set仅在访问不可见(private/protected)或未声明属性、无同名public属性、且方法存在且为public时触发;误用于public属性无效,易致无限递归或类型错误;需配合__isset/__unset才能支持isset/unset;隐性成本高,建议优先使用显式getter/setter。

PHP 中的 __get 和 __set 不是“重载”——它们不覆盖已有属性访问,而是兜底拦截对**不可见(private/protected)或不存在**属性的读写操作。用错场景或忽略约束,轻则逻辑静默失效,重则引发无限递归或类型错误。
什么时候 __get/__set 才会被触发?
只在以下条件**同时满足**时才调用:
- 访问的属性名在当前作用域不可见(
private或protected),或根本未声明; - 没有显式定义同名的 public 属性;
- 类中已声明
public function __get($name)和/或public function __set($name, $value); - 方法必须是
public,否则 PHP 直接报致命错误(Fatal error: Cannot access private method)。
常见误判:给 public $data 赋值却期望触发 __set ——不会触发,因为访问的是可见属性本身。
__get 和 __set 的参数与返回值陷阱
__get 只接收一个参数 $name,必须返回值;__set 接收两个参数 $name 和 $value,无返回要求。但容易踩的坑包括:
立即学习“PHP免费学习笔记(深入)”;
-
__get中若直接return $this->$name(而$name恰好是 private 属性),会再次触发__get,导致无限递归; -
__set写入时若不做类型校验,可能把字符串塞进预期为数组的字段,后续foreach报 Warning; - 若
__get返回null,而调用方用了 strict_types=1,又没做空值检查,可能引发 TypeError。
安全写法示例:
public function __get($name)
{
if ($name === 'email') {
return $this->_email ?? '';
}
throw new \InvalidArgumentException("Property {$name} is not accessible");
}
public function __set($name, $value)
{
if ($name === 'email' && is_string($value)) {
$this->_email = filter_var($value, FILTER_SANITIZE_EMAIL);
return;
}
throw new \InvalidArgumentException("Cannot assign to property {$name}");
}
和 __isset/__unset 配合使用的必要性
仅靠 __get/__set 无法让 isset($obj->email) 或 unset($obj->email) 正常工作——这两个操作会分别尝试调用 __isset 和 __unset。如果没实现,isset 对私有属性永远返回 false,unset 则静默失败。
-
__isset($name)应返回 bool,决定isset()结果; -
__unset($name)用于清理内部存储,比如从缓存数组中移除键; - 三者行为需保持语义一致:若
__isset('x')返回 true,则__get('x')不应抛异常或返回 null(除非业务明确允许空值)。
真正难的不是写这两个方法,而是判断该不该用:一旦引入 __get/__set,IDE 自动补全失效、静态分析工具(如 PHPStan)难以推导属性类型、调试时 var_dump 看不到实际字段——这些隐性成本远高于语法糖带来的便利。除非你在做 ORM 映射、DTO 封装或兼容弱结构数据源,否则优先用显式 getter/setter 方法。



















