It seems like withoutGlobalScopes() and withoutGlobalScope() is ignored on HasManyThrough relations.
The query generated by the example below is:
SELECT
*
FROM
"comments"
INNER JOIN
"posts" ON "post"."id" = "comments"."post_id"
WHERE
"posts"."deleted_at" IS NULL
AND "posts"."user_id" = ?
But should be:
SELECT
*
FROM
"comments"
INNER JOIN
"posts" ON "post"."id" = "comments"."post_id"
WHERE
"posts"."user_id" = ?
class User extends Model
{
public function comments()
{
return $this->hasManyThrough(Comment::class, Post::class)
->withoutGlobalScopes();
}
}
Does it work if you run withoutGlobalScopes before the relationship?
This is why I believe the above will work: https://github.com/laravel/framework/blob/56a58e0fa3d845bb992d7c64ac9bb6d0c24b745a/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php#L251-L263
Because of this, I think this is possibly related to #18052 and #21856
What do you mean by "run before the relationship"?
dd(
$user->comments()->toSql(),
$user->comments()->withoutGlobalScopes()->toSql()
);
Both build the same query with with:
"posts"."deleted_at" IS NULL
For example, $user->withoutGlobalScopes()->comments() possibly?
Call to undefined method Illuminate\Database\Query\Builder::comments()
Forgot that it's the trait's function returning $this. Seems like it could be a legitimate bug.
Does withoutGlobalScopes() apply only current model not the related model?
$store = Store::with('user')
->withoutGlobalScopes()
->where('storename', $storename)
->first();
also does not work.
Store does not have scope but User has. It does not apply for User therefore it returns
[
...
user: null
]
Please need some help
The intermediate model is a complicated case. Just calling the relationship ($user->comments()) already adds the SoftDeletes constraint to the query. ->withoutGlobalScopes() would have to remove it retroactively.
Somewhat related but the opposite problem: Other global scopes on the intermediate model don't get applied at all:
class Post extends Model {
protected static function boot() {
parent::boot();
static::addGlobalScope('test', function ($query) {
$query->where('column', 'value');
});
}
}
Perhaps a better solution for this but this is how I got around this problem by extending the hasManyThrough relationship when I needed to extend the existing soft deletes trait and scope to work with a different database structure that doesnt use deleted_at columns with a timestamp
<?php
namespace App\Eloquent\Relations;
use Illuminate\Database\Eloquent;
class HasManyThrough extends Eloquent\Relations\HasManyThrough
{
/**
* Set the join clause on the query
* @param \Illuminate\Database\Eloquent\Builder|null $query
* @return void
*/
protected function setJoin(Eloquent\Builder $query = null)
{
$query = ($query) ?: $this->query;
$strForeignKey = $this->related->getTable().'.'.$this->secondKey;
$query->join($this->parent->getTable(), $this->getQualifiedParentKeyName(), '=', $strForeignKey);
if ($Scope = $this->parentSoftDeletes()) {
// Eloquent hard codes $query->whereNull($this->parent->getQualifiedDeletedAtColumn())
// on HasManyThrough which breaks everything, so use our instance of SoftDeletingScope instead
$Scope->apply($query, $this->parent);
}
}
/**
* Determine whether close parent of the relation uses Soft Deletes
* @return Eloquent\SoftDeletingScope|null
*/
public function parentSoftDeletes()
{
$arrRemovedScopes = $this->query->removedScopes();
// Get the instance of SoftDeletingScope on the parent model that requires it
foreach ($this->parent->getGlobalScopes() as $strClassName => $Scope) {
if (
$Scope instanceof Eloquent\SoftDeletingScope &&
!in_array($strClassName, $arrRemovedScopes)
) {
return $Scope;
}
}
// Seems unlikely but just in case SoftDeletes is on the model but the scope wasnt applied / failed to boot
if (
!in_array(Eloquent\SoftDeletingScope::class, $arrRemovedScopes) &&
in_array(Eloquent\SoftDeletes::class, class_uses_recursive(get_class($this->parent)))
) {
return new Eloquent\SoftDeletingScope();
}
return null;
}
}
Almost two years later and there is still no way of doing this without using an external package :/
@LonnyX you're free to help out by sending in a PR.
+1
Tried to reproduce this in tests to fix it, but it seems it's working correctly now (at least in 6.x).
@tillkruss is it working now? Or my test scenario is not as same as yours?
https://github.com/laravel/framework/commit/74f7b9e9d9a2a766d813228492e484761cad9c64
@yaim Your test scenario is different, the issue is about global scopes on the _intermediate_ model.
Fixed by #32609
Somewhat related but the opposite problem: Other global scopes on the intermediate model don't get applied at all
I stumbled upon that today @staudenmeir, what a surprise to discover that!
Before I deep dive into the code, do you think a PR could be issued for this particular case, or is there something I'm not aware of that prevents from having a global solution?
@ChristopheB I looked into it back then when I noticed the issue but didn't see a good solution. It would also be quite a breaking change if we change/fix this now.
I guess HasManyThrough relationships just won't support any other intermediate global scopes besides the native SoftDeletes.
@staudenmeir thanks for your input. We have decided not to use global scopes on our project, so sadly I think I won't have the time to try to fix that. Still I could issue a PR for the docs.
Most helpful comment
Almost two years later and there is still no way of doing this without using an external package :/