__get和__set仅在访问未声明或不可见(private/protected)的属性时触发,不拦截public属性或已定义私有属性;需配合__isset和__unset才能完整支持动态属性操作。

__get 和 __set 什么时候会被触发
当访问一个 private 或 protected 属性,且该属性在类中**不存在**或**不可见**时,__get 和 __set 才会被调用。它们不会拦截 public 属性的直接读写,也不会对已定义的私有属性自动生效——除非你显式把它 unset 或不声明。
常见错误现象:Notice: Undefined property 依然出现,说明没进 __get;原因通常是属性被声明为 public,或类里已有同名私有属性但拼写/大小写不一致。
- 只对“不可访问属性”起作用(即语言层面拒绝直接访问的那些)
- 若属性存在但只是
private,而你又没在类里定义它,__get/__set不会接管——必须是“完全未声明”或用unset($this->prop)主动移除后才触发 - 继承链中,父类的
__get不会自动代理子类新增的私有属性访问,得各自实现或统一委托
__set 的参数和赋值逻辑怎么写才安全
__set 接收两个参数:$name(属性名)和 $value(要赋的值)。它本身不存储数据,你要自己决定存哪、怎么校验、是否允许写入。
典型误用:在 __set 里直接给 $this->$name = $value,结果触发无限递归(因为又尝试写一个不存在的属性,再次进入 __set)。
- 必须用
protected $data = [];这类容器来存动态值,避免直接赋值触发重入 - 建议加白名单或类型检查,比如只允许设置
['title', 'content'],其他名直接throw new InvalidArgumentException - 如果想支持“仅读属性”,在
__set里判断$name === 'id'就抛异常,比靠注释更可靠
public function __set($name, $value)
{
if (!in_array($name, ['title', 'slug'])) {
throw new \InvalidArgumentException("Cannot set property '$name'");
}
$this->data[$name] = trim((string) $value);
}
__get 返回 null 还是抛异常,取决于你的 API 约定
__get 必须返回一个值,不能 void。返回 null 很常见,但容易掩盖问题:调用方可能把 null 当成“有值但为空”,而非“根本没这个字段”。
使用场景差异明显:做 DTO(数据传输对象)时倾向返回 null;做配置代理或领域模型时,更推荐对未知属性抛 Exception,防止拼写错误静默失败。
- 查
$this->data[$name]前务必用isset()或array_key_exists(),否则触发 notice - 不要在
__get里做 heavy operation(如 DB 查询),它可能被频繁调用,且开发者通常不预期 getter 有副作用 - 若需延迟加载,至少加个
static $loaded = [];缓存,避免重复初始化
public function __get($name)
{
if (!array_key_exists($name, $this->data)) {
throw new \RuntimeException("Property '$name' is not defined or has no value");
}
return $this->data[$name];
}
与 __isset 和 __unset 配合才能真正闭环
只实现 __get/__set 是不够的。empty($obj->foo) 或 isset($obj->foo) 不会走 __get,而是走 __isset;unset($obj->foo) 则走 __unset。缺一个,行为就不一致。
容易踩的坑:用 empty() 判断一个动态属性,结果始终为 true,因为没实现 __isset,PHP 默认返回 false。
-
__isset应该和__get使用同一套存在性判断逻辑(比如都查$this->data) -
__unset要负责从你的存储容器中删掉对应键,而不是什么都不做 - 四个方法(
__get/__set/__isset/__unset)最好一起实现,否则使用者会困惑为什么有些操作有效、有些无效
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











