From 702cb8801ffc384fda06a7839312fc07b9f6b807 Mon Sep 17 00:00:00 2001 From: leobeal Date: Tue, 30 Jul 2024 08:59:49 +0200 Subject: [PATCH] [11.x] Fixes through() relationship (#52318) * Adds a failing test * Allows MorphOneOrMany in through() method --- .../PendingHasThroughRelationship.php | 28 ++-- .../Database/EloquentThroughTest.php | 122 ++++++++++++++++++ 2 files changed, 140 insertions(+), 10 deletions(-) create mode 100644 tests/Integration/Database/EloquentThroughTest.php diff --git a/src/Illuminate/Database/Eloquent/PendingHasThroughRelationship.php b/src/Illuminate/Database/Eloquent/PendingHasThroughRelationship.php index 478d3d3a8de5..4b6aad6cd16e 100644 --- a/src/Illuminate/Database/Eloquent/PendingHasThroughRelationship.php +++ b/src/Illuminate/Database/Eloquent/PendingHasThroughRelationship.php @@ -4,6 +4,8 @@ use BadMethodCallException; use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Database\Eloquent\Relations\MorphMany; +use Illuminate\Database\Eloquent\Relations\MorphOneOrMany; use Illuminate\Support\Str; /** @@ -44,7 +46,7 @@ public function __construct($rootModel, $localRelationship) * * @template TRelatedModel of \Illuminate\Database\Eloquent\Model * - * @param string|(callable(TIntermediateModel): (\Illuminate\Database\Eloquent\Relations\HasOne|\Illuminate\Database\Eloquent\Relations\HasMany)) $callback + * @param string|(callable(TIntermediateModel): (\Illuminate\Database\Eloquent\Relations\HasOne|\Illuminate\Database\Eloquent\Relations\HasMany|\Illuminate\Database\Eloquent\Relations\MorphOneOrMany)) $callback * @return ( * $callback is string * ? \Illuminate\Database\Eloquent\Relations\HasManyThrough<\Illuminate\Database\Eloquent\Model, TIntermediateModel, TDeclaringModel>|\Illuminate\Database\Eloquent\Relations\HasOneThrough<\Illuminate\Database\Eloquent\Model, TIntermediateModel, TDeclaringModel> @@ -64,7 +66,7 @@ public function has($callback) $distantRelation = $callback($this->localRelationship->getRelated()); if ($distantRelation instanceof HasMany) { - return $this->rootModel->hasManyThrough( + $returnedRelation = $this->rootModel->hasManyThrough( $distantRelation->getRelated()::class, $this->localRelationship->getRelated()::class, $this->localRelationship->getForeignKeyName(), @@ -72,16 +74,22 @@ public function has($callback) $this->localRelationship->getLocalKeyName(), $distantRelation->getLocalKeyName(), ); + } else { + $returnedRelation = $this->rootModel->hasOneThrough( + $distantRelation->getRelated()::class, + $this->localRelationship->getRelated()::class, + $this->localRelationship->getForeignKeyName(), + $distantRelation->getForeignKeyName(), + $this->localRelationship->getLocalKeyName(), + $distantRelation->getLocalKeyName(), + ); + } + + if ($this->localRelationship instanceof MorphOneOrMany) { + $returnedRelation->where($this->localRelationship->getQualifiedMorphType(), $this->localRelationship->getMorphClass()); } - return $this->rootModel->hasOneThrough( - $distantRelation->getRelated()::class, - $this->localRelationship->getRelated()::class, - $this->localRelationship->getForeignKeyName(), - $distantRelation->getForeignKeyName(), - $this->localRelationship->getLocalKeyName(), - $distantRelation->getLocalKeyName(), - ); + return $returnedRelation; } /** diff --git a/tests/Integration/Database/EloquentThroughTest.php b/tests/Integration/Database/EloquentThroughTest.php new file mode 100644 index 000000000000..338dca202d55 --- /dev/null +++ b/tests/Integration/Database/EloquentThroughTest.php @@ -0,0 +1,122 @@ +increments('id'); + $table->boolean('public'); + }); + + Schema::create('other_commentables', function (Blueprint $table) { + $table->increments('id'); + }); + + Schema::create('comments', function (Blueprint $table) { + $table->increments('id'); + $table->string('commentable_type'); + $table->integer('commentable_id'); + }); + + Schema::create('likes', function (Blueprint $table) { + $table->increments('id'); + $table->unsignedInteger('comment_id'); + }); + + + $post = tap(new Post(['public' => true]))->save(); + $comment = tap((new Comment)->commentable()->associate($post))->save(); + (new Like())->comment()->associate($comment)->save(); + (new Like())->comment()->associate($comment)->save(); + + $otherCommentable = tap(new OtherCommentable())->save(); + $comment2 = tap((new Comment)->commentable()->associate($otherCommentable))->save(); + (new Like())->comment()->associate($comment2)->save(); + } + + public function test() + { + /** @var Post $post */ + $post = Post::first(); + $this->assertEquals(2, $post->commentLikes()->count()); + } +} + +class Comment extends Model +{ + public $timestamps = false; + + public function commentable() + { + return $this->morphTo(); + } + + public function likes() + { + return $this->hasMany(Like::class); + } +} + +class Post extends Model +{ + public $timestamps = false; + + protected $guarded = []; + + protected $withCount = ['comments']; + + public function comments() + { + return $this->morphMany(Comment::class, 'commentable'); + } + + public function commentLikes() + { + return $this->through($this->comments())->has('likes'); + } + + public function texts() + { + return $this->hasMany(Text::class); + } +} + +class OtherCommentable extends Model +{ + public $timestamps = false; + + public function comments() + { + return $this->morphMany(Comment::class, 'commentable'); + } +} + +class Text extends Model +{ + public $timestamps = false; + + protected $guarded = []; + + public function post() + { + return $this->belongsTo(Post::class); + } +} + +class Like extends Model +{ + public $timestamps = false; + + public function comment() + { + return $this->belongsTo(Comment::class); + } +}