Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Laravel Observers - updated triggers itself endlessly and produces a bcrypt error

Tags:

php

laravel

I am facing an error that I would like to see explained. I have an UserObserver and every time a user gets updated and the active field updates to true a new password is generated and a welcome email is sent.

The function looks like this.

    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))
                )
            );
        }
    }

As you can see in the if statement there is a check to see if the user is active and if the database field has been changed (isDirty()). If this is true a new password is being generated, hashed with bcrypt and then send to the user via notifications. (mail)
As expected the password update triggers the method again, but now the isDirty('active) should return false. This does not happen, it returns true through all the iterations. After the PHP max execution time has been reached I get the following error:

[Fri Jan 11 09:13:13 2019] PHP Fatal error: Maximum execution time of 60 seconds exceeded in app/src/vendor/laravel/framework/src/Illuminate/Hashing/BcryptHasher.php

After enabling xdebug the following exception is thrown. (as expected)

PHP Error: Maximum function nesting level of '256' reached, aborting! in /home/ilyas/script/clockwork/app/src/vendor/laravel/framework/src/Illuminate/Support/Collection.php on line 1971

From this issue, which can be solved easily, I have 2 questions.

Why does bcrypt thow an error after the max execution time has been reached?

Why does $user->isDirty('active') return true throughout after every update in this loop although the last update in the observer did not update the active field?

As asked by Mozammil $user->getDirty() returns this the first time the updated method is triggered.

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

From the second time it returns until timeout is reached:

array(3) {
  'password' =>
  string(60) "$2y$10$rlAbpelKnT/yp5zFhXcjwelEKkDEx5SfNJWqL1LiDltRnHYBLINmK"
  'active' =>
  bool(true)
  'updated_at' =>
  string(19) "2019-01-11 11:27:13"
}
like image 488
Odyssee Avatar asked Oct 15 '25 19:10

Odyssee


1 Answers

Thanks to Dries Vints and Jonas Staudenmeir for providing an answer on GitHub.

From DriesVints:

Well, think about it. The "updated" event happens after your model was updated. So any changes you made on your model are bound to still be picked up by the isDirty call. The fact that $user->active returns true is indeed because it was changed to true from the original update. The original changes aren't cleared or anything. Since you are constantly referencing the same object being passed to the updated method this is the expected behaviour.

From Jonas Staudenmeir

This is happening because the updated event is fired before syncOriginal() is called.

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

like image 178
Odyssee Avatar answered Oct 18 '25 08:10

Odyssee



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!