Repository navigation
Fix withoutHook() and add withHooks() for bulk queries - #369
Merged
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Pull Request Checklist
Summary
withoutHook()had several bugs that could change or delete the wrong data, or switch hooks off by accident for unrelated code. This PR fixes it and addswithHooks(), so hooks can run on bulk queries when you ask for it.The problems
withoutHook()was a static method that set one flag shared by the whole PHP process, then returned a query builder. The flag was only switched back on when a model finishedsave(),update(),delete()orrestore().That caused three real bugs:
$user->withoutHook()->update([...])and$user->withoutHook()->delete()look like they act on$user. They don't. The call returns a fresh builder with noWHERE, so every row in the table is updated or deleted. In a test with three rows, the update changed all three and the delete emptied the table.withoutHook()was followed by anything other than a model write (a read, a bulk builder update, an early return), hooks stayed off for the next model write, even on a different model. In a long-running worker this can go unnoticed for a long time.save()orupdateOrCreate()threw an error. The builder has nosave(), so$user->withoutHook()->save()failed withBadMethodCallException.The only safe forms before this PR were two separate statements (
$user->withoutHook(); $user->save();) andModel::withoutHook()->create([...]).What changed
withoutHook()is now per modelhooksSuppressedflag.$user->withoutHook()sets the flag on that instance and returns the same instance. So->save(),->update([...])and->delete()can be chained safely and only touch that one record.User::withoutHook()returns a builder (newBuilder::withoutHook()). Every model the builder creates or loads throughcreate(),find(),first()orget()skips hooks for its next write.updateOrCreate()andfirstOrCreate(), soUser::withoutHook()->updateOrCreate(...)keeps working.save(),update(),delete()andrestore(), including the early returns that used to leak it.New:
withHooks()A plain query builder write is a single SQL statement, so no model is involved and no hooks run. With
withHooks(), the builder loads the matching records in chunks of 500 (by primary key) and calls each model's ownupdate(),delete(),restore()orforceDelete(). Hooks, watches and temporal snapshots then run for every record.update(),delete(),restore()andforceDelete().WHEREafter the update are not skipped, because the chunking walks the primary key.withHooks(), nothing changes. Plain builder writes stay one fast query.Behavior changes to know about
User::withoutHook(); User::create([...]);no longer skips hooks. The first call now returns a builder that is thrown away. UseUser::withoutHook()->create([...])or$user->withoutHook()->save(). I checked the repo: the one place that used the old form isAuthenticate::logout(), which I updated to the instance form.User::withoutHook()->find($id)carries the switch with it. If it is saved later, that first save skips hooks.withHooks()is slower than a plain bulk update because every record gets its own query. It is opt-in.withHooks()->update()follows the model's$creatablelist, like$user->update([...]).DB::transaction().Checklist