Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

ghiath.dev's avatar

Revert changes in saving Eloquent hook

Is there anyway to revert the changes in the saving hook for a model if some condition appears then throw an exception?

 protected static function boot()
    {
        static::saving(function ($model) {
            if (someCondition()) {
				revert();
                throw new \Exception();
            }
        });

        parent::boot();
    }

I have noticed that even i throw an error there the changes are committed after that.

0 likes
32 replies
Sinnbeck's avatar

What is revert(); ?

Did you try \DB::rollBack();

ghiath.dev's avatar

@Sinnbeck this is just a way to explain how i want it to be ... there is a someCondition() up there also

ghiath.dev's avatar

@Sinnbeck Same results...

This is test code i use

try {
		$this->expectException(CustomException::class);
		$model->change();
} catch (\Exception $exception) {
// assertions
		throw $exception;
}
ghiath.dev's avatar

@Sinnbeck It is a unit test for a function in the model itself which makes some updates using the ->update method

Sinnbeck's avatar

@ghiath.dev I dont think ->update() calls ->save() on the model, if its a query. Only if its on a single instance.

So could you show the code?

Sinnbeck's avatar

@ghiath.dev I just tested

Added this to the User model

   protected static function booted()
   {
       static::saving(function ($user) {
           throw new \Exception();
       });
   }

This does not trigger an exception

Usere::where('id', 1)->update(['password' => 'foo']);

This does (and does not persist anything to the database)

$user = User::first();
$user->update([
        'password' => 'fooasd',
    ]);

Tested in laravel 8, but I can test it in 9 in case it was changed.

1 like
Sinnbeck's avatar

@ghiath.dev Yeah calling ->update() directly on the model should work. It does for me. I just tested in a closure route.

Sinnbeck's avatar

I just tested it again with catching the exception

Route::get('test', function () {
    $user = \App\User::first();
    try {
        $user->update([
            'domain' => 'fooasd',
        ]);
    }
    catch (\Exception $e) {
        return 'foooo';
    }
    return view('welcome');
});

And it still works as excepted. Unless you can give me a way to replicate the error, I don't think I can help much. Does it work for you if you test with the code I just posted?

ghiath.dev's avatar

@Sinnbeck Event is not firing when the updating from a query and that very interesting point.

I have tested on the model and it is working now

ghiath.dev's avatar

Also there is a need to mention that model keeps the changes on its attributes even the exception is thrown.

Since i am catching the error and continue on the same model it shows that attributes are changed, I had to refresh the model inside the saving hook or resetting it like

$model->setRawAttributes($model->getRawOriginal(), true);

I think it is better cause the refresh method makes a not necessary query to the database

Would you agree?

Sinnbeck's avatar

@ghiath.dev I don't think a single request to get the data reset is bad, but if your solution works, then that is most likely fine.

If the problem is solved, please mark a best answer to set the thread as solved

kokoshneta's avatar

@ghiath.dev The fact that this won’t work with a query is mentioned in the docs:

When issuing a mass update via Eloquent, the saving, saved, updating, and updated model events will not be fired for the updated models. This is because the models are never actually retrieved when issuing a mass update.

tykus's avatar

You have not started the SQL query yet, so rolling back a transaction is not relevant.

Since you are in the saving Model event; you have access to the original attributes on the model which you could reset as the Model's attributes. However, you can simply throw the Exception instead which will prevent the query every executing.

 protected static function boot()
 {
    parent::boot();
    static::saving(function ($model) {
        throw_if (someCondition(),  new \Exception());
    });
}
ghiath.dev's avatar

@tykus As i mentioned before throwing the exception here still commit the changes

tykus's avatar

@ghiath.dev is someCondition true; is the Exception actually thrown?

Anyway, to prevent saving, you can return false in the Closure.

ghiath.dev's avatar

@tykus Yes i can catch it somewhere else... thats why returning false is not a solution; It wont tell there is a logic error there

tykus's avatar

@ghiath.dev what happens when you catch the Exception? If the Exception was thrown, I would expect that the saving event would be stopped

ghiath.dev's avatar

@tykus Nope.

The saving event stops i think but it doesn't stop the full process; I mean saving the new values

tykus's avatar

@ghiath.dev so the model event must return exactly false to stop the save

        if ($this->fireModelEvent('saving') === false) {
            return false;
        }

But I don't understand why the operation would continue past a thrown exception.

tykus's avatar

@ghiath.dev if it explicitly returns false, it does stop the save process, yes. The === in the code I shared above means void will not stop the save process. Check out the HasEvents trait

ghiath.dev's avatar

@tykus It leaves the code without any exceptions thrown and that's not what i want to.

I use this model function in a controller that catch the exception and return the message for the user.

tykus's avatar

@ghiath.dev in that case, I think the Model Event is not the correct place to throw the exception

Please or to participate in this conversation.