Skip to content

Commit

Permalink
[5.2] Apply constraints to a morphTo relation on eager load (#13724)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
phroggyy authored and taylorotwell committed May 26, 2016
1 parent dadf71f commit e84b2ac
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 95 deletions.
41 changes: 2 additions & 39 deletions src/Illuminate/Database/Eloquent/Relations/MorphTo.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -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;
}
}
67 changes: 11 additions & 56 deletions tests/Database/DatabaseEloquentMorphToTest.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
<?php

use Mockery as m;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Relations\MorphTo;

class DatabaseEloquentMorphToTest extends PHPUnit_Framework_TestCase
Expand Down Expand Up @@ -37,61 +36,6 @@ public function testLookupDictionaryIsProperlyConstructed()
], $dictionary);
}

public function testModelsAreProperlyPulledAndMatched()
{
$relation = $this->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');
Expand Down Expand Up @@ -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');
Expand Down
90 changes: 90 additions & 0 deletions tests/Database/DatabaseEloquentSoftDeletesIntegrationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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...
*/
Expand Down Expand Up @@ -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();
}
}

/**
Expand Down

0 comments on commit e84b2ac

Please sign in to comment.