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);
},
]);
}
现在有时我也想预先加载每个 subscription
的 plan
。
为此,我尝试了类似的方法:
$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.)
我在整个应用程序中执行了一个特定的关系查询,其中我只需要将 active
列设置为 true
的用户 subscriptions
。
我在 User
模型中有一个 scope
方法,它应用所述过滤器来避免 copy/paste,例如:
public function scopeWithActiveSubscriptions($query)
{
$query->with([
'subscriptions' => function ($query) {
$query->where('active', true);
},
]);
}
现在有时我也想预先加载每个 subscription
的 plan
。
为此,我尝试了类似的方法:
$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.)