[12.x] Add withTrashedRelations() to automatically eager load deleted related models
#57696
+133
−3
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.
Problem
When you have several related models that have all been trashed, it can be tricky and verbose to load these along with your original model.
Let's use a blog for this example, which we can expect to have the model structure of User -> Post -> Comment. Now let's say our blog allows us to archive users along with any content they've created. That's all pretty basic Eloquent, until you need to add a way to still show archived users and their content.
The assumption would be to do something like this:
However if we look at the queries that ran we'll quickly see why your blog is still missing some content:
The method only removed the global scope for the model we queried and not any relations.
A solution for this would be to add constraints to your eager loaded models:
If we check the queries again, everything is now scoped correctly:
As you can see, those
with()constraints will quickly get out of hand on larger models or a more complicated query.Solution
use App\Models\User; User::limit(10) ->with('posts.comments') - ->withTrashed() + ->withTrashedRelations() ->get();This new method will automatically remove the trashed scope on all eager loaded models, just like we did with the manual constraints, but without the bloat. If we check our query again, everything is still scoped correctly!
Now I'm not discounting that you could just scope this on the model relationship, but for scenarios like this you generally want the default behaviour throughout the application, and to override it in smaller doses.
Notes
After much trial and error this seemed the best approach I could find that avoided a breaking change (based on the many ways relationships can be loaded) but I'm open to suggestions! There's also the possibility to repeat this for
onlyTrashed()- I just wasn't sure if it would be wanted.Currently this doesn't work with lazy or automatic eager loading, but if there's interest in this first step I'm happy to investigate further.
The problem I see with both of those is we don't have access to the original query because they happen once the data is collected - unless we introduce a new method like
loadWithTrashed()or wrap it in a static callback like with event muting:This implementation isn't limited to just soft deletes though, it could be used for a number of other things as it allows you to ensure any eager-loaded nested relation uses the same scope as the parent.