Laravel - 嵌套关系导致先前的过滤器被忽略

Laravel - Nested relation causes previous filters to be ignored

我在整个应用程序中执行了一个特定的关系查询,其中我只需要将 active 列设置为 true 的用户 subscriptions

我在 User 模型中有一个 scope 方法,它应用所述过滤器来避免 copy/paste,例如:

public function scopeWithActiveSubscriptions($query)
{
    $query->with([
        'subscriptions' => function ($query) {
            $query->where('active', true);
        },
    ]);
}

现在有时我也想预先加载每个 subscriptionplan

为此,我尝试了类似的方法:

$user = User::where('id', 1)
    ->withActiveSubscriptions()
    ->with('subscriptions.plan')
    ->first();

$subscriptionList = $user->subscriptions;

但是查询结果到所有个订阅, 换句话说,忽略 ->where('active', true) 部分(scope 方法)。

我怎样才能使它正常工作?

一个快速的解决方案是修改 scopeWithActiveSubscriptions 方法以允许它接受另一个可选参数,该参数告诉它还应该包括哪些附加关系,这样您就不会丢失过滤。

public function scopeWithActiveSubscriptions($query, array $with = [])
{
    // just merges hard coded subscription filtering with the supplied relations from $with parameter
    $query->with(array_merge([
        'subscriptions' => function ($query) {
            $query->where('active', true);
        }
    ], $with));
}

现在您可以告诉范围您想要包含哪些嵌套关系,您不再需要自己调用 with 来包含它们。

$user = User::where('id', 1)
    ->withActiveSubscriptions(['subscriptions.plan'])
    // ->with('subscriptions.plan') // no longer needed as we're telling the scope to do that for us
    ->first();

$subscriptionList = $user->subscriptions;

有了它,您可以将自定义关系传递给范围,例如 (我在这里即兴创作只是为了演示目的)

$user = User::where('id', 1)
    ->withActiveSubscriptions([
        'subscriptions.plan' => fn($q) => $q->where('plans.type', 'GOLD')
    ])->first();

Learn more about Laravel's Eloquent Scopes.

希望我已经把你推得更远了。

似乎 Laravel 还没有任何 chainable (Builder-style) 解决方案(对于被问到的情况),我们最终编辑了 scope 过滤器。

变成这样的东西:

public function scopeWithPendingSubscriptions(Builder $query, $subRelations = null)
{
    $query->with([
        'subscriptions' => function (HasMany $query) use ($subRelations) {
            $query->where('active', '=', true);
            if ($subRelations) {
                $query->with($subRelations);
            }
        },
    ]);
}

这让我可以进行如下查询:

// ...
->withActiveSubscriptions('plan');

而不是我的旧(不工作)代码,它是:

// ...
->withActiveSubscriptions()
->with('subscriptions.plan');

Note that even passing nested-filters is now possible, like:

// ...
->withActiveSubscriptions(['plan' => function ($query) {
   $query->where('name');
}]);

(Basically same as Laravel's ->with(...) method.)