-
Notifications
You must be signed in to change notification settings - Fork 11.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[5.7] Add Model::withoutEventDispatcher() method to temporarily bypass model events #27419
Conversation
It is sometimes desirable to bypass model events temporarily. For example, when seeding. While it is already possible to do this, the code is verbose. This aims to simplify that by creating an intuitively named helper method to execute a callback with the event dispatcher temporarily disabled, returning the result of the callback.
I like this, I'm using a not-so-elegant version for the test suite: /**
* Saves the given models without any events
*
* The event disable/enable logic is from
* https://laravel.io/forum/09-14-2014-turn-off-eloquent-events?page=1#reply-29610
*
* @param Model $model
* @return void
*/
protected function modelSaveNoEvents(Model $model): void
{
// This is trick for the IDE to resolve the static functions below
// and technically not necessary…
$modelName = $model;
// …and this will always succeed
if ($model instanceof Model) {
$modelName = \get_class($model);
}
$dispatcher = $modelName::getEventDispatcher();
$modelName::unsetEventDispatcher();
$model->save();
$modelName::setEventDispatcher($dispatcher);
} I've never had a use-case outside Wish it would be possible do to something similar to the higher order proxy: One thing I'm not sure if we need this functionality on the core model or rather just a helper somewhere else? |
Thanks for the feedback. I can think of other reasons to temporarily disable this outside of tests so it would be good if the solution wasn't test-specific. The higher-order proxy method would be nice, but it's probably overkill for this. I see this as just a small natural extension of the existing |
Sounds reasonable to me 👍 |
} | ||
|
||
return $result; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code formatting could be shortened to:
public static function withoutEventDispatcher(callable $callback)
{
$dispatcher = static::getEventDispatcher();
static::unsetEventDispatcher();
try {
return $callback();
} finally {
static::setEventDispatcher($dispatcher);
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good call on removing the temp variable - I didn't realise finally
would still be executed after a return
. Thanks for teaching me something new!
Regarding the if
condition - the reason I added this is because setEventDispatcher()
requires a valid Dispatcher
and it's possible that the user didn't actually have an event dispatcher set.
You might say that someone shouldn't use this method if they don't have an event dispatcher set, but I guess it would be nice to be able to call the method without having to worry about that.
Is there going to be confusion on how this applies to models? People may not realize it's a static variable and to do $model->withoutModelEvents() is to actually disable events for all models. They may only expect it to work for the specific model instance. Thoughts? |
Fair question, and the answer is probably yes. However, from my perspective I don't think it's any different to the existing It could be helpful to add a comment to each of these methods to clarify it though. |
Since it's mainly for test data setup, maybe locating the method as |
That would certainly be less ambiguous and the location still feels intuitive and discoverable. It would also fulfil my own current use case, but it's not too hard to imagine other scenarios where it wouldn't. I don't think it feels quite as tidy and encapsulated as co-locating it with the other If the use case is common enough, I could see |
I've wanted this forever :D laravel/ideas#344 |
@robclancy yea I can finally get rid of those awful methods in my TestCase file for each model 😆 |
This is very useful! Thank you very much guys. 🍻 |
Btw I don't know why, but for me the method is named: |
That's correct, it was changed afterwards. |
It is sometimes desirable to temporarily bypass model events. For example, when seeding - imagine your model has a
created
listener and asaved
listener. You are trying to test thesaved
listener, but thecreated
listener is also firing when seeding the data for the test.While it is already possible to do this, the code is verbose and requires either muddying up the test with implementation details, or by creating a base model or trait to add the functionality directly to the model (my current workaround). This aims to simplify that by creating an intuitively named helper method to execute a callback with the event dispatcher temporarily disabled, returning the result of the callback.
As this is a new method, it should not break anything. It just enhances the existing API around getting, setting, and unsetting the event dispatcher.
Before:
After:
I realise that a similar PR was rejected in the past, but I feel this PR is different for the following reasons:
Model::unsetEventDispatcher()
API so I believe it achieves the same clarity about what it is doing.