
Laravel withWhereHas(): не дублюйте умову в with() і whereHas()
Як замінити однакову умову в whereHas() і with() на withWhereHas(), одночасно відфільтрувати батьківські моделі та завантажити лише відповідні пов'язані записи.
Іноді потрібно одночасно зробити дві речі: вибрати тільки ті моделі, у яких є пов'язані записи з певною умовою, і завантажити тільки ці пов'язані записи.
Якщо окремо використовувати whereHas() і with(), одна й та сама умова легко з'являється у коді двічі. Для такого випадку Laravel має withWhereHas().
Замість однакової умови у whereHas() і with()
$posts = Post::query() ->whereHas( 'comments', fn ($query) => $query ->where('approved', true), ) ->with([ 'comments' => fn ($query) => $query ->where('approved', true), ]) ->get();$posts = Post::query() ->withWhereHas( 'comments', fn ($query) => $query ->where('approved', true), ) ->get();Обидва варіанти мають однаковий намір: отримати тільки дописи, у яких є схвалені коментарі, і одразу завантажити тільки схвалені коментарі.
З withWhereHas() умова описана один раз. Якщо її потрібно змінити, немає ризику виправити whereHas() і забути про таку саму умову в with().
Що саме робить withWhereHas()
$posts = Post::query() ->withWhereHas( 'comments', fn ($query) => $query ->where('approved', true), ) ->get();withWhereHas() використовує одну й ту саму умову у двох місцях:
- залишає тільки ті
Post, які мають схвалені коментарі; - попередньо завантажує тільки схвалені
comments.
Тому після виконання запиту в $post->comments будуть саме ті коментарі, які відповідають умові.
Коли достатньо whereHas()
$posts = Post::query() ->whereHas( 'comments', fn ($query) => $query ->where('approved', true), ) ->get();Якщо потрібно лише знайти дописи, які мають схвалені коментарі, але самі коментарі зараз не потрібні, достатньо whereHas().
Коли достатньо with()
$posts = Post::query() ->with([ 'comments' => fn ($query) => $query ->where('approved', true), ]) ->get();Якщо потрібно отримати всі дописи, але для кожного завантажити тільки схвалені коментарі, використовуйте with(). Наявність схваленого коментаря в цьому випадку не впливає на те, чи потрапить сам допис у результат.
withWhereHas() потрібний, коли треба обидві дії
$posts = Post::query() ->withWhereHas( 'comments', fn ($query) => $query ->where('approved', true), ) ->get();Це саме той випадок, для якого withWhereHas() читається найкраще: зв'язок впливає і на відбір батьківських моделей, і на те, які пов'язані записи потрібно завантажити.
Якщо умови навмисно різні, не об'єднуйте їх
$posts = Post::query() ->whereHas( 'comments', fn ($query) => $query ->where('approved', true), ) ->with([ 'comments' => fn ($query) => $query ->latest(), ]) ->get();Тут логіка вже інша: допис повинен мати хоча б один схвалений коментар, але завантажити потрібно всі коментарі, відсортовані від нових до старих.
У такому випадку окремі whereHas() і with() правильніші. withWhereHas() не потрібно використовувати лише заради коротшого запису, якщо умови насправді різні.
Спільна умова повинна мати однаковий зміст в обох місцях
Функція зворотного виклику в withWhereHas() використовується і для перевірки зв'язку, і для його попереднього завантаження.
Тому сюди добре підходять саме спільні умови відбору: where(), scopes та інші правила, які повинні однаково діяти в обох частинах запиту.
withWhereHas() варто використовувати тоді, коли одна й та сама умова повинна одночасно визначати, які батьківські моделі потраплять у результат і які пов'язані записи будуть завантажені. Один намір - одна умова.




