Laravel 观察者 - updated 方法无限触发并导致 bcrypt 错误

3
我遇到了一个错误,希望能够得到解释。我有一个UserObserver,每当用户更新并且active字段更新为true时,就会生成一个新密码并发送欢迎电子邮件。
该函数如下所示:
    public function updated (User $user)
    {
        if ($user->active && $user->isDirty('active')) {
            $password = generatePassword();
            $user->password = bcrypt($password);
            $user->save();

            $user->notify(
                new UserWelcomeNotification(
                    $user->email,
                    $password,
                    new MailResource(Email::getMailInfo(23))
                )
            );
        }
    }

如您在if语句中所看到的,有一个检查用户是否处于活动状态并且数据库字段是否已更改(isDirty())。如果为真,则会生成一个新密码,使用bcrypt进行哈希处理,然后通过通知(邮件)发送给用户。
预计密码更新将再次触发该方法,但现在isDirty('active)应该返回false。这没有发生,它通过所有迭代都返回true。在达到PHP最大执行时间之后,我收到以下错误:

[Fri Jan 11 09:13:13 2019] PHP致命错误:app/src/vendor/laravel/framework/src/Illuminate/Hashing/BcryptHasher.php中超过60秒的最大执行时间

启用xdebug后,将抛出以下异常。(预期)

PHP错误:在/home/ilyas/script/clockwork/app/src/vendor/laravel/framework/src/Illuminate/Support/Collection.php第1971行中达到了256个最大函数嵌套级别,正在中止!

从这个可以轻松解决的问题中,我有两个问题。

为什么在达到最大执行时间后bcrypt会抛出错误?

为什么在此循环中每次更新后$user->isDirty('active')都返回true,尽管观察者中的最后一个更新未更新active字段?

如Mozammil所要求的那样,$user->getDirty()在第一次触发更新方法时返回这个。

array(2) {
  'active' =>
  bool(true)
  'updated_at' =>
  string(19) "2019-01-11 11:27:13"
}

从第二次返回开始直到超时时间到达:
array(3) {
  'password' =>
  string(60) "$2y$10$rlAbpelKnT/yp5zFhXcjwelEKkDEx5SfNJWqL1LiDltRnHYBLINmK"
  'active' =>
  bool(true)
  'updated_at' =>
  string(19) "2019-01-11 11:27:13"
}

在更新事件中不应该更新同一模型,否则会陷入无限循环。 - sumit sharma
@sumitsharma 那不是我的问题。我在问题中已经说过它可以很容易地被修复。bcrypt抛出错误和isDirty('active')仍然返回true的事实才是问题所在。 - Odyssee
我很好奇。你能否执行 dd($user->getDirty()) 以便我们知道 Laravel 认为哪些值是脏的? - Mozammil
@Mozammil 我已经更新了我的问题,并附上了 getDirty() 的输出。 - Odyssee
我正在查看API。我很难理解isDirty()wasChanged()之间的区别。我认为$user->wasChanged('active')可能会解决这个问题。但是,我需要自己尝试一下。 - Mozammil
显示剩余5条评论
1个回答

1

感谢Dries Vints和Jonas Staudenmeir在GitHub上提供的答案。

DriesVints说:

好吧,想一想。"updated"事件发生在您的模型更新之后。因此,您对模型所做的任何更改都必须仍然被isDirty调用捕获到。$user->active返回true的事实确实是因为它从原始更新中更改为true。原始更改并没有被清除或任何其他操作。由于您不断引用传递给更新方法的相同对象,这是预期的行为。

来自Jonas Staudenmeir的话:

这是因为在调用syncOriginal()之前触发了更新事件。

https://github.com/laravel/framework/issues/27138


谢谢。它确实有效 :) - Mozammil
2
他们的回答太糟糕了。一个好的回答应该是像这样的:“只需在观察者更新中包含'this'即可使其不触发自身”。 - CptMisery
3
这是解决问题的真正答案:https://dev59.com/L10b5IYBdhLWcg3wQPVN#51301753 unsetEventDispatcher() - CptMisery
1
@CptMisery,我不知道那个方法,谢谢! - Odyssee
如何停止这个问题。我遇到了同样的问题,即使我停止了服务器,它仍然会发送电子邮件。 - Yasir Ijaz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接