Если верить документации Laravel, «Многие-ко-Многим» — это почти сказка. Две модели, одна pivot-таблица, пара методов, и всё работает. На бумаге выглядит красиво. В реальности же ты сидишь, материшься и дебажишь, почему у тебя у пользователя три роли, но при выборке приходит четыре, и ещё половина pivot-данных внезапно не там.
Laravel любит делать вид, что Eloquent — это магия. Типа пишешь belongsToMany, и всё будет работать, как швейцарские часы. Только проблема в том, что твой проект — это не часы, а скорее советская стиралка, которая то шумит, то бьёт током, то выкидывает бельё на пол.
И вот ты сидишь и думаешь: «А надо ли оно мне вообще?». Но деваться некуда: пользователи должны иметь роли, роли — пользователей, а где-то там ещё правами машет pivot-таблица, которую называют то role_user, то user_roles, пока ты не запутаешься окончательно.
Пример
Допустим у тебя есть User, есть Role. Казалось бы, примитив. Взял — связал.
В коде красиво:
class User extends Model {
public function roles() {
return $this->belongsToMany(Role::class);
}
}
class Role extends Model {
public function users() {
return $this->belongsToMany(User::class);
}
}
Ну и, казалось бы, живи спокойно. Но нет. Потом приходит задача: «А давай хранить дату назначения роли». Ну разумеется, где? В pivot-таблице. То есть надо не просто связать, а ещё и метаданные туда впихнуть.
И вот ты уже не делаешь «attach» и «detach», а колдуешь с массивами:
$user->roles()->attach($roleId, ['assigned_at' => now()]);
Выглядит несложно. Пока не надо обновлять. Тогда всё превращается в пляски с sync, updateExistingPivot, и ты сидишь как шаман с бубном, пытаясь объяснить Laravel, что да, роль у пользователя есть, просто поле в pivot надо поменять, а не городить новую связь.
Грабли, которые можно словить
Названия pivot-таблиц.
Laravel по умолчанию ждёт, что таблица будет называться в алфавитном порядке: role_user, а не user_role. Один раз ошибся — всё, связь молчит, как будто её и нет. Потом полдня ковыряешься в SQL и думаешь, почему attach никуда не пишет.
Pivot-данные и массовые обновления.
Документация красиво показывает attach. А потом тебе нужно заменить у 1000 пользователей их роли. Ты жмёшь sync — и Laravel радостно удаляет старое и вставляет новое. Вроде и логично, но если ты случайно забыл передать что-то одно, полбазы без ролей.
Ленивая загрузка.
Сидишь такой, пишешь $user->roles, получаешь коллекцию. Потом запускаешь цикл по 5000 юзерам и внезапно понимаешь, что твой сервер превратился в обогреватель. Потому что у тебя N+1 запросов, и каждый $user->roles бьёт в базу. Ну да, можно with(), но кто про это вспоминает вовремя?
Сложные связи.
«А давай у нас роли будут не только у пользователей, но ещё и у групп». И ты понимаешь, что Eloquent тут уже не помощник. Начинается SQL руками, потому что belongsToMany красиво работает только на простых игрушках.
Почему это больно
Eloquent создаёт иллюзию простоты. Типа ты пишешь в стиле «человеческого языка», и база magically всё понимает. Но когда проект растёт, pivot-таблица превращается в помойку, а методы attach/detach/sync — в ловушку, где одно неверное движение, и ты снёс к чертям все связи.
Когда проект дорастает, большинство перестаёт верить в магию. Да, belongsToMany остаётся, но вся работа с pivot уходит в кастомные DAO или сервисы, где SQL пишется руками. Это скучно, но зато понятно: ты сам решаешь, что обновить, что вставить, а что оставить.
Laravel-шная магия норм для pet-project’а или админки «для своих». Но если у тебя реально важные данные — забудь про сказки. Контролируй процесс руками.
Итог
Отношения «Многие-ко-Многим» в Laravel — это как отношения в жизни: звучат красиво, но на практике всё грязно и местами больно. Да, они работают, но только пока проект маленький и чистый. Как только надо что-то больше простого «прикрутить роль к юзеру» — готовься к боли и костылям.
Так что, если хочешь меньше сюрпризов, держи Eloquent под контролем. А лучше — не верь магии до конца. Потому что в один день sync убьёт твои данные, и ты вспомнишь эту статью.
0 комментариев