diff --git a/extension.neon b/extension.neon index f1c33895e..e78e157e0 100644 --- a/extension.neon +++ b/extension.neon @@ -166,7 +166,7 @@ services: - phpstan.broker.dynamicMethodReturnTypeExtension - - class: NunoMaduro\Larastan\ReturnTypes\RelationExtension + class: NunoMaduro\Larastan\ReturnTypes\RelationCollectionExtension tags: - phpstan.broker.dynamicMethodReturnTypeExtension diff --git a/src/ReturnTypes/RelationExtension.php b/src/ReturnTypes/RelationCollectionExtension.php similarity index 80% rename from src/ReturnTypes/RelationExtension.php rename to src/ReturnTypes/RelationCollectionExtension.php index 13c32e40a..ee915362d 100644 --- a/src/ReturnTypes/RelationExtension.php +++ b/src/ReturnTypes/RelationCollectionExtension.php @@ -11,6 +11,7 @@ use PhpParser\Node\Expr\MethodCall; use PHPStan\Analyser\Scope; use PHPStan\Reflection\MethodReflection; +use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Type\DynamicMethodReturnTypeExtension; use PHPStan\Type\Generic\GenericObjectType; use PHPStan\Type\ObjectType; @@ -19,7 +20,7 @@ /** * @internal */ -final class RelationExtension implements DynamicMethodReturnTypeExtension +final class RelationCollectionExtension implements DynamicMethodReturnTypeExtension { /** @var BuilderHelper */ private $builderHelper; @@ -52,6 +53,12 @@ public function isMethodSupported(MethodReflection $methodReflection): bool return false; } + $returnType = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); + + if (! in_array(Collection::class, $returnType->getReferencedClasses(), true)) { + return false; + } + return $methodReflection->getDeclaringClass()->hasNativeMethod($methodReflection->getName()); } @@ -66,7 +73,7 @@ public function getTypeFromMethodCall( /** @var ObjectType $modelType */ $modelType = $methodReflection->getDeclaringClass()->getActiveTemplateTypeMap()->getType('TRelatedModel'); - $returnType = $methodReflection->getVariants()[0]->getReturnType(); + $returnType = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); if (in_array(Collection::class, $returnType->getReferencedClasses(), true)) { $collectionClassName = $this->builderHelper->determineCollectionClassName($modelType->getClassname()); diff --git a/tests/Features/Models/Relations.php b/tests/Features/Models/Relations.php index 424749e4c..559a8dc77 100644 --- a/tests/Features/Models/Relations.php +++ b/tests/Features/Models/Relations.php @@ -14,6 +14,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\MorphTo; +use Illuminate\Database\Eloquent\Relations\MorphToMany; class Relations { @@ -186,6 +187,22 @@ public function testWithoutTrashedWithBelongsToRelation(User $user): BelongsTo { return $user->group()->withoutTrashed(); } + + /** + * @phpstan-return MorphToMany
+ */ + public function testMorphToManyWithTimestamps(Tag $tag): MorphToMany + { + return $tag->addresses(); + } + + /** + * @phpstan-return MorphToMany + */ + public function testMorphToManyWithPivot(Tag $tag): MorphToMany + { + return $tag->addresses(); + } } /** @@ -210,33 +227,27 @@ public function relation(): HasMany { return $this->hasMany(User::class); } - - public function addRelation(): User - { - return $this->relation()->create([]); - } } -class TestRelationCreateOnExistingModel +class Tag extends Model { - /** @var User */ - private $user; - - public function testRelationCreateOnExistingModel(): Account + /** + * @phpstan-return MorphToMany + */ + public function addresses(): MorphToMany { - return $this->user->accounts()->create(); + return $this->morphToMany(Address::class, 'taggable')->withTimestamps(); } -} -class Post extends Model -{ - public function author(): BelongsTo + /** + * @phpstan-return MorphToMany + */ + public function addressesWithPivot(): MorphToMany { - return $this->belongsTo(User::class); + return $this->morphToMany(Address::class, 'taggable')->withPivot('foo'); } +} - public function addUser(User $user): self - { - return $this->author()->associate($user); - } +class Address extends Model +{ }