Логика связей в Laravel осуществляется с помощью класса Model, который упрощает работу и управление такими отношениями и поддерживает следующие типы связей:
Один к одному / hasOne
Связь вида «один к одному» является очень простой. К примеру, если пользователь может содержать один телефон, то модель User связана с моделью Phone связью hasOne(). Мы можем определить такое отношение:
class User extends Model{ public function phone() { return $this->hasOne('Phone'); } }
По умолчанию считается, что поле в связующей таблице называется по имени модели плюс _id. В данном случае предполагается, что таблица phones содержит поле user_id. Если мы хотим переопределить стандартное имя, необходимо передать второй параметр методу hasOne(). Кроме того, мы можем передать в метод третий аргумент, чтобы указать, какие локальные столбцы (модели User) следует использовать для объединения:
return $this->hasOne('Phone', 'foreign_key'); return $this->hasOne('Phone', 'foreign_key', 'local_key');
Принадлежность / belongsTo
Для создания обратного отношения в модели имеется специальный метод belongsTo() («принадлежит к»). Если пользователь hasOne телефон, то телефон belongsTo пользователю.
class Phone extends Model{ public function user() { return $this->belongsTo('User'); } }
В примере выше модель Phone будет искать поле user_id в таблице phones. Если вы хотите назвать внешний ключ по другому, передайте это имя вторым параметром к методу belongsTo():
return $this->belongsTo('App\User', 'local_key');
Третий аргумент определит имя связного столбца в родительской таблице.
return $this->belongsTo('App\User', 'local_key', 'parent_key');
Один ко многим / hasMany
Один пользователь может содержать множество телефонов. Тогда следует использовать связь hasMany(). Другими примерами данного отношения «один ко многим» является статья в блоге, которая имеет «много» комментариев или мама, имеющая детей. Мы можем смоделировать это отношение таким образом:
class User extends Model{ public function phones() { return $this->hasMany('Phone'); } }
Теперь мы можем получить все телефоны пользователя с помощью динамического свойства:
$phones = User::find(1)->phones;
В этом случае, мы получили массив телефонов, и для просмотра всех телефонов, нужно пройтись по элементам массива.
foreach($phones as $one){ echo $one->body; echo '\n'; }
Если нужно добавить ограничения на получаемые телефоны, можно вызвать метод phones() и продолжить добавлять условия:
$phone = User::find(1)->phones()->where('title', '=', 'foo')->first();
Теперь мы получили один телефон, который сразу можно вывести на экран:
$phone->body
Рассмотрим еще один пример, который выявит недостатки связи hasMany. Предположим, у нас есть модель Book и модель Author. Book принадлежит (связь belongsTo) Author. И наоборот, Author содержит (связь hasMany) множество Book.
Определение связи в модели Book:
class Book extends Model { public function author() { return $this->belongsTo('Author'); } }
Получить все названия книг можно следующим образом:
foreach (Book::all() as $book) { echo $book->name; }
Мы обошлись всего одним заросом. Даже если будет 100 книг, это всё-равно один запрос:
SELECT * FROM books
Однако, если к выводу названия книги добавим автора:
foreach (Book::all() as $book) { echo $book->name; echo $book->author->name; }
То, получаем 101 запрос. Первым запросом получаем все книги, а потом в цикле делаем запрос на получение автора книги.
SELECT * FROM books; SELECT * FROM authors WHERE id = 1; SELECT * FROM authors WHERE id = 2; SELECT * FROM authors WHERE id = 3; ...
Это очень не рационально. И чтобы сократить нагрузку на сервер бызы данных, можно воспользоваться активной загрузкой.
Активная загрузка
Акнивная загрузка подразумевает подключение связующей модели с помощью метода with
.
foreach (Book::with('author')->get() as $book) { echo $book->author->name; }
Таким образом будут выполнены всего два запроса:
SELECT * FROM books; SELECT * FROM authors WHERE id IN(1,2,3);
Конечно, вы можете загрузить несколько отношений одновременно:
$books = Book::with('author', 'publisher')->get();
Разумное использование активной загрузки поможет сильно повысить производительность приложения.
Многие ко многим / belongsToMany
Отношения типа «многие ко многим» сложнее отношений hasOne() и hasMany(). Примером может служить пользователь, имеющий много ролей, где роли также относятся ко многим пользователям. Например, несколько пользователей могут иметь роль «Admin». Нужны три таблицы для этой связи: users
, roles
и role_user
. Имя таблицы role_user получается из упорядоченных по алфавиту имён связанных моделей, она должна иметь поля user_id
и role_id
.
Вы можете определить отношение «многие ко многим», написав метод, возвращающий результат метода belongsToMany(). Давайте определим метод roles() для модели User:
namespace App; use Illuminate\Database\Eloquent\Model; class User extends Model { /** * Роли, принадлежащие пользователю. */ public function roles() { return $this->belongsToMany('App\Role'); } }
Теперь мы можем получить роли пользователя через динамическое свойство roles:
$user = App\User::find(1); foreach ($user->roles as $role) { // }
Как и для других типов отношений, вы можете вызвать метод roles(), продолжив конструировать запрос для отношения:
$roles = App\User::find(1)->roles()->orderBy('name')->get();
Вы можете переопределить имя таблицы, передав второй параметр методу belongsToMany():
return $this->belongsToMany('App\Role', 'role_user');
В дополнение к заданию имени соединительной таблицы, вы можете также задать имена столбцов ключей в таблице, передав дополнительные параметры методу belongsToMany(). Третий аргумент — это имя внешнего ключа модели, на которой вы определяете отношения, в то время как четвертый аргумент — это внешний ключ модели, с которой вы собираетесь связаться:
return $this->belongsToMany('App\Role', 'role_user', 'user_id', 'role_id');
Чтобы определить обратное отношение «многие-ко-многим», просто поместите другой вызов belongsToMany() на вашу модель. Чтобы продолжить пример с ролями пользователя, давайте определим метод users() для модели Role:
namespace App; use Illuminate\Database\Eloquent\Model; class Role extends Model { /** * Пользователи, принадлежащие роли. */ public function users() { return $this->belongsToMany('App\User'); } }
работа с отношением «многие-ко-многим» требует наличия промежуточной таблицы. Eloquent предоставляет некоторые очень полезные способы взаимодействия с такой таблицей. Например, давайте предположим, что наш объект User имеет много связанных с ним объектов Role. После получения доступа к этому отношению мы можем получить доступ к промежуточной таблице с помощью pivot атрибута модели:
$user = App\User::find(1); foreach ($user->roles as $role) { echo $role->pivot->created_at; }
Обратите внимание на то, что каждой полученной модели Role автоматически присваивается атрибут pivot. Этот атрибут содержит модель, представляющую промежуточную таблицу, и может быть использован, как и любая другая модель.
По умолчанию, только ключи модели будут представлять pivot объект. Если ваша «pivot» таблица содержит дополнительные атрибуты, вам необходимо указать их при определении отношения:
return $this->belongsToMany('App\Role')->withPivot('column1', 'column2');
Если вы хотите, чтобы ваша «pivot» таблица автоматически поддерживала временные метки created_at и updated_at, используйте метод withTimestamps()
при определении отношений:
return $this->belongsToMany('App\Role')->withTimestamps();
Вы также можете отфильтровать результаты, возвращённые методом belongsToMany(), с помощью методов wherePivot()
и wherePivotIn()
при определении отношения
return $this->belongsToMany('App\Role')->wherePivot('approved', 1); return $this->belongsToMany('App\Role')->wherePivotIn('priority', [1, 2]);
Ко многим через / hasManyThrough
Связь «ко многим через» обеспечивает удобный короткий путь для доступа к удалённым отношениям через промежуточные. Например, модель Country может иметь много Post через модель User. В данном примере вы можете просто собрать все статьи для заданной country. Таблицы для этих отношений будут выглядеть так:
countries id - integer name - string users id - integer country_id - integer name - string posts id - integer user_id - integer title - string
Несмотря на то, что таблица posts не содержит столбца country_id, отношение «ко многим через» позволит нам получить доступ к posts через country с помощью $country->posts. Для выполнения этого запроса Eloquent ищет country_id в промежуточной таблице users. После нахождения совпадающих ID пользователей они используются в запросе к таблице posts.
Теперь, когда мы рассмотрели структуру таблицы для отношений, давайте определим отношения для модели Country:
namespace App; use Illuminate\Database\Eloquent\Model; class Country extends Model { /** * Получить все статьи по заданной области. */ public function posts() { return $this->hasManyThrough('App\Post', 'App\User'); } }
Первый параметр, переданный в метод hasManyThrough()
является именем конечной модели, которую мы получаем, а второй параметр — это имя промежуточной модели. Третий параметр — имя внешнего ключа для промежуточной модели, четвертый параметр — имя внешнего ключа для конечной модели, а пятый аргумент (для версии 5.2 и выше) — локальный ключ:
class Country extends Model { public function posts() { return $this->hasManyThrough( 'App\Post', 'App\User', 'country_id', 'user_id', 'id' ); } }
Полиморфные отношения один ко многим
Полиморфные отношения позволяют модели быть связанной с более чем одной моделью.
Пример: три модели Мan (Мужчина), Woman (Женщина) и Car (Автомобиль), соответствующие таблицам men, women и cars. Мужчина (buyer) может купить несколько автомобилей, женщина (buyer) может купить несколько автомобилей, автомобиль может быть приобретен одним покупателем (мужчиной или женщиной). Где buyer - это условное название группы моделей (Man и Woman). Причем, количество моделей в ней не ограничено двумя.
Автомобиль должен хранить идентификатор покупателя (Buyer ID) и вид покупателя (Buyer Type).
class Man extends Model { public function cars() { return $this->morphMany(Car::class, 'buyer'); } } class Woman extends Model { public function cars() { return $this->morphMany(Car::class, 'buyer'); } } class Car extends Model { public function buyer() { return $this->morphTo(); } }
Сохранение записей:
// Создание отношений между покупателем (Man/Woman) и Car. $man->cars()->saveMany([ $car1, $car2, ]); $woman->cars()->saveMany([ $car1, $car2, ]); // Или используйте метод save() $man->cars()->save($car); $woman->cars()->save($car); // Создание отношений между Car и buyer (Men/Women). $car1->buyer()->associate($man)->save(); $car2->buyer()->associate($woman)->save();
Извлечение записей:
// Получение автомобилей покупателя $men->cars $women->cars // Получение покупателя автомобиля $car->buyer
Полиморфные связи многие ко многим
В дополнение к традиционным полиморфным связям вы можете также задать полиморфные связи многие ко многим. Например, модели блогов Post и Video могут разделять полиморфную связь с моделью Tag. Используя полиморфное отношение «многие-ко-многим», вы имеете единственный список уникальных тегов, которые совместно используются через сообщения в блоге и видео. Во-первых, давайте рассмотрим структуру таблиц:
posts id - integer name - string videos id - integer name - string tags id - integer name - string taggables tag_id - integer taggable_id - integer taggable_type - string
Теперь мы готовы к установке связи с моделью. Обе модели Post и Video будут иметь связь morphToMany()
через метод tags():
namespace App; use Illuminate\Database\Eloquent\Model; class Post extends Model { /** * Получить все теги статьи. */ public function tags() { return $this->morphToMany('App\Tag', 'taggable'); } }
Теперь для модели Tag вы должны определить метод для каждой из моделей отношения. Для нашего примера мы определим метод posts() и метод videos():
namespace App; use Illuminate\Database\Eloquent\Model; class Tag extends Model { /** * Получить все сообщения, связанные с тегом. */ public function posts() { return $this->morphedByMany('App\Post', 'taggable'); } /** * Получить все видео, связанные с тегом. */ public function videos() { return $this->morphedByMany('App\Video', 'taggable'); } }
Извлечение отношений
$post = App\Post::find(1); foreach ($post->tags as $tag) { // }
Вы можете также получить владельца полиморфного отношения от полиморфной модели, получив доступ к имени метода, который выполняет вызов morphedByMany(). В нашем случае, это метод posts() или videos() для модели Tag. Так вы получите доступ к этим методам как к динамическим свойствам:
$tag = App\Tag::find(1); foreach ($tag->videos as $video) { // }