-
Notifications
You must be signed in to change notification settings - Fork 10.7k
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
[11.x] Eloquent inverse relations #51582
base: 11.x
Are you sure you want to change the base?
[11.x] Eloquent inverse relations #51582
Conversation
Thanks for submitting a PR! Note that draft PR's are not reviewed. If you would like a review, please mark your pull request as ready for review in the GitHub user interface. Pull requests that are abandoned in draft may be closed due to inactivity. |
This would be great to have in the core, ever since Jonathan Reinink wrote a great blog post about optimizing circular relationships in Laravel this has been on my mind. The package stancl/laravel-hasmanywithinverse ran with that idea and implemented it for the The one downside is that this can cause an infinite loop in |
I haven't done any mitigation for it yet, but I have encountered it before, so it's on my radar. Partly I'm putting this up in draft right now so that I can start getting feedback before I sink much more time into it (if it's likely to get rejected, it'll probably be because it started getting complex before people could assess what it's actually about). |
Why is it called "inverse" instead of something more sensible like |
Essentially it's marking the reverse/inverse relationship, not just a model that it's linked to. They're already referred to as "inverse" relations in the documentation. |
Wouldn't this causes an infinite loop in |
Potentially, yes. Well... no, this wouldn't cause it - the potential already exists when you use |
I feel like it's clunky you have to pass the name of the inverse relationship which almost always matches the model you are in at the time. |
I've pushed up changes to make |
2cf444f
to
eb42cbf
Compare
3e7ae71
to
58a9f01
Compare
58a9f01
to
cc25c9d
Compare
I disagree; the word "inverse" in the docs is used as an indication that |
No, it's setting the "parent" model as the inverse relationship. That's why this isn't on any
I could do |
Yes you are right, the code indeed sets the inverse relationship to the "parent" exactly as you say, but the word This PR is a good idea anyway regardless of which word is used :) |
src/Illuminate/Database/Eloquent/Relations/Concerns/SupportsInverseRelations.php
Outdated
Show resolved
Hide resolved
ff6ca30
to
bea61e6
Compare
How many times has this happened to you?
With the policy:
Only to find that you're now getting a new query for every single comment to pull the post back out of the database?
Obviously you could eager-load the post back onto the model, but even then that's creating a lot of extra busywork and it might miss scopes that were added on the original query:
Well it's happened to me. A lot. So I decided to do something about it.
Introducing
inverse()
inverse()
is a modifier forHas*
andMorph*
relations that allows you to easily set the parent model in the correct relation on the child models:Now you can do:
And each
Comment
model will have itspost
relation filled out with the actual instance of the parent model, with no extra queries, or missed scopes.Note that it injects the actual instance, instead of querying and pulling another instance which could then get out of sync:
Optionally Skipping an Inverse Relationship
If you don't want to incluide the inverse relationship for some reason, you can tack
->withoutInverse()
onto the query builder that you get from the relation:Guessing the Inverse Relationship
If you don't define the inverse relationship it will attempt to guess the correct relationship based on the foreign key of the relation, the foriegn key of the parent, the name of the parent model, the name of a polymorphic relationship, or a couple of common relationship names such as
owner
.If the parent is the same class as the child, it will also guess
parent
.Backwards Compatibility
There aren't really any backwards compatibility concerns - the new method is "opt-in", and the changes made to the existing relationships shouldn't have any affect if the new method isn't used.
Which Relationships Can Be Inverted?
Only the
HasOneOrMany
relationships make sense forinverse()
. This means that it's available forHasOne
,HasMany
,MorphOne
, andMorphMany
. It also supports theOneOfMany
modifiers.None of the
BelongsTo*
relations really make sense to define an inverse relationship because the inverse will probably be a collection.Potential risks
As mentioned by @stayallive, there is a known issue with
getQueueableRelations()
andrelationsToArray()
when you have circular object references, leading to an infinite loop as it tries to dig down through the "tree" to discover relations. While this change isn't introducing that issue (it already exists), it does increase the likelihood that developers will encounter it.I don't think that trying to fix it in this PR is wise, because that will increase the complexity of this change and, again, this change doesn't create the issue - it just makes it more likely for developers to encounter it. I've personally encountered it in my own work when I was using
setRelation
to manually do whatinverse()
does.I do have a mitigation in mind for it, but I think that I'll reserve that for a separate PR. In short, it could be mitigated by keeping track of the objects that have been serialized, and don't keep digging down on ones that we have already seen - it shouldn't be particularly more memory hungry (it will involve temporarily holding another array of references to objects), but it does need to get added in a number of places.