From 30ea42dd005aa9572d4a71d6e2f95e689cab88b4 Mon Sep 17 00:00:00 2001 From: Jonas Staudenmeir Date: Fri, 30 Nov 2018 00:49:58 +0100 Subject: [PATCH] Fix HasManyThrough existence queries with same parent and through parent table --- .../Eloquent/Relations/HasManyThrough.php | 29 +++++++++++- .../Database/EloquentHasManyThroughTest.php | 46 +++++++++++++++++++ 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php b/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php index 93f781dcd007..96a5420de3e8 100644 --- a/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php +++ b/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php @@ -480,10 +480,14 @@ protected function prepareQueryBuilder($columns = ['*']) */ public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*']) { - if ($parentQuery->getQuery()->from == $query->getQuery()->from) { + if ($parentQuery->getQuery()->from === $query->getQuery()->from) { return $this->getRelationExistenceQueryForSelfRelation($query, $parentQuery, $columns); } + if ($parentQuery->getQuery()->from === $this->throughParent->getTable()) { + return $this->getRelationExistenceQueryForThroughSelfRelation($query, $parentQuery, $columns); + } + $this->performJoin($query); return $query->select($columns)->whereColumn( @@ -516,6 +520,29 @@ public function getRelationExistenceQueryForSelfRelation(Builder $query, Builder ); } + /** + * Add the constraints for a relationship query on the same table as the through parent. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Builder $parentQuery + * @param array|mixed $columns + * @return \Illuminate\Database\Eloquent\Builder + */ + public function getRelationExistenceQueryForThroughSelfRelation(Builder $query, Builder $parentQuery, $columns = ['*']) + { + $table = $this->throughParent->getTable().' as '.$hash = $this->getRelationCountHash(); + + $query->join($table, $hash.'.'.$this->secondLocalKey, '=', $this->getQualifiedFarKeyName()); + + if ($this->throughParentSoftDeletes()) { + $query->whereNull($hash.'.'.$this->throughParent->getDeletedAtColumn()); + } + + return $query->select($columns)->whereColumn( + $parentQuery->getQuery()->from.'.'.$this->localKey, '=', $hash.'.'.$this->firstKey + ); + } + /** * Get a relationship join table hash. * diff --git a/tests/Integration/Database/EloquentHasManyThroughTest.php b/tests/Integration/Database/EloquentHasManyThroughTest.php index 06ce0acc2729..67164c473bcb 100644 --- a/tests/Integration/Database/EloquentHasManyThroughTest.php +++ b/tests/Integration/Database/EloquentHasManyThroughTest.php @@ -4,6 +4,7 @@ use Illuminate\Support\Facades\Schema; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Tests\Integration\Database\DatabaseTestCase; /** @@ -27,6 +28,17 @@ public function setUp() $table->integer('owner_id')->nullable(); $table->string('owner_slug')->nullable(); }); + + Schema::create('categories', function ($table) { + $table->increments('id'); + $table->integer('parent_id')->nullable(); + $table->softDeletes(); + }); + + Schema::create('products', function ($table) { + $table->increments('id'); + $table->integer('category_id'); + }); } public function test_basic_create_and_retrieve() @@ -83,6 +95,21 @@ public function test_has_self_custom_owner_key() $this->assertEquals(1, $users->count()); } + + public function test_has_same_parent_and_through_parent_table() + { + Category::create(); + Category::create(); + Category::create(['parent_id' => 1]); + Category::create(['parent_id' => 2])->delete(); + + Product::create(['category_id' => 3]); + Product::create(['category_id' => 4]); + + $categories = Category::has('subProducts')->get(); + + $this->assertEquals([1], $categories->pluck('id')->all()); + } } class User extends Model @@ -129,3 +156,22 @@ class Team extends Model public $timestamps = false; protected $guarded = []; } + +class Category extends Model +{ + use SoftDeletes; + + public $timestamps = false; + protected $guarded = []; + + public function subProducts() + { + return $this->hasManyThrough(Product::class, self::class, 'parent_id'); + } +} + +class Product extends Model +{ + public $timestamps = false; + protected $guarded = []; +}