Replies: 5 comments 6 replies
-
I'd second this. its a pitfall i can easily see junior engineers falling into. I'd also be happy to look at making a PR for it, assuming its not a waste of my time. |
Beta Was this translation helpful? Give feedback.
-
That is why it is stated in the documentation that mass updates or raw queries are not triggering events. |
Beta Was this translation helpful? Give feedback.
-
One potential idea to solve this problem is to make a builder for that model and then override the update function for that model to throw an error instead of updating. It's not perfect because you wouldn't have intellisense yell at you when you invoke the update method. It would just fail. But presuming good test coverage you could catch those cases. |
Beta Was this translation helpful? Give feedback.
-
A year later, this is still an issue, one i'm a bit surprised it hasn't been tackled. I've personally ran into multiple issue related to this, common examples include;
At one point, i considered a simple trait, that overwrote the query builder, and limited the operations that way, a good solution. But even then, you run into things like a model being written to via a relationship, bypassing the model entirely, and making the model specific query-builder useless. It feels like something that would need to be handled in the framework, rather than in userland. |
Beta Was this translation helpful? Give feedback.
-
// Eloquent builder
public function update(array $values)
{
return $this->toBase()->update($this->addUpdatedAtColumn($values));
} toBase returns the Query Builder so also the Eloquent Builder's updates are going through the Query builder update. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Summary
I'd like the ability to prevent the query builder from making changes to the data (insert/update/delete).
Reasoning
I like that I can use model events to track data changes. However, using some of the framework's functionality to make data changes will avoid loading the model. In those scenarios, model changes do not fire, and I'm then unable to track the data changes.
Of course, I can adjust how I'm working with things to make sure I'm working with models and not the query builder. However, the difference between working with the query builder vs. a loaded model can sometimes be quite subtle (especially to junior developers or developers not experienced with Eloquent).
Simple Example
Post::find(1)->update(['status' => $status]);
Post::where('slug', $slug)->update(['status' => $status]);
Both of these will update the status of a post. However, only the first will fire model events.
This is because
find()
will return a model, butwhere()
returns a query builder.A naive glance might see they're both starting from the "Post" model and might not catch the difference.
Of course, I could add "->first()" to the second one to actually retrieve the model, and then model events will fire:
Post::where('slug', $slug)->first()->update(['status' => $status]);
Another Example
Some examples can easily sneak up on you, especially if you're not familiar with the distinction between what is being called on a loaded model vs. a query builder.
Consider this:
$post->comments()->increment('views')
Assuming
$post->comments()
is returning a relation (such asHasMany
), which relies on the query builder, it will increment theviews
column in thecomments
table for all the comments related to the$post
.This happens efficiently, but the post models are never loaded, and therefore no model events are fired. This will cause issues if I'm trying to track data changes, so I can troubleshoot issues, or roll back changes, or provide transparency into the journey of the data.
I acknowledge that in many cases it is more efficient to allow the query builder to do it's thing rather than loading all the models and working through one at a time. As someone who consider's themself to be a data person, it is sometimes painful for me to see things go the less efficient route.
Implementation Thoughts
I may not be the best person to tackle this, but I'm more than happy to submit a PR with my attempt at implementation if this is something that would be considered.
My first thought is to add a static property such as
$allowDataChanges
toIlluminate/Database/Query/Builder
(defaulted to "true").Then add a simple check to the functions that make data changes (such as insert, insertOrIgnore, insertGetId, insertUsing, update, updateFrom, upsert, delete, truncate), to first verify data changes are allowed before attempting to make the changes.
Of course, since calling
->save()
on a model ultimately passes through the query builder, I'd need to update thesave()
function inIlluminate/Database/Eloquent/Model
to update the builder's static$allowDataChanges
to allow it, and then revert it back to it's original state after making the data change.I'd also consider creating a function where you could pass a callback to override the behavior in one-off scenarios where you really do want to take advantage of the query builder's efficiency, or whatever other reason, and consciously handle (or ignore) the things that model events would be doing.
For example, something like
Builder::withAllowedDataChanges(function () { ... });
Apologies if this is an undesirable approach to implementation. Working on a framework is a bit outside of my experience, so I'd also be happy to hear other suggestions on implementation. I also wouldn't be offended if someone crafted a PR of their own for this.
Wrap-up
This is something that has been bugging me (and I know I'm not exactly alone here) for a couple years now and I'd love to see it handled in the framework.
Again, I'm happy to put together a PR with something I can pull together and see where it goes from there. I'm posting this here before I go through that effort to get a sense for whether or not it has a chance to get included in the framework.
Beta Was this translation helpful? Give feedback.
All reactions