Laravel密码重置触发访问器getAttribute() on null报错
问题根因
报错核心原因是自定义用户提供者的retrieveByCredentials方法逻辑错误:你把凭证匹配条件错误加在了with()预加载的关联过滤闭包中,没有对主查询的用户做任何匹配筛选。
你当前写的条件拼接逻辑执行效果如下:
$query->with([$this->foreign_model => function ($q) use ($key, $value) { $q->where($key, $value); }]);
- 主查询
users表没有加任何筛选条件,会直接取表中第一条用户记录 - 预加载
person关联时才会带上where email = 传入邮箱的过滤条件 - 如果取到的第一条用户对应的person邮箱和传入邮箱不匹配,预加载返回的
person结果就是null,后续访问$this->person->getAttribute()就会触发你看到的Call to a member function getAttribute() on null报错。
这个逻辑完全可以解释你遇到的所有现象:
- 手动执行
User::find({id})->email正常:直接指定ID查询对应用户,没有给person预加载加过滤条件,关联可以正常加载 - seeder生成的测试账号重置密码正常:大概率seeder生成的第一条用户刚好就是你测试用的账号,预加载person时where条件刚好匹配,关联不会返回null
- 传入不存在的邮箱必报错:所有用户的person都不匹配邮箱条件,预加载结果永远为null
- 传入真实存在的邮箱也报错:查询返回的是users表第一条用户,不是对应邮箱的用户,只要第一条用户的person邮箱和传入值不一致就会报错
修复方案
把关联匹配逻辑调整为:先用whereHas筛选出关联表匹配凭证条件的用户,再无过滤预加载person关联即可,修改retrieveByCredentials的条件循环部分代码:
foreach ($credentials as $key => $value) { if (str_contains($key, 'password')) { continue; } if (is_array($value) || $value instanceof Arrayable) { // 先通过关联条件筛选匹配的主用户记录 $query->whereHas($this->foreign_model, function ($q) use ($key, $value) { $q->whereIn($key, $value); }); // 正常预加载关联,不要给预加载结果加过滤 $query->with($this->foreign_model); } elseif ($value instanceof Closure) { $value($query); } else { // 单值匹配逻辑同上 $query->whereHas($this->foreign_model, function ($q) use ($key, $value) { $q->where($key, $value); }); $query->with($this->foreign_model); } }
额外优化建议
给访问器加空值兜底,避免极端场景下关联数据异常时抛出致命错误:
public function getEmailAttribute() { return $this->person?->getAttribute('email') ?? ''; } public function getFirstNameAttribute() { return $this->person?->getAttribute('firstName') ?? ''; } public function getLastNameAttribute() { return $this->person?->getAttribute('lastName') ?? ''; }
内容的提问来源于stack exchange,提问作者julianatkin
相关产品推荐
相关产品推荐

