From e84b2acd4b004fb6b870ed27d35b6436d9b17338 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leo=20Sj=C3=B6berg?= Date: Thu, 26 May 2016 22:40:56 +0200 Subject: [PATCH] [5.2] Apply constraints to a morphTo relation on eager load (#13724) * Apply the bindings to a morphed object * Remove withTrashed workaround # Conflicts: # src/Illuminate/Database/Eloquent/Relations/MorphTo.php * Remove unit test and breaking line * Clone the relation query and add tests * Add tests * Cleanup and another test * Remove unnecessary code * Style fixes --- .../Database/Eloquent/Relations/MorphTo.php | 41 +-------- .../Database/DatabaseEloquentMorphToTest.php | 67 +++----------- ...baseEloquentSoftDeletesIntegrationTest.php | 90 +++++++++++++++++++ 3 files changed, 103 insertions(+), 95 deletions(-) diff --git a/src/Illuminate/Database/Eloquent/Relations/MorphTo.php b/src/Illuminate/Database/Eloquent/Relations/MorphTo.php index 06a488d6294d..4ded56c4c69e 100644 --- a/src/Illuminate/Database/Eloquent/Relations/MorphTo.php +++ b/src/Illuminate/Database/Eloquent/Relations/MorphTo.php @@ -29,13 +29,6 @@ class MorphTo extends BelongsTo */ protected $dictionary = []; - /* - * Indicates if soft-deleted model instances should be fetched. - * - * @var bool - */ - protected $withTrashed = false; - /** * Create a new morph to relationship instance. * @@ -182,9 +175,8 @@ protected function getResultsByType($type) $key = $instance->getTable().'.'.$instance->getKeyName(); - $query = $instance->newQuery(); - - $query = $this->useWithTrashed($query); + $query = clone $this->query; + $query->setModel($instance); return $query->whereIn($key, $this->gatherKeysByType($type)->all())->get(); } @@ -237,33 +229,4 @@ public function getDictionary() { return $this->dictionary; } - - /** - * Fetch soft-deleted model instances with query. - * - * @return $this - */ - public function withTrashed() - { - $this->withTrashed = true; - - $this->query = $this->useWithTrashed($this->query); - - return $this; - } - - /** - * Return trashed models with query if told so. - * - * @param \Illuminate\Database\Eloquent\Builder $query - * @return \Illuminate\Database\Eloquent\Builder - */ - protected function useWithTrashed(Builder $query) - { - if ($this->withTrashed && $query->getMacro('withTrashed') !== null) { - return $query->withTrashed(); - } - - return $query; - } } diff --git a/tests/Database/DatabaseEloquentMorphToTest.php b/tests/Database/DatabaseEloquentMorphToTest.php index 03c9824f7b77..6d04bed2736f 100644 --- a/tests/Database/DatabaseEloquentMorphToTest.php +++ b/tests/Database/DatabaseEloquentMorphToTest.php @@ -1,7 +1,6 @@ getRelation(); - - $one = m::mock('StdClass'); - $one->morph_type = 'morph_type_1'; - $one->foreign_key = 'foreign_key_1'; - - $two = m::mock('StdClass'); - $two->morph_type = 'morph_type_1'; - $two->foreign_key = 'foreign_key_1'; - - $three = m::mock('StdClass'); - $three->morph_type = 'morph_type_2'; - $three->foreign_key = 'foreign_key_2'; - - $relation->addEagerConstraints([$one, $two, $three]); - - $relation->shouldReceive('createModelByType')->once()->with('morph_type_1')->andReturn($firstQuery = m::mock('Illuminate\Database\Eloquent\Builder')); - $relation->shouldReceive('createModelByType')->once()->with('morph_type_2')->andReturn($secondQuery = m::mock('Illuminate\Database\Eloquent\Builder')); - $firstQuery->shouldReceive('getTable')->andReturn('foreign_table_1'); - $firstQuery->shouldReceive('getKeyName')->andReturn('id'); - $secondQuery->shouldReceive('getTable')->andReturn('foreign_table_2'); - $secondQuery->shouldReceive('getKeyName')->andReturn('id'); - - $firstQuery->shouldReceive('newQuery')->once()->andReturn($firstQuery); - $secondQuery->shouldReceive('newQuery')->once()->andReturn($secondQuery); - - $firstQuery->shouldReceive('whereIn')->once()->with('foreign_table_1.id', ['foreign_key_1'])->andReturn($firstQuery); - $firstQuery->shouldReceive('get')->once()->andReturn(Collection::make([$resultOne = m::mock('StdClass')])); - $resultOne->shouldReceive('getKey')->andReturn('foreign_key_1'); - - $secondQuery->shouldReceive('whereIn')->once()->with('foreign_table_2.id', ['foreign_key_2'])->andReturn($secondQuery); - $secondQuery->shouldReceive('get')->once()->andReturn(Collection::make([$resultTwo = m::mock('StdClass')])); - $resultTwo->shouldReceive('getKey')->andReturn('foreign_key_2'); - - $one->shouldReceive('setRelation')->once()->with('relation', $resultOne); - $two->shouldReceive('setRelation')->once()->with('relation', $resultOne); - $three->shouldReceive('setRelation')->once()->with('relation', $resultTwo); - - $relation->getEager(); - } - - public function testModelsWithSoftDeleteAreProperlyPulled() - { - $builder = m::mock('Illuminate\Database\Eloquent\Builder'); - - $relation = $this->getRelation(null, $builder); - - $builder->shouldReceive('getMacro')->once()->with('withTrashed')->andReturn(function () { return true; }); - $builder->shouldReceive('withTrashed')->once(); - - $relation->withTrashed(); - } - public function testAssociateMethodSetsForeignKeyAndTypeOnModel() { $parent = m::mock('Illuminate\Database\Eloquent\Model'); @@ -139,6 +83,17 @@ protected function getRelationAssociate($parent) public function getRelation($parent = null, $builder = null) { $builder = $builder ?: m::mock('Illuminate\Database\Eloquent\Builder'); + $builder->shouldReceive('toBase')->andReturn($builder); + $builder->shouldReceive('removedScopes')->andReturn([]); + $builder->shouldReceive('withoutGlobalScopes')->with([])->andReturn($builder); + $builder->shouldReceive('getRawBindings')->andReturn([ + 'select' => [], + 'join' => [], + 'where' => [], + 'having' => [], + 'order' => [], + 'union' => [], + ]); $builder->shouldReceive('where')->with('relation.id', '=', 'foreign.value'); $related = m::mock('Illuminate\Database\Eloquent\Model'); $related->shouldReceive('getKeyName')->andReturn('id'); diff --git a/tests/Database/DatabaseEloquentSoftDeletesIntegrationTest.php b/tests/Database/DatabaseEloquentSoftDeletesIntegrationTest.php index 63b01fa717b9..86724b862a54 100644 --- a/tests/Database/DatabaseEloquentSoftDeletesIntegrationTest.php +++ b/tests/Database/DatabaseEloquentSoftDeletesIntegrationTest.php @@ -2,6 +2,7 @@ use Carbon\Carbon; use Illuminate\Database\Connection; +use Illuminate\Database\Eloquent\SoftDeletingScope; use Illuminate\Pagination\Paginator; use Illuminate\Database\Query\Builder; use Illuminate\Database\Eloquent\SoftDeletes; @@ -50,6 +51,8 @@ public function createSchema() $this->schema()->create('comments', function ($table) { $table->increments('id'); + $table->integer('owner_id')->nullable(); + $table->string('owner_type')->nullable(); $table->integer('post_id'); $table->string('body'); $table->timestamps(); @@ -506,6 +509,74 @@ public function testOrWhereWithSoftDeleteConstraint() $this->assertEquals(['abigailotwell@gmail.com'], $users->pluck('email')->all()); } + public function testMorphToWithTrashed() + { + $this->createUsers(); + + $abigail = SoftDeletesTestUser::where('email', 'abigailotwell@gmail.com')->first(); + $post1 = $abigail->posts()->create(['title' => 'First Title']); + $post1->comments()->create([ + 'body' => 'Comment Body', + 'owner_type' => SoftDeletesTestUser::class, + 'owner_id' => $abigail->id, + ]); + + $abigail->delete(); + + $comment = SoftDeletesTestCommentWithTrashed::with(['owner' => function ($q) { + $q->withoutGlobalScope(SoftDeletingScope::class); + }])->first(); + + $this->assertEquals($abigail->email, $comment->owner->email); + + $comment = SoftDeletesTestCommentWithTrashed::with(['owner' => function ($q) { + $q->withTrashed(); + }])->first(); + + $this->assertEquals($abigail->email, $comment->owner->email); + } + + public function testMorphToWithConstraints() + { + $this->createUsers(); + + $abigail = SoftDeletesTestUser::where('email', 'abigailotwell@gmail.com')->first(); + $post1 = $abigail->posts()->create(['title' => 'First Title']); + $post1->comments()->create([ + 'body' => 'Comment Body', + 'owner_type' => SoftDeletesTestUser::class, + 'owner_id' => $abigail->id, + ]); + + $comment = SoftDeletesTestCommentWithTrashed::with(['owner' => function ($q) { + $q->where('email', 'taylorotwell@gmail.com'); + }])->first(); + + $this->assertEquals(null, $comment->owner); + } + + public function testMorphToWithoutConstraints() + { + $this->createUsers(); + + $abigail = SoftDeletesTestUser::where('email', 'abigailotwell@gmail.com')->first(); + $post1 = $abigail->posts()->create(['title' => 'First Title']); + $comment1 = $post1->comments()->create([ + 'body' => 'Comment Body', + 'owner_type' => SoftDeletesTestUser::class, + 'owner_id' => $abigail->id, + ]); + + $comment = SoftDeletesTestCommentWithTrashed::with('owner')->first(); + + $this->assertEquals($abigail->email, $comment->owner->email); + + $abigail->delete(); + $comment = SoftDeletesTestCommentWithTrashed::with('owner')->first(); + + $this->assertEquals(null, $comment->owner); + } + /** * Helpers... */ @@ -606,6 +677,25 @@ class SoftDeletesTestComment extends Eloquent protected $dates = ['deleted_at']; protected $table = 'comments'; protected $guarded = []; + + public function owner() + { + return $this->morphTo(); + } +} + +class SoftDeletesTestCommentWithTrashed extends Eloquent +{ + use SoftDeletes; + + protected $dates = ['deleted_at']; + protected $table = 'comments'; + protected $guarded = []; + + public function owner() + { + return $this->morphTo(); + } } /**