Framework: withoutGlobalScopes() ignored on HasManyThrough

Created on 5 Feb 2018  路  18Comments  路  Source: laravel/framework

  • Laravel Version: 5.5.33 - 5.6.15
  • PHP Version: 7.2

Description:

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" = ?

Steps To Reproduce:

class User extends Model
{
    public function comments()
    {
        return $this->hasManyThrough(Comment::class, Post::class)
            ->withoutGlobalScopes();
    }
}
bug help wanted

Most helpful comment

Almost two years later and there is still no way of doing this without using an external package :/

All 18 comments

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.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

shopblocks picture shopblocks  路  3Comments

Fuzzyma picture Fuzzyma  路  3Comments

PhiloNL picture PhiloNL  路  3Comments

gabriellimo picture gabriellimo  路  3Comments

fideloper picture fideloper  路  3Comments