Может войдёшь?
Черновики Написать статью Профиль

Отношения

перевод документация 5.х

  1. 1. Введение
  2. 2. Определение отношений
    1. 2.1. Один к одному
    2. 2.2. Один ко многим
    3. 2.3. Один ко многим (Обратное отношение)
    4. 2.4. Многие ко многим
    5. 2.5. Ко многим через
    6. 2.6. Полиморфные отношения
    7. 2.7. Полиморфные связи многие ко многим
    8. 2.8. Запросы к отношениям
    9. 2.9. Методы отношений или динамические свойства
    10. 2.10. Проверка существования связей при выборке
    11. 2.11. Выборка по отсутствию отношения
    12. 2.12. Подсчёт моделей в отношении
  3. 3. Нетерпеливая загрузка
    1. 3.1. Ограничение нетерпеливых загрузок
    2. 3.2. Ленивая нетерпеливая загрузка
  4. 4. Вставка и изменение связанных моделей
    1. 4.1. Метод Save
    2. 4.2. Метод Create
    3. 4.3. Отношение «принадлежит к»
    4. 4.4. Отношение многие-ко-многим
  5. 5. Привязка родительских меток времени
Этот перевод актуален для англоязычной документации на (ветка 5.3) , (ветка 5.2) и (ветка 5.1). Опечатка? Выдели и нажми Ctrl+Enter.

Введение

Ваши таблицы скорее всего как-то связаны с другими таблицами БД. Например, статья в блоге может иметь много комментариев, а заказ может быть связан с оставившим его пользователем. Eloquent упрощает работу и управление такими отношениями. Laravel поддерживает многие типы связей:

  1. Один к одному
  2. Один ко многим
  3. Многие ко многим
  4. Ко многим через
  5. Полиморфные связи
  6. Полиморфные связи «многие ко многим»

Определение отношений

Eloquent отношения определены как функции в ваших классах модели Eloquent. Как и сами модели Eloquent, отношения являются мощными конструкторами запросов, которые определяют отношения как функции, и обеспечивают мощную сцепку методов и возможности для запросов. Например, мы можем прицепить дополнительные ограничения к отношению этих posts:

PHP
$user->posts()->where('active'1)->get();

Но прежде чем погрузиться в использование отношений, давайте узнаем, как определяется каждый тип отношений.

Один к одному

Связь вида «один к одному» является очень простой. К примеру, модель User может иметь один Phone. Чтобы определить такое отношение, мы помещаем метод PHPphone() в модель User. Метод PHPphone() должен вызвать метод PHPhasOne() и вернуть его результат:

PHP
<?php

namespace App;

use 
Illuminate\Database\Eloquent\Model;

class 
User extends Model
{
  
/**
   * Получить запись с номером телефона пользователя.
   */
  
public function phone()
  {
    return 
$this->hasOne('App\Phone');
  }
}

Первый параметр, передаваемый PHPhasOne(), — имя связанной модели. Как только отношение установлено, вы можете получить к нему доступ через динамические свойства Eloquent. Динамические свойства позволяют вам получить доступ к функциям отношений, если бы они были свойствами модели:

PHP
$phone User::find(1)->phone;

Eloquent определяет внешний ключ отношения по имени модели. В данном случае предполагается, что это user_id. Если вы хотите перекрыть стандартное имя, передайте второй параметр методу PHPhasOne():

PHP
return $this->hasOne('App\Phone''foreign_key');

Также Eloquent подразумевает, что внешний ключ должен иметь значение, привязанное к родительскому столбцу id (или другому $primaryKey). Другими словами, Eloquent будет искать значение столбца id пользователя в столбце user_id записи Phone. Кроме того вы можете передать в метод третий аргумент, чтобы указать свой столбец для объединения:

PHP
return $this->hasOne('App\Phone''foreign_key''local_key');

Создание обратного отношения

Итак, у нас есть доступ к модели Phone из нашего User. Теперь давайте определим отношение для модели Phone, которое будет иметь доступ к User, владеющего этим телефоном. Для создания обратного отношения в модели Phone используйте метод PHPbelongsTo():

PHP
<?php

namespace App;

use 
Illuminate\Database\Eloquent\Model;

class 
Phone extends Model
{
  
/**
    * Получить пользователя, владеющего данным телефоном.
    */
  
public function user()
  {
    return 
$this->belongsTo('App\User');
  }
}

В примере выше Eloquent будет искать связь между user_id в модели Phone и id в модели User. По умолчанию Eloquent определяет имя внешнего ключа по имени метода отношения, добавляя суффикс _id. Однако, если имя внешнего ключа модели Phone не user_id, передайте это имя вторым параметром в метод PHPbelongsTo():

PHP
/**
  * Получить пользователя, владеющего данным телефоном.
  */
public function user()
{
  return 
$this->belongsTo('App\User''foreign_key');
}

Если ваша родительская модель не использует id в качестве первичного ключа, или вам бы хотелось присоединить дочернюю модель к другому столбцу, вы можете передать третий параметр в метод PHPbelongsTo(), который определяет имя связанного столбца в родительской таблице:

PHP
/**
* Получить пользователя, владеющего данным телефоном.
*/
public function user()
{
  return 
$this->belongsTo('App\User''foreign_key''other_key');
}

Один ко многим

Отношение «один ко многим» используется для определения отношений, где одна модель владеет некоторым количеством других моделей. Примером отношения «один ко многим» является статья в блоге, которая имеет «много» комментариев. Как и другие отношения Eloquent вы можете смоделировать это отношение таким образом:

PHP
<?php

namespace App;

use 
Illuminate\Database\Eloquent\Model;

class 
Post extends Model
{
  
/**
   * Получить комментарии статьи блога.
   */
  
public function comments()
  {
    return 
$this->hasMany('App\Comment');
  }
}

Помните, что Eloquent автоматически определяет столбец внешнего ключа в модели Comment. По соглашению, Eloquent возьмёт «snake case» названия владеющей модели плюс _id. Таким образом, для данного примера, Eloquent предполагает, что внешним ключом для модели Comment будет post_id.

После определения отношения мы можем получить доступ к коллекции комментариев, обратившись к свойству comments. Помните, что поскольку Eloquent поддерживает «динамические свойства», мы можем обращаться к функциям отношений, как если бы они были определены свойством модели:

PHP
$comments App\Post::find(1)->comments;

foreach (
$comments as $comment) {
  
//
}

Конечно, так как отношения служат и в качестве конструкторов запросов, вы можете добавлять дополнительные условия к тем комментариям, которые получены вызовом метода PHPcomments():

PHP
$comments App\Post::find(1)->comments()->where('title''foo')->first();

Как и для метода PHPhasOne() вы можете указать внешний и локальный ключи, передав дополнительные параметры в метод PHPhasMany():

PHP
return $this->hasMany('App\Comment''foreign_key');

return 
$this->hasMany('App\Comment''foreign_key''local_key');

Один ко многим (Обратное отношение)

После получения доступа ко всем комментариям статьи давайте определим отношение, которое позволит комментарию получить доступ к его статье. Чтобы определить обратное отношение PHPhasMany(), определим функцию отношения на дочерней модели, которая вызывает метод PHPbelongsTo():

PHP
<?php

namespace App;

use 
Illuminate\Database\Eloquent\Model;

class 
Comment extends Model
{
  
/**
   * Получить статью данного комментария.
   */
  
public function post()
  {
    return 
$this->belongsTo('App\Post');
  }
}

После определения отношений мы можем получить модель Post для Comment, обратившись к динамическому свойству post:

PHP
$comment App\Comment::find(1);

echo 
$comment->post->title;

В примере выше Eloquent пробует связать post_id из модели Comment с id модели Post. По умолчанию Eloquent определяет внешний ключ по имени метода отношения плюс _id. Однако, если внешний ключ для модели Comment не post_id, вы можете передать своё имя вторым параметром в метод PHPbelongsTo():

PHP
/**
 * Получить статью данного комментария.
 */
public function post()
{
  return 
$this->belongsTo('App\Post''foreign_key');
}

Если ваша родительская модель не использует id в качестве первичного ключа, или вам бы хотелось присоединить дочернюю модель к другому столбцу, вы можете передать третий параметр в метод PHPbelongsTo(), который определяет имя связанного столбца в родительской таблице:

PHP
/**
 * Получить статью данного комментария.
 */
public function post()
{
  return 
$this->belongsTo('App\Post''foreign_key''other_key');
}

Многие ко многим

Отношения типа «многие ко многим» сложнее отношений PHPhasOne() и PHPhasMany(). Примером может служить пользователь, имеющий много ролей, где роли также относятся ко многим пользователям. Например, несколько пользователей могут иметь роль «Admin». Нужны три таблицы для этой связи: users, roles и role_user. Имя таблицы role_user получается из упорядоченных по алфавиту имён связанных моделей, она должна иметь поля user_id и role_id.

Вы можете определить отношение «многие ко многим», написав метод, возвращающий результат метода PHPbelongsToMany(). Давайте определим метод PHProles() для модели User:

PHP
<?php

namespace App;

use 
Illuminate\Database\Eloquent\Model;

class 
User extends Model
{
  
/**
   * Роли, принадлежащие пользователю.
   */
  
public function roles()
  {
    return 
$this->belongsToMany('App\Role');
  }
}

Теперь мы можем получить роли пользователя через динамическое свойство roles:

PHP
$user App\User::find(1);

foreach (
$user->roles as $role) {
  
//
}

Естественно, как и для других типов отношений, вы можете вызвать метод PHProles(), продолжив конструировать запрос для отношения:

PHP
$roles App\User::find(1)->roles()->orderBy('name')->get();

Как уже упоминалось ранее, чтобы определить имя для таблицы присоединения отношений, Eloquent соединит два названия взаимосвязанных моделей в алфавитном порядке. Тем не менее, вы можете переопределить имя, передав второй параметр методу PHPbelongsToMany():

PHP
return $this->belongsToMany('App\Role''role_user');

В дополнение к заданию имени соединительной таблицы, вы можете также задать имена столбцов ключей в таблице, передав дополнительные параметры методу PHPbelongsToMany(). Третий аргумент — это имя внешнего ключа модели, на которой вы определяете отношения, в то время как четвертый аргумент — это внешний ключ модели, с которой вы собираетесь связаться:

PHP
return $this->belongsToMany('App\Role''role_user''user_id''role_id');

Чтобы определить обратное отношение «многие-ко-многим», просто поместите другой вызов PHPbelongsToMany() на вашу модель. Чтобы продолжить пример с ролями пользователя, давайте определим метод PHPusers() для модели Role:

PHP
<?php

namespace App;

use 
Illuminate\Database\Eloquent\Model;

class 
Role extends Model
{
  
/**
   * Пользователи, принадлежащие роли.
   */
  
public function users()
  {
    return 
$this->belongsToMany('App\User');
  }
}

Как вы можете видеть, соотношение определяется точно так же, как и его для User, за исключением ссылки App\User. Так как мы повторно используем метод PHPbelongsToMany(), все обычные таблицы и параметры настройки ключей доступны при определении обратного отношения многих-ко-многим.

Получение промежуточных столбцов таблицы

Как вы уже запомнили, работа с отношением «многие-ко-многим» требует наличия промежуточной таблицы. Eloquent предоставляет некоторые очень полезные способы взаимодействия с такой таблицей. Например, давайте предположим, что наш объект User имеет много связанных с ним объектов Role. После получения доступа к этому отношению мы можем получить доступ к промежуточной таблице с помощью pivot атрибута модели:

PHP
$user App\User::find(1);

foreach (
$user->roles as $role) {
  echo 
$role->pivot->created_at;
}

Обратите внимание на то, что каждой полученной модели Role автоматически присваивается атрибут pivot. Этот атрибут содержит модель, представляющую промежуточную таблицу, и может быть использован, как и любая другая модель Eloquent.

По умолчанию, только ключи модели будут представлять pivot объект. Если ваша «pivot» таблица содержит дополнительные атрибуты, вам необходимо указать их при определении отношения:

PHP
return $this->belongsToMany('App\Role')->withPivot('column1''column2');

Если вы хотите, чтобы ваша «pivot» таблица автоматически поддерживала временные метки created_at и updated_at, используйте метод PHPwithTimestamps() при определении отношений:

PHP
return $this->belongsToMany('App\Role')->withTimestamps();
+ 5.3 5.2

добавлено в 5.3 () 5.2 ()

Фильтрация отношений через столбцы промежуточной таблицы

Вы также можете отфильтровать результаты, возвращённые методом PHPbelongsToMany(), с помощью методов PHPwherePivot() и PHPwherePivotIn() при определении отношения

PHP
return $this->belongsToMany('App\Role')->wherePivot('approved'1);

return 
$this->belongsToMany('App\Role')->wherePivotIn('priority', [12]);

Ко многим через

Связь «ко многим через» обеспечивает удобный короткий путь для доступа к удалённым отношениям через промежуточные. Например, модель Country может иметь много Post через модель User. В данном примере вы можете просто собрать все статьи для заданной country. Таблицы для этих отношений будут выглядеть так:

confcountries
    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 с помощью PHP$country->posts. Для выполнения этого запроса Eloquent ищет country_id в промежуточной таблице users. После нахождения совпадающих ID пользователей они используются в запросе к таблице posts.

Теперь, когда мы рассмотрели структуру таблицы для отношений, давайте определим отношения для модели Country:

PHP
<?php

namespace App;

use 
Illuminate\Database\Eloquent\Model;

class 
Country extends Model
{
  
/**
   * Получить все статьи по заданной области.
   */
  
public function posts()
  {
    return 
$this->hasManyThrough('App\Post''App\User');
  }
}

Первый параметр, переданный в метод PHPhasManyThrough() является именем конечной модели, которую мы получаем, а второй параметр — это имя промежуточной модели.

Обычные соглашения для внешнего ключа Eloquent будут использоваться при выполнении запросов отношения. Если вы хотите настроить ключи отношения, вы можете передать их третьим и четвертым параметрами методу PHPhasManyThrough(). Третий параметр — имя внешнего ключа для промежуточной модели, четвертый параметр — имя внешнего ключа для конечной модели, а пятый аргумент (для версии 5.2 и выше) — локальный ключ:

PHP
class Country extends Model
{
  public function 
posts()
  {
    return 
$this->hasManyThrough(
              
'App\Post''App\User',
              
'country_id''user_id''id'
    
);

    
//для версии 5.1:
    //return $this->hasManyThrough('App\Post', 'App\User', 'country_id', 'user_id');
  
}
}

Полиморфные отношения

Структура таблицы

Полиморфные отношения позволяют модели быть связанной с более чем одной моделью. Например, предположим, пользователи вашего приложения могут «комментировать» и статьи, и видео. Используя полиморфные отношения, вы можете использовать единственную таблицу comments для обоих этих сценариев. Во-первых, давайте посмотрим на структуру таблицы, необходимую для таких отношений:

confposts
    id - integer
    title - string
    body - text

videos
    id - integer
    title - string
    url - string

comments
    id - integer
    body - text
    commentable_id - integer
    commentable_type - string

Главные поля, на которые нужно обратить внимание: commentable_id и commentable_type в таблице comments. Первое содержит ID статьи или видео, а второе — имя класса-модели владельца. Это позволяет ORM определить, какой класс модели должен быть возвращён при использовании отношения commentable.

Структура модели

Теперь давайте рассмотрим, какие определения для модели нам нужны, чтобы построить её отношения:

PHP
<?php

namespace App;

use 
Illuminate\Database\Eloquent\Model;

class 
Comment extends Model
{
  
/**
   * Получить все модели, обладающие commentable.
   */
  
public function commentable()
  {
    return 
$this->morphTo();
  }
}

class 
Post extends Model
{
  
/**
   * Получить все комментарии статьи.
   */
  
public function comments()
  {
    return 
$this->morphMany('App\Comment''commentable');
  }
}

class 
Video extends Model
{
  
/**
   * Получить все комментарии видео.
   */
  
public function comments()
  {
    return 
$this->morphMany('App\Comment''commentable');
  }
}

Чтение полиморфного отношения

После определения моделей и таблиц вы можете получить доступ к отношениям через модели. Например, чтобы получить все комментарии статьи, просто используйте динамической свойство comments:

PHP
$post App\Post::find(1);

foreach (
$post->comments as $comment) {
  
//
}

Вы можете также получить владельца полиморфного отношения от полиморфной модели, получив доступ к имени метода, который вызывает PHPmorphTo(). В нашем случае это метод PHPcommentable() для модели Comment. Так мы получим доступ к этому методу как к динамическому свойству:

PHP
$comment App\Comment::find(1);

$commentable $comment->commentable;

Отношение PHPcommentable() модели Comment вернёт либо объект Post, либо объект Video в зависимости от типа модели, которой принадлежит комментарий.

Пользовательские полиморфные типы

По умолчанию Laravel будет использовать полностью определённое имя класса для хранения типа связанной модели. Например, учитывая пример выше, где Comment может принадлежать Post или Video, значение по умолчанию для commentable_type было бы или App\Post или App\Video, соответственно. Однако вы можете захотеть отделить свою базу данных от внутренней структуры вашего приложения. В этом случае вы можете определить отношение PHPmorph map(), чтобы дать команду Eloquent использовать заданное имя для каждой модели вместо имени класса:

PHP
use Illuminate\Database\Eloquent\Relations\Relation;

Relation::morphMap([
  
'posts' => 'App\Post',
  
'videos' => 'App\Video',
]);
+ 5.2 5.1

добавлено в 5.2 () 5.1 ()

Или вы можете определить свою строку для ассоциации с моделью:

PHP
use Illuminate\Database\Eloquent\Relations\Relation;

Relation::morphMap([
  
'posts' => App\Post::class,
  
'comments' => App\Comment::class,
]);

Вы можете зарегистрировать PHPmorphMap() в функции PHPboot() или в своём AppServiceProvider или создать отдельный сервис-провайдер.

Полиморфные связи многие ко многим

Структура таблиц

В дополнение к традиционным полиморфным связям вы можете также задать полиморфные связи многие ко многим. Например, модели блогов Post и Video могут разделять полиморфную связь с моделью Tag. Используя полиморфное отношение «многие-ко-многим», вы имеете единственный список уникальных тегов, которые совместно используются через сообщения в блоге и видео. Во-первых, давайте рассмотрим структуру таблиц:

confposts
    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 будут иметь связь PHPmorphToMany() в базовом классе Eloquent через метод PHPtags():

PHP
<?php

namespace App;

use 
Illuminate\Database\Eloquent\Model;

class 
Post extends Model
{
  
/**
   * Получить все теги статьи.
   */
  
public function tags()
  {
    return 
$this->morphToMany('App\Tag''taggable');
  }
}

Определение обратного отношения

Теперь для модели Tag вы должны определить метод для каждой из моделей отношения. Для нашего примера мы определим метод PHPposts() и метод PHPvideos():

PHP
<?php

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');
  }
}

Получение отношений

Как только ваша таблица и модели определены, вы можете получить доступ к отношениям через свои модели. Например, чтобы получить доступ ко всем тегам для сообщения, вы можете просто использовать динамическое свойство tag:

PHP
$post App\Post::find(1);

foreach (
$post->tags as $tag) {
  
//
}

Вы можете также получить владельца полиморфного отношения от полиморфной модели, получив доступ к имени метода, который выполняет вызов PHPmorphedByMany(). В нашем случае, это метод PHPposts() или PHPvideos() для модели Tag. Так вы получите доступ к этим методам как к динамическим свойствам:

PHP
$tag App\Tag::find(1);

foreach (
$tag->videos as $video) {
  
//
}

Запросы к отношениям

Поскольку все отношения определены функциями, мы можем вызывать эти функции, чтобы получить экземпляр отношения, фактически не выполняя запросы отношения. Кроме того, все типы Eloquent отношений также являются конструкторами запросов, позволяя вам сцеплять условия запроса отношения перед выполнением SQL-запроса к вашей базе данных.

Например, представьте систему блогов, в которой модель User имеет множество связей с моделью Post:

PHP
<?php

namespace App;

use 
Illuminate\Database\Eloquent\Model;

class 
User extends Model
{
  
/**
   * Получить все статьи пользователя.
   */
  
public function posts()
  {
    return 
$this->hasMany('App\Post');
  }
}

Вы можете запросить отношения PHPposts() и добавить дополнительные условия к запросу отношения так:

PHP
$user App\User::find(1);

$user->posts()->where('active'1)->get();

Вы можете использовать любой из методов конструктора запросов на отношении, поэтому не забудьте изучить документацию по конструктору запросов, где описаны все доступные вам методы.

Методы отношений или динамические свойства

Если вам не нужно добавлять дополнительные ограничения к Eloquent запросу отношения, вы можете просто получить доступ к отношению, как будто это свойство. Например, продолжая использовать наши модели User и Post в качестве примера, мы можем получить доступ ко всем сообщениям пользователя так:

PHP
$user App\User::find(1);

foreach (
$user->posts as $post) {
  
//
}

Динамические свойства поддерживают «ленивую загрузку». Это означает, что они загрузят свои данные для отношения только в момент обращения к ним. Из-за этого разработчики часто используют нетерпеливую загрузку, чтобы предварительно загрузить отношения, для которых они знают, что доступ будет получен после загрузки модели. Нетерпеливая загрузка обеспечивает значительное сокращение SQL-запросов, которые должны быть выполнены, чтобы загрузить отношения модели.

Проверка существования связей при выборке

При чтении отношений модели вам может быть нужно ограничить результаты в зависимости от существования отношения. Например, вы хотите получить все статьи в блоге, имеющие хотя бы один комментарий. Для этого можно использовать метод PHPhas():

PHP
// Получить все статьи в блоге, имеющие хотя бы один комментарий...
$posts App\Post::has('comments')->get();

Вы также можете указать оператор и число:

PHP
// Получить все статьи в блоге, имеющие три и более комментариев...
$posts Post::has('comments''>='3)->get();

Можно конструировать вложенные операторы PHPhas() с помощью точечной нотации. Например, вы можете получить все статьи, которые имеют хотя бы один комментарий и голос:

PHP
// Получить все статьи, которые имеют хотя бы один комментарий и голос...
$posts Post::has('comments.votes')->get();

Если вам нужно ещё больше возможностей, вы можете использовать методы PHPwhereHas() и PHPorWhereHas(), чтобы поместить условия «where» в ваши запросы PHPhas(). Эти методы позволяют вам добавить свои ограничения в отношения, такие как проверку содержимого комментария:

PHP
// Получить все статьи с хотя бы одним комментарием, имеющим слово "foo"%
$posts Post::whereHas('comments', function ($query) {
  
$query->where('content''like''foo%');
})->
get();
+ 5.3

добавлено в 5.3 ()

Выборка по отсутствию отношения

При получении записей модели бывает необходимо ограничить результаты выборки на основе отсутствия отношения. Например, если вы хотите получить все статьи, у которых нет комментариев. Для этого передайте имя отношения в метод PHPdoesntHave():

PHP
$posts App\Post::doesntHave('comments')->get();

Для ещё большего уточнения используйте метод PHPwhereDoesntHave(), чтобы добавить условия where в ваши запросы PHPdoesntHave(). Этот метод позволяет добавить дополнительные ограничения к отношению, например, проверку содержимого комментария:

PHP
$posts Post::whereDoesntHave('comments', function ($query) {
  
$query->where('content''like''foo%');
})->
get();
+ 5.3 5.2

добавлено в 5.3 () 5.2 ()

Подсчёт моделей в отношении

Если вы хотите посчитать число результатов отношения, не загружая их, используйте метод PHPwithCount(), который поместит столбец PHP{relation}_count в вашу результирующую модель. Например:

PHP
$posts App\Post::withCount('comments')->get();

foreach (
$posts as $post) {
  echo 
$post->comments_count;
}

Вы можете добавить «число» для нескольких отношений так же, как и добавить ограничения к запросам:

PHP
$posts Post::withCount(['votes''comments' => function ($query) {
  
$query->where('content''like''foo%');
}])->
get();

echo 
$posts[0]->votes_count;
echo 
$posts[0]->comments_count;

Нетерпеливая загрузка

При доступе к Eloquent отношениям как к свойствам отношения «лениво загружаются». Это означает, что данные отношения фактически не загружены, пока вы не обратитесь к свойству. Однако Eloquent может «нетерпеливо загружать» отношения в то время, когда вы запрашиваете родительскую модель. Нетерпеливая загрузка облегчает проблему N+1 запроса. Чтобы проиллюстрировать проблему N+1 запроса, рассмотрите модель Book, которая связана с Author:

PHP
<?php

namespace App;

use 
Illuminate\Database\Eloquent\Model;

class 
Book extends Model
{
  
/**
   * Получить автора книги.
   */
  
public function author()
  {
    return 
$this->belongsTo('App\Author');
  }
}

Теперь давайте получим все книги и их авторов:

PHP
$books App\Book::all();

foreach (
$books as $book) {
  echo 
$book->author->name;
}

Этот цикл выполнит 1 запрос, чтобы получить все книги по таблице, затем выполнится другой запрос для каждой книги, чтобы получить автора. Так, если бы у нас было 25 книг, этот цикл выполнил бы 26 запросов: 1 для исходной книги и 25 дополнительных запросов, чтобы получить автора каждой книги.

К счастью мы можем использовать нетерпеливую загрузку, чтобы уменьшить эту работу всего до 2 запросов. При запросах вы можете определить, какие отношения должны быть нетерпеливо загружены с использованием метода PHPwith():

PHP
$books App\Book::with('author')->get();

foreach (
$books as $book) {
  echo 
$book->author->name;
}

Для данной операции будут выполнены только два запроса:

sqlselect * from books

select * from authors where id in (1, 2, 3, 4, 5, ...)

Нетерпеливо загружающиеся множественные отношения

Иногда вам, возможно, понадобится нетерпеливо загружать несколько различных отношений в единственной итерации. Для этого просто передайте дополнительные параметры в метод PHPwith():

PHP
$books App\Book::with('author''publisher')->get();

Вложенная нетерпеливая загрузка

Для вложенных отношений нетерпеливой загрузки вы можете использовать точечную нотацию. Например, давайте нетерпеливо загружать всех авторов книг и все личные контакты автора в одном Eloquent операторе:

PHP
$books App\Book::with('author.contacts')->get();

Ограничение нетерпеливых загрузок

Иногда вам может понадобиться нетерпеливо загружать отношения. Но также иногда может понадобиться и определить дополнительные ограничения запроса для нетерпеливого запроса загрузки. Вот пример:

PHP
$users App\User::with(['posts' => function ($query) {
  
$query->where('title''like''%first%');
}])->
get();

В данном примере Eloquent будет нетерпеливо загружать только те статьи, в которых столбец title содержит слово first. Конечно, вы можете вызвать другие методы конструктора запросов для дополнительной настройки нетерпеливой загрузки операции:

PHP
$users App\User::with(['posts' => function ($query) {
  
$query->orderBy('created_at''desc');
}])->
get();

Ленивая нетерпеливая загрузка

Иногда вам, возможно, понадобится нетерпеливо загружать отношение после того, как родительская модель уже была получена. Например, это может быть полезно, если вы должны динамично решить, загружать ли связанные модели:

PHP
$books App\Book::all();

if (
$someCondition) {
  
$books->load('author''publisher');
}

Если вам нужно установить дополнительные ограничения запроса на нетерпеливо загружаемый запрос, вы можете передать массив, ключами которого будут отношения, которые необходимо загрузить. Значения массива должны быть экземплярами PHPClosure, которые получают экземпляр запроса:

PHP
$books->load(['author' => function ($query) {
  
$query->orderBy('published_date''asc');
}]);

Вставка и изменение связанных моделей

Метод Save

Eloquent предоставляет удобные методы для добавления новых моделей к отношениям. Например, если вам понадобится вставить новый Comment для модели Post. Вместо того, чтобы вручную установить атрибут post_id для Comment, вы можете вставить Comment непосредственно из метода PHPsave() отношения:

PHP
$comment = new App\Comment(['message' => 'A new comment.']);

$post App\Post::find(1);

$post->comments()->save($comment);

Заметьте, что мы не обращались к отношению PHPcomments() как к динамическому свойству. Вместо этого мы вызвали метод PHPcomments(), чтобы получить экземпляр отношения. Метод PHPsave() автоматически добавит надлежащее значение post_id в новую модель Comment.

Если вам нужно сохранить несколько связанных моделей, вы можете использовать метод PHPsaveMany():

PHP
$post App\Post::find(1);

$post->comments()->saveMany([
  new 
App\Comment(['message' => 'A new comment.']),
  new 
App\Comment(['message' => 'Another comment.']),
]);
+ 5.2 5.1

добавлено в 5.2 () 5.1 ()

Сохранение и отношения многие-ко-многим

При работе с отношением многие-ко-многим метод PHPsave() принимает вторым параметром массив атрибутов дополнительных промежуточных таблиц:

PHP
App\User::find(1)->roles()->save($role, ['expires' => $expires]);

Метод Create

В дополнение к методам PHPsave() и PHPsaveMany() вы можете также использовать метод PHPcreate(), который принимает массив атрибутов, создает модель и вставляет её в базу данных. Различие между PHPsave() и PHPcreate() в том, что PHPsave() принимает экземпляр Eloquent модели целиком, в то время как PHPcreate() принимает простой PHP PHParray:

PHP
$post App\Post::find(1);

$comment $post->comments()->create([
  
'message' => 'A new comment.',
]);

Перед использованием метода PHPcreate() пересмотрите документацию по массовому назначению атрибутов.

Отношение «принадлежит к»

При обновлении отношения PHPbelongsTo() вы можете использовать метод PHPassociate(). Этот метод установит внешний ключ на дочерней модели:

PHP
$account App\Account::find(10);

$user->account()->associate($account);

$user->save();

При удалении отношения PHPbelongsTo() вы можете использовать метод PHPdissociate(). Этот метод сбросит внешний ключ отношения в PHPnull:

PHP
$user->account()->dissociate();

$user->save();

Отношение многие-ко-многим

Присоединение / Отсоединение

Также Eloquent предоставляет несколько дополнительных вспомогательных методов, чтобы сделать работу со связанными моделями более удобной. Например, давайте предположим, что у пользователя может быть много ролей, и у роли может быть много пользователей. Чтобы присоединить роль к пользователю вставкой записи в промежуточную таблицу, которая присоединяется к моделям, используйте метод PHPattach():

PHP
$user App\User::find(1);

$user->roles()->attach($roleId);

При присоединении отношения к модели вы можете также передать массив дополнительных данных, которые будут вставлены в промежуточную таблицу:

PHP
$user->roles()->attach($roleId, ['expires' => $expires]);

Конечно, иногда может быть необходимо отсоединить роль от пользователя. Чтобы удалить запись отношения многие-ко-многим, используйте метод PHPdetach(). Метод PHPdetach() удалит соответствующую запись из промежуточной таблицы. Однако, обе модели останутся в базе данных:

PHP
// Отсоединить одну роль от пользователя...
$user->roles()->detach($roleId);

// Отсоединить все роли от пользователя...
$user->roles()->detach();

Для удобства PHPattach() и PHPdetach() также принимают массивы ID в параметрах:

PHP
$user App\User::find(1);

$user->roles()->detach([123]);

$user->roles()->attach([=> ['expires' => $expires], 23]);
+ 5.2

добавлено в 5.2 ()

Изменение записи в сводной таблице

Если вам необходимо изменить существующую запись в вашей сводной таблице, используйте метод PHPupdateExistingPivot():

PHP
  $user App\User::find(1);

$user->roles()->updateExistingPivot($roleId$attributes);

Синхронизация ассоциаций

Вы можете также использовать метод PHPsync(), чтобы создать ассоциации многие-ко-многим. Метод PHPsync() принимает массив ID, чтобы поместить его в промежуточную таблицу. Все ID, которые не находятся в данном массиве, будут удалены из промежуточной таблицы. После того как эта работа завершена, только ID из данного массива будут существовать в промежуточной таблице:

PHP
$user->roles()->sync([123]);

Также вы можете передать дополнительные значения промежуточной таблицы с массивом ID:

PHP
$user->roles()->sync([=> ['expires' => true], 23]);
+ 5.3 5.2

добавлено в 5.3 () 5.2 ()

Если вы не хотите отделять существующие ID, используйте метод PHPsyncWithoutDetaching():

PHP
 $user->roles()->syncWithoutDetaching([123]);
+ 5.3

добавлено в 5.3 ()

Переключение ассоциаций

Отношение многие-ко-многим также предоставляет метод PHPtoggle(), который «переключает» состояние присоединений с заданными ID. Если данный ID сейчас присоединён, то он будет отсоединён. И наоборот, если сейчас он отсоединён, то будет присоединён:

PHP
$user->roles()->toggle([123]);

Сохранение дополнительных данных в сводной таблице

При работе с отношением многие-ко-многим метод PHPsave() принимает вторым аргументом массив дополнительных атрибутов промежуточной таблицы:

PHP
App\User::find(1)->roles()->save($role, ['expires' => $expires]);

Изменение записи в сводной таблице

Для изменения существующей строки в сводной таблице используйте метод PHPupdateExistingPivot(). Этот метод принимает внешний ключ сводной записи и массив атрибутов для изменения:

PHP
$user App\User::find(1);

$user->roles()->updateExistingPivot($roleId$attributes);

Привязка родительских меток времени

Когда модель имеет связь PHPbelongsTo() или PHPbelongsToMany() с другими моделями, например, Comment, которая принадлежит Post, иногда полезно обновить метку времени родителя, когда дочерняя модель обновлена. Например, когда модель Comment обновлена, вы можете автоматически «привязать» метки времени updated_at на владеющий ей Post. Eloquent упрощает эту работу. Просто добавьте свойство touches, содержащее имена отношений к дочерней модели:

PHP
<?php

namespace App;

use 
Illuminate\Database\Eloquent\Model;

class 
Comment extends Model
{
  
/**
   * Все привязанные отношения.
   *
   * @var array
   */
  
protected $touches = ['post'];

  
/**
   * Получить статью, к которой привязан комментарий.
   */
  
public function post()
  {
    return 
$this->belongsTo('App\Post');
  }
}

Теперь, когда вы обновляете Comment, у владеющего Post тоже обновится столбец updated_at, позволяя проще узнать, когда необходимо аннулировать кэш модели Post:

PHP
comment App\Comment::find(1);

$comment->text 'Edit to this comment!';

$comment->save();

Комментарии (8)

blackdark20

Самая сложная статья при обучении 30% материала лишь понял

loneybibi

Полностью согласен, тоже понял только меньшую половину статьи. Похоже просто на грубый перевод..

RomanBush

Нифига, хороший перевод. Тут некоторые вещи адаптированны для новичков. На английском некоторые вещи менее понятны, чем здесь.
Если чо — я обе версии прочитал. Причём обе по два раза. Понял примерно 70%. Некоторые вещи понял только благодаря тому, что прочитал обе версии статьи. Они слегка отличаются.

kublahanov

Нетерпеливая загрузка = Жадная загрузка. Перевод "Жадная" - как-то более распространён.

Piligrim

Зато «нетерпеливая» гораздо более понятно для тех кто до сих пор был не в теме.

Meffistroler

Чтение полиморфного отношения
Если вы создаете отношения по своим полям, то нужно делать так:

PHP
<?php

namespace App;

use 
Illuminate\Database\Eloquent\Model;

class 
Comment extends Model
{
  
/**
   * Получить все модели, обладающие commentable.
   */
  
public function commentable()
  {
    return 
$this->morphTo('''tag''id_pn');
  }
}

class 
Post extends Model
{
  
/**
   * Получить все комментарии статьи.
   */
  
public function comments()
  {
    return 
$this->morphMany('App\Comment''''tag''id_pn');
  }
}

class 
Video extends Model
{
  
/**
   * Получить все комментарии видео.
   */
  
public function comments()
  {
    return 
$this->morphMany('App\Comment''''tag''id_pn');
  }
}

Т.е в полиморфной модели в метод morphTo нужно передать теже $name, $type, $id, что и у владельцев полиморфного отношения.

P.S: Сам с толкнулся с такой проблемой, но решения или пояснения не нашел, поэтому решил оставить тут комментрай, вдруг кому пригодиться...

CAE_Ducem

Не пойму как организовать
В разных городах есть одинаковые товары
но у них разные цены

Alexandr5

Для меня. Ибо достало путаться.

PHP
class Post extends Model {
  public function 
comments(){
    return 
$this->hasMany('App\Comment''id_в_таблице_комментов''id_в_таблице_постов');
  }
}


class 
Comment extends Model {
  public function 
post(){
    return 
$this->belongsTo('App\Post''id_в_таблице_комментов''id_в_таблице_постов');
  }
}

Написать комментарий

Разметка: ? ?

Авторизуйся, чтобы прокомментировать.