-
Notifications
You must be signed in to change notification settings - Fork 11k
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] Fix fluent syntax for HasManyThrough when combining HasMany followed by HasOne #53335
[11.x] Fix fluent syntax for HasManyThrough when combining HasMany followed by HasOne #53335
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. |
@calebdw Maybe I can ask you for help here on the typing, given all your (amazing!) work on adding generic types for all this. Currently the type of the resulting relation is inferred based (solely) on the type of the framework/src/Illuminate/Database/Eloquent/PendingHasThroughRelationship.php Lines 48 to 57 in f51b643
After my change, the resulting type is also influenced by |
Hmm, you might need to add a new |
f51b643
to
a8e18c3
Compare
a8e18c3
to
efe1781
Compare
…ional template type
* : ( | ||
* $relationship is \Illuminate\Database\Eloquent\Relations\HasMany<TIntermediateModel, $this> | ||
* ? \Illuminate\Database\Eloquent\PendingHasThroughRelationship<TIntermediateModel, $this, \Illuminate\Database\Eloquent\Relations\HasMany<TIntermediateModel, $this>> | ||
* : \Illuminate\Database\Eloquent\PendingHasThroughRelationship<TIntermediateModel, $this, \Illuminate\Database\Eloquent\Relations\HasOne<TIntermediateModel, $this>> | ||
* ) |
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.
You can probably clean up the conditions if you add a Relation template type here:
/**
* Create a pending has-many-through or has-one-through relationship.
*
* @template TIntermediateModel of \Illuminate\Database\Eloquent\Model
* @template TLocalRelation of \Illuminate\Database\Eloquent\Relations\HasOneOrMany<TIntermediateModel, covariant $this>
*
* @param string|TLocalRelation $relationship
* @return (
* $relationship is string
* ? \Illuminate\Database\Eloquent\PendingHasThroughRelationship<\Illuminate\Database\Eloquent\Model, $this, ...>
* : \Illuminate\Database\Eloquent\PendingHasThroughRelationship<TIntermediateModel, $this, TLocalRelation>
* )
*/
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.
Wow, thanks for your quick feedback!
I must admit this was my first idea as well, but somehow I couldn't get it to play right. Using this I was faced with:
------ ------------------------------------------------------------------------------------------------------------------------------
Line Relations.php
------ ------------------------------------------------------------------------------------------------------------------------------
195 Expected type
Illuminate\Database\Eloquent\PendingHasThroughRelationship<Illuminate\Types\Relations\Mechanic,
$this(Illuminate\Types\Relations\User),
Illuminate\Database\Eloquent\Relations\HasOne<Illuminate\Types\Relations\Mechanic,
$this(Illuminate\Types\Relations\User)>>, actual:
Illuminate\Database\Eloquent\PendingHasThroughRelationship<Illuminate\Types\Relations\Mechanic,
$this(Illuminate\Types\Relations\User),
Illuminate\Database\Eloquent\Relations\HasOneOrMany<Illuminate\Types\Relations\Mechanic, covariant
$this(Illuminate\Types\Relations\User)>>
199 Expected type Illuminate\Database\Eloquent\Relations\HasOneThrough<Illuminate\Types\Relations\Car,
Illuminate\Types\Relations\Mechanic, $this(Illuminate\Types\Relations\User)>, actual:
Illuminate\Database\Eloquent\Relations\HasManyThrough<Illuminate\Types\Relations\Car,
Illuminate\Types\Relations\Mechanic,
$this(Illuminate\Types\Relations\User)>|Illuminate\Database\Eloquent\Relations\HasOneThrough<Illuminate\Types\Relations\Car,
Illuminate\Types\Relations\Mechanic, $this(Illuminate\Types\Relations\User)>
215 Expected type
Illuminate\Database\Eloquent\PendingHasThroughRelationship<Illuminate\Types\Relations\Mechanic,
$this(Illuminate\Types\Relations\User),
Illuminate\Database\Eloquent\Relations\HasMany<Illuminate\Types\Relations\Mechanic,
$this(Illuminate\Types\Relations\User)>>, actual:
Illuminate\Database\Eloquent\PendingHasThroughRelationship<Illuminate\Types\Relations\Mechanic,
$this(Illuminate\Types\Relations\User),
Illuminate\Database\Eloquent\Relations\HasOneOrMany<Illuminate\Types\Relations\Mechanic, covariant
$this(Illuminate\Types\Relations\User)>>
224 Expected type Illuminate\Database\Eloquent\Relations\HasManyThrough<Illuminate\Types\Relations\Car,
Illuminate\Types\Relations\Mechanic, $this(Illuminate\Types\Relations\User)>, actual:
Illuminate\Database\Eloquent\Relations\HasManyThrough<Illuminate\Types\Relations\Car,
Illuminate\Types\Relations\Mechanic,
$this(Illuminate\Types\Relations\User)>|Illuminate\Database\Eloquent\Relations\HasOneThrough<Illuminate\Types\Relations\Car,
Illuminate\Types\Relations\Mechanic, $this(Illuminate\Types\Relations\User)>
------ ------------------------------------------------------------------------------------------------------------------------------
This seems to suggest that somehow TLocalRelation
is not properly inherited as either HasOne
or HasMany
, but keeps using HasOneOrMany
which is kind of unfortunate.
* @template TIntermediateModel of \Illuminate\Database\Eloquent\Model | ||
* @template TDeclaringModel of \Illuminate\Database\Eloquent\Model | ||
* @template TLocalRelationship of \Illuminate\Database\Eloquent\Relations\HasOneOrMany<TIntermediateModel, TDeclaringModel> |
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.
I'm not sure if you need all three templates---you might can do with just the relation template given that both the model types can be gleaned from the relation type
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.
Is there some canonical way to do this? Last time I dived into this I was told there to be no trivial way to do such a thing; see phpstan/phpstan#8814.
Adding a new generic type to a class should've been considered a BC break from type system POV. But I guess Laravel doesn't care about that. |
In #45894 'fluent' syntax for has-many-through and has-one-through relations was added. When combining has-one and has-many, one has 4 options:
HasOne
followed byHasOne
=>HasOneThrough
HasOne
followed byHasMany
=>HasManyThrough
HasMany
followed byHasMany
=>HasManyThrough
HasMany
followed byHasOne
=>HasManyThrough
All of these options work fine when using the 'classic' relation definitions. However, the 4th option does not work with the new 'fluent' syntax, as ending with a
HasOne
makes it incorrectly infer the type of the 'full' relation to beHasOneThrough
rather thanHasManyThrough
.This PR aims to fix this discrepancy between 'classic' and 'fluent' syntax, by fixing the latter to also allow the 4th option. This is illustrated by adding tests in the first commit that will fail without the fix.
Open issue: the inferred typing on the
has
method will still incorrectly reportHasOneThrough
with current changeset.