Я выполняю определенный запрос отношения во всем приложении, где мне нужны только пользовательские subscriptions
, для которых в столбце active
установлено значение true
.
И у меня есть метод scope
в модели User
, который применяет указанный фильтр, чтобы избежать копирования/вставки, например:
public function scopeWithActiveSubscriptions($query)
{
$query->with([
'subscriptions' => function ($query) {
$query->where('active', true);
},
]);
}
Теперь иногда я тоже хочу с нетерпением загрузить plan
каждого subscription
.
Для этого я попробовал что-то вроде:
$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 еще нет решения цепочка (в стиле Builder) (для заданной ситуации), и мы закончили тем, что отредактировали фильтр 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.)
d= (◕‿↼ ) Я приму это позже, в надежде, что кто-то знает еще лучший Laravel магия.