diff --git a/src/Illuminate/Database/Eloquent/Builder.php b/src/Illuminate/Database/Eloquent/Builder.php index f93114bf04af..a76b240c784e 100755 --- a/src/Illuminate/Database/Eloquent/Builder.php +++ b/src/Illuminate/Database/Eloquent/Builder.php @@ -96,7 +96,7 @@ public function applyGlobalScope($identifier, $scope) * @param \Illuminate\Database\Eloquent\ScopeInterface|string $scope * @return $this */ - public function removeGlobalScope($scope) + public function withoutGlobalScope($scope) { if (is_string($scope)) { unset($this->scopes[$scope]); @@ -118,7 +118,7 @@ public function removeGlobalScope($scope) * * @return $this */ - public function removeGlobalScopes() + public function withoutGlobalScopes() { $this->scopes = []; @@ -461,7 +461,7 @@ public function onDelete(Closure $callback) */ public function getModels($columns = ['*']) { - $results = $this->getQueryWithScopes()->get($columns); + $results = $this->loadScopes()->getQuery()->get($columns); $connection = $this->model->getConnectionName(); @@ -641,7 +641,9 @@ public function has($relation, $operator = '>=', $count = 1, $boolean = 'and', C call_user_func($callback, $query); } - return $this->addHasWhere($query, $relation, $operator, $count, $boolean); + return $this->addHasWhere( + $query->loadScopes(), $relation, $operator, $count, $boolean + ); } /** @@ -750,7 +752,7 @@ public function orWhereHas($relation, Closure $callback, $operator = '>=', $coun */ protected function addHasWhere(Builder $hasQuery, Relation $relation, $operator, $count, $boolean) { - $this->mergeWheresToHas($hasQuery, $relation); + $this->mergeModelDefinedRelationWheresToHasQuery($hasQuery, $relation); if (is_numeric($count)) { $count = new Expression($count); @@ -766,14 +768,14 @@ protected function addHasWhere(Builder $hasQuery, Relation $relation, $operator, * @param \Illuminate\Database\Eloquent\Relations\Relation $relation * @return void */ - protected function mergeWheresToHas(Builder $hasQuery, Relation $relation) + protected function mergeModelDefinedRelationWheresToHasQuery(Builder $hasQuery, Relation $relation) { // Here we have the "has" query and the original relation. We need to copy over any // where clauses the developer may have put in the relationship function over to // the has query, and then copy the bindings from the "has" query to the main. $relationQuery = $relation->getBaseQuery(); - $hasQuery->removeGlobalScopes()->mergeWheres( + $hasQuery->mergeWheres( $relationQuery->wheres, $relationQuery->getBindings() ); @@ -805,7 +807,7 @@ public function with($relations) $relations = func_get_args(); } - $eagers = $this->parseRelations($relations); + $eagers = $this->parseWithRelations($relations); $this->eagerLoad = array_merge($this->eagerLoad, $eagers); @@ -818,7 +820,7 @@ public function with($relations) * @param array $relations * @return array */ - protected function parseRelations(array $relations) + protected function parseWithRelations(array $relations) { $results = []; @@ -835,7 +837,7 @@ protected function parseRelations(array $relations) // We need to separate out any nested includes. Which allows the developers // to load deep relationships using "dots" without stating each level of // the relationship with its own key in the array of eager load names. - $results = $this->parseNested($name, $results); + $results = $this->parseNestedWith($name, $results); $results[$name] = $constraints; } @@ -850,7 +852,7 @@ protected function parseRelations(array $relations) * @param array $results * @return array */ - protected function parseNested($name, $results) + protected function parseNestedWith($name, $results) { $progress = []; @@ -885,12 +887,12 @@ protected function callScope($scope, $parameters) /** * Get the underlying query builder instance with applied global scopes. * - * @return \Illuminate\Database\Query\Builder|static + * @return \Illuminate\Database\Eloquent\Builder|static */ - public function getQueryWithScopes() + public function loadScopes() { if (! $this->scopes) { - return $this->getQuery(); + return $this; } $builder = clone $this; @@ -905,7 +907,7 @@ public function getQueryWithScopes() } } - return $builder->getQuery(); + return $builder; } /** @@ -1022,7 +1024,7 @@ public function __call($method, $parameters) } if (in_array($method, $this->passthru)) { - return call_user_func_array([$this->getQueryWithScopes(), $method], $parameters); + return call_user_func_array([$this->loadScopes()->getQuery(), $method], $parameters); } call_user_func_array([$this->query, $method], $parameters); diff --git a/src/Illuminate/Database/Eloquent/Model.php b/src/Illuminate/Database/Eloquent/Model.php index bc3fe251568c..9d5f0109976b 100755 --- a/src/Illuminate/Database/Eloquent/Model.php +++ b/src/Illuminate/Database/Eloquent/Model.php @@ -1869,7 +1869,7 @@ public function newQueryWithoutScope($scope) { $builder = $this->newQuery(); - return $builder->removeGlobalScope($scope); + return $builder->withoutGlobalScope($scope); } /** diff --git a/src/Illuminate/Database/Eloquent/SoftDeletingScope.php b/src/Illuminate/Database/Eloquent/SoftDeletingScope.php index 20482d6156f4..057b156a02e2 100644 --- a/src/Illuminate/Database/Eloquent/SoftDeletingScope.php +++ b/src/Illuminate/Database/Eloquent/SoftDeletingScope.php @@ -98,7 +98,7 @@ protected function addRestore(Builder $builder) protected function addWithTrashed(Builder $builder) { $builder->macro('withTrashed', function (Builder $builder) { - return $builder->removeGlobalScope($this); + return $builder->withoutGlobalScope($this); }); } @@ -113,7 +113,9 @@ protected function addOnlyTrashed(Builder $builder) $builder->macro('onlyTrashed', function (Builder $builder) { $model = $builder->getModel(); - $builder->removeGlobalScope($this)->getQuery()->whereNotNull($model->getQualifiedDeletedAtColumn()); + $builder->withoutGlobalScope($this)->whereNotNull( + $model->getQualifiedDeletedAtColumn() + ); return $builder; }); diff --git a/tests/Database/DatabaseEloquentGlobalScopesTest.php b/tests/Database/DatabaseEloquentGlobalScopesTest.php index 4a2cd540c8c1..285c13164099 100644 --- a/tests/Database/DatabaseEloquentGlobalScopesTest.php +++ b/tests/Database/DatabaseEloquentGlobalScopesTest.php @@ -20,7 +20,7 @@ public function testGlobalScopeIsApplied() public function testGlobalScopeCanBeRemoved() { $model = new EloquentGlobalScopesTestModel(); - $query = $model->newQuery()->removeGlobalScope(ActiveScope::class); + $query = $model->newQuery()->withoutGlobalScope(ActiveScope::class); $this->assertEquals('select * from "table"', $query->toSql()); $this->assertEquals([], $query->getBindings()); } @@ -36,7 +36,7 @@ public function testClosureGlobalScopeIsApplied() public function testClosureGlobalScopeCanBeRemoved() { $model = new EloquentClosureGlobalScopesTestModel(); - $query = $model->newQuery()->removeGlobalScope('active_scope'); + $query = $model->newQuery()->withoutGlobalScope('active_scope'); $this->assertEquals('select * from "table" order by "name" asc', $query->toSql()); $this->assertEquals([], $query->getBindings()); } @@ -48,7 +48,7 @@ public function testGlobalScopeCanBeRemovedAfterTheQueryIsExecuted() $this->assertEquals('select * from "table" where "active" = ? order by "name" asc', $query->toSql()); $this->assertEquals([1], $query->getBindings()); - $query->removeGlobalScope('active_scope'); + $query->withoutGlobalScope('active_scope'); $this->assertEquals('select * from "table" order by "name" asc', $query->toSql()); $this->assertEquals([], $query->getBindings()); } @@ -56,11 +56,11 @@ public function testGlobalScopeCanBeRemovedAfterTheQueryIsExecuted() public function testAllGlobalScopesCanBeRemoved() { $model = new EloquentClosureGlobalScopesTestModel(); - $query = $model->newQuery()->removeGlobalScopes(); + $query = $model->newQuery()->withoutGlobalScopes(); $this->assertEquals('select * from "table"', $query->toSql()); $this->assertEquals([], $query->getBindings()); - $query = EloquentClosureGlobalScopesTestModel::removeGlobalScopes(); + $query = EloquentClosureGlobalScopesTestModel::withoutGlobalScopes(); $this->assertEquals('select * from "table"', $query->toSql()); $this->assertEquals([], $query->getBindings()); } diff --git a/tests/Database/DatabaseEloquentSoftDeletesIntegrationTest.php b/tests/Database/DatabaseEloquentSoftDeletesIntegrationTest.php index ee055a956f60..a8343de5fed2 100644 --- a/tests/Database/DatabaseEloquentSoftDeletesIntegrationTest.php +++ b/tests/Database/DatabaseEloquentSoftDeletesIntegrationTest.php @@ -44,6 +44,14 @@ public function createSchema() $table->timestamps(); $table->softDeletes(); }); + + $this->schema()->create('comments', function ($table) { + $table->increments('id'); + $table->integer('post_id'); + $table->string('body'); + $table->timestamps(); + $table->softDeletes(); + }); } /** @@ -54,6 +62,8 @@ public function createSchema() public function tearDown() { $this->schema()->drop('users'); + $this->schema()->drop('posts'); + $this->schema()->drop('comments'); } /** @@ -137,11 +147,55 @@ public function testWhereHasWithDeletedRelationship() $abigail = SoftDeletesTestUser::where('email', 'abigailotwell@gmail.com')->first(); $post = $abigail->posts()->create(['title' => 'First Title']); - $post->delete(); + $users = SoftDeletesTestUser::where('email', 'taylorotwell@gmail.com')->has('posts')->get(); + $this->assertEquals(0, count($users)); + + $users = SoftDeletesTestUser::where('email', 'abigailotwell@gmail.com')->has('posts')->get(); + $this->assertEquals(1, count($users)); + + $users = SoftDeletesTestUser::where('email', 'doesnt@exist.com')->orHas('posts')->get(); + $this->assertEquals(1, count($users)); + + $users = SoftDeletesTestUser::whereHas('posts', function ($query) { + $query->where('title', 'First Title'); + })->get(); + $this->assertEquals(1, count($users)); + + $users = SoftDeletesTestUser::whereHas('posts', function ($query) { + $query->where('title', 'Another Title'); + })->get(); + $this->assertEquals(0, count($users)); + + $users = SoftDeletesTestUser::where('email', 'doesnt@exist.com')->orWhereHas('posts', function ($query) { + $query->where('title', 'First Title'); + })->get(); + $this->assertEquals(1, count($users)); + + // With Post Deleted... + + $post->delete(); $users = SoftDeletesTestUser::has('posts')->get(); + $this->assertEquals(0, count($users)); + } + /** + * @group test + */ + public function testWhereHasWithNestedDeletedRelationship() + { + $this->createUsers(); + + $abigail = SoftDeletesTestUser::where('email', 'abigailotwell@gmail.com')->first(); + $post = $abigail->posts()->create(['title' => 'First Title']); + $comment = $post->comments()->create(['body' => 'Comment Body']); + $comment->delete(); + + $users = SoftDeletesTestUser::has('posts.comments')->get(); $this->assertEquals(0, count($users)); + + $users = SoftDeletesTestUser::doesntHave('posts.comments')->get(); + $this->assertEquals(1, count($users)); } /** @@ -203,4 +257,22 @@ class SoftDeletesTestPost extends Eloquent protected $dates = ['deleted_at']; protected $table = 'posts'; protected $guarded = []; + + public function comments() + { + return $this->hasMany(SoftDeletesTestComment::class, 'post_id'); + } +} + + +/** + * Eloquent Models... + */ +class SoftDeletesTestComment extends Eloquent +{ + use SoftDeletes; + + protected $dates = ['deleted_at']; + protected $table = 'comments'; + protected $guarded = []; } diff --git a/tests/Database/DatabaseSoftDeletingScopeTest.php b/tests/Database/DatabaseSoftDeletingScopeTest.php index cd474edf96f9..d356da89ff75 100644 --- a/tests/Database/DatabaseSoftDeletingScopeTest.php +++ b/tests/Database/DatabaseSoftDeletingScopeTest.php @@ -60,7 +60,7 @@ public function testWithTrashedExtension() $callback = $builder->getMacro('withTrashed'); $givenBuilder = m::mock('Illuminate\Database\Eloquent\Builder'); $givenBuilder->shouldReceive('getModel')->andReturn($model = m::mock('Illuminate\Database\Eloquent\Model')); - $givenBuilder->shouldReceive('removeGlobalScope')->with($scope)->andReturn($givenBuilder); + $givenBuilder->shouldReceive('withoutGlobalScope')->with($scope)->andReturn($givenBuilder); $result = $callback($givenBuilder); $this->assertEquals($givenBuilder, $result); @@ -78,9 +78,9 @@ public function testOnlyTrashedExtension() $givenBuilder = m::mock('Illuminate\Database\Eloquent\Builder'); $givenBuilder->shouldReceive('getQuery')->andReturn($query = m::mock('StdClass')); $givenBuilder->shouldReceive('getModel')->andReturn($model); - $givenBuilder->shouldReceive('removeGlobalScope')->with($scope)->andReturn($givenBuilder); + $givenBuilder->shouldReceive('withoutGlobalScope')->with($scope)->andReturn($givenBuilder); $model->shouldReceive('getQualifiedDeletedAtColumn')->andReturn('table.deleted_at'); - $query->shouldReceive('whereNotNull')->once()->with('table.deleted_at'); + $givenBuilder->shouldReceive('whereNotNull')->once()->with('table.deleted_at'); $result = $callback($givenBuilder); $this->assertEquals($givenBuilder, $result);