Вступление
Таблицы базы данных часто связаны друг с другом. Например, сообщение в блоге может содержать много комментариев, или заказ может быть связан с пользователем, который его разместил. Eloquent упрощает управление и работу с этими отношениями и поддерживает несколько различных типов отношений:
- Один к одному
- Один ко многим
- Много ко многим
- Имеет один сквозной
- Имеет много через
- Один в Один (Полиморфный)
- Один ко многим (полиморфный)
- Много ко многим (полиморфный)
Определение отношений
Красноречивые отношения определяются как методы в ваших классах модели Eloquent. Поскольку, как и сами модели Eloquent, отношения также служат мощными построителями запросов , определяя отношения как методы, обеспечивая мощные возможности создания цепочек и запросов. Например, мы можем связать дополнительные ограничения на это posts
отношение:
$user->posts()->where('active', 1)->get();
Но прежде чем углубляться в использование отношений, давайте узнаем, как определить каждый тип.
Один к одному
Отношения один-к-одному - это очень основное отношение. Например, User
модель может быть связана с ней Phone
. Чтобы определить это отношение, мы помещаем phone
метод в User
модель. phone
Метод должен вызвать hasOne
метод и возвращает результат:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* Get the phone record associated with the user.
*/
public function phone()
{
return $this->hasOne('App\Phone');
}
}
Первым аргументом, переданным hasOne
методу, является имя связанной модели. Как только связь определена, мы можем получить связанную запись, используя динамические свойства Eloquent. Динамические свойства позволяют получить доступ к методам отношений, как если бы они были свойствами, определенными в модели:
$phone = User::find(1)->phone;
Eloquent определяет внешний ключ отношения на основе названия модели. В этом случае Phone
предполагается , что модель автоматически имеет user_id
внешний ключ. Если вы хотите переопределить это соглашение, вы можете передать второй аргумент hasOne
методу:
return $this->hasOne('App\Phone', 'foreign_key');
Кроме того, Eloquent предполагает, что внешний ключ должен иметь значение, соответствующее id
(или пользовательскому $primaryKey
) столбцу родительского элемента. Другими словами, Eloquent будет искать значение id
столбца пользователя в user_id
столбце Phone
записи. Если вы хотите, чтобы отношения использовали значение, отличное от id
, вы можете передать третий аргумент hasOne
методу, указывающему ваш пользовательский ключ:
return $this->hasOne('App\Phone', 'foreign_key', 'local_key');
Определение обратной связи
Таким образом, мы можем получить доступ к Phone
модели из нашего User
. Теперь давайте определим отношение к Phone
модели, которая позволит нам получить доступ к User
владельцу телефона. Мы можем определить обратную hasOne
связь, используя belongsTo
метод:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Phone extends Model
{
/**
* Get the user that owns the phone.
*/
public function user()
{
return $this->belongsTo('App\User');
}
}
В приведенном выше примере, Красноречивый будет пытаться соответствовать user_id
от Phone
модели к модели id
на User
модели. Eloquent определяет имя внешнего ключа по умолчанию, проверяя имя метода отношения и добавляя суффикс имени метода _id
. Однако, если внешний ключ в Phone
модели отсутствует user_id
, вы можете передать имя пользовательского ключа в качестве второго аргумента belongsTo
метода:
/**
* Get the user that owns the phone.
*/
public function user()
{
return $this->belongsTo('App\User', 'foreign_key');
}
Если ваша родительская модель не использует в id
качестве своего первичного ключа или вы хотите присоединить дочернюю модель к другому столбцу, вы можете передать третий аргумент belongsTo
методу, определяющему пользовательский ключ вашей родительской таблицы:
/**
* Get the user that owns the phone.
*/
public function user()
{
return $this->belongsTo('App\User', 'foreign_key', 'other_key');
}
Один ко многим
Отношение один-ко-многим используется для определения отношений, когда одной модели принадлежит любое количество других моделей. Например, сообщение в блоге может иметь бесконечное количество комментариев. Как и все другие отношения Eloquent, отношения «один ко многим» определяются путем размещения функции в вашей модели Eloquent:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
/**
* Get the comments for the blog post.
*/
public function comments()
{
return $this->hasMany('App\Comment');
}
}
Помните, что Eloquent автоматически определит правильный столбец внешнего ключа в Comment
модели. По соглашению, Eloquent возьмет имя «случая змеи» модели-владельца и добавит к нему суффикс _id
. Таким образом, для этого примера Eloquent будет предполагать, что внешний ключ Comment
модели - это post_id
.
Как только отношения определены, мы можем получить доступ к коллекции комментариев, открыв comments
свойство. Помните, поскольку Eloquent предоставляет «динамические свойства», мы можем получить доступ к методам отношений, как если бы они были определены как свойства в модели:
$comments = App\Post::find(1)->comments;
foreach ($comments as $comment) {
//
}
Поскольку все взаимосвязи также служат построителями запросов, вы можете добавить дополнительные ограничения, к которым извлекаются комментарии, вызывая comments
метод и продолжая связывать условия с запросом:
$comment = App\Post::find(1)->comments()->where('title', 'foo')->first();
Как и hasOne
метод, вы также можете переопределить внешний и локальный ключи, передав дополнительные hasMany
методы в метод:
return $this->hasMany('App\Comment', 'foreign_key');
return $this->hasMany('App\Comment', 'foreign_key', 'local_key');
Один ко многим (обратный)
Теперь, когда мы можем получить доступ ко всем комментариям поста, давайте определим отношение, чтобы позволить комментарию получить доступ к его родительскому посту. Чтобы определить обратную hasMany
зависимость, определите функцию отношения в дочерней модели, которая вызывает belongsTo
метод:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Comment extends Model
{
/**
* Get the post that owns the comment.
*/
public function post()
{
return $this->belongsTo('App\Post');
}
}
После того как отношение было определено, мы можем получить Post
модель для a Comment
, обратившись к post
«динамическому свойству»:
$comment = App\Comment::find(1);
echo $comment->post->title;
В приведенном выше примере, Красноречивый будет пытаться соответствовать post_id
от Comment
модели к модели id
на Post
модели. Eloquent определяет имя внешнего ключа по умолчанию, проверяя имя метода взаимосвязи и добавляя суффикс имени метода с _
именем столбца первичного ключа. Однако, если внешний ключ в Comment
модели отсутствует post_id
, вы можете передать имя пользовательского ключа в качестве второго аргумента belongsTo
метода:
/**
* Get the post that owns the comment.
*/
public function post()
{
return $this->belongsTo('App\Post', 'foreign_key');
}
Если ваша родительская модель не использует в id
качестве своего первичного ключа или вы хотите присоединить дочернюю модель к другому столбцу, вы можете передать третий аргумент belongsTo
методу, определяющему пользовательский ключ вашей родительской таблицы:
/**
* Get the post that owns the comment.
*/
public function post()
{
return $this->belongsTo('App\Post', 'foreign_key', 'other_key');
}
Много ко многим
Многие-ко-многим отношения немного сложнее , чем hasOne
и hasMany
отношения. Примером такого отношения является пользователь со многими ролями, где роли также разделяются другими пользователями. Например, многие пользователи могут иметь роль «Администратор». Для определения этого отношения, необходимы три таблицы базы данных: users
, roles
, и role_user
. role_user
Таблица выводится из алфавитного порядка соответствующих названий модели, и содержит user_id
и role_id
столбцы.
Отношения «многие ко многим» определяются путем написания метода, который возвращает результат belongsToMany
метода. Например, давайте определим roles
метод для нашей User
модели:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* The roles that belong to the user.
*/
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();
Как упоминалось ранее, для определения имени таблицы присоединяемой таблицы отношений Eloquent объединит два связанных имени модели в алфавитном порядке. Однако вы можете переопределить это соглашение. Вы можете сделать это, передав второй аргумент belongsToMany
методу:
return $this->belongsToMany('App\Role', 'role_user');
Помимо настройки имени присоединяемой таблицы, вы также можете настроить имена столбцов ключей таблицы, передав дополнительные belongsToMany
методы в метод. Третий аргумент - это имя внешнего ключа модели, для которой вы определяете отношение, а четвертый аргумент - это имя внешнего ключа модели, к которой вы присоединяетесь:
return $this->belongsToMany('App\Role', 'role_user', 'user_id', 'role_id');
Определение обратной связи
Чтобы определить обратное отношение «многие ко многим», нужно сделать еще один вызов для belongsToMany
связанной модели. Чтобы продолжить наш пример ролей пользователей, давайте определим users
метод на Role
модели:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Role extends Model
{
/**
* The users that belong to the role.
*/
public function users()
{
return $this->belongsToMany('App\User');
}
}
Как видите, отношение определяется точно так же, как и его User
аналог, за исключением ссылки на модель. Поскольку мы повторно используем метод, все обычные параметры настройки таблицы и ключа доступны при определении обратного отношения «многие ко многим».App\User
belongsToMany
Получение промежуточных столбцов таблицы
Как вы уже узнали, работа с отношениями «многие ко многим» требует наличия промежуточной таблицы. Eloquent предоставляет несколько очень полезных способов взаимодействия с этой таблицей. Например, давайте предположим, что у нашего User
объекта есть много Role
объектов, с которыми он связан. Получив доступ к этой взаимосвязи, мы можем получить доступ к промежуточной таблице, используя pivot
атрибут на моделях:
$user = App\User::find(1);
foreach ($user->roles as $role) {
echo $role->pivot->created_at;
}
Обратите внимание, что каждой Role
модели, которую мы получаем, автоматически присваивается pivot
атрибут. Этот атрибут содержит модель, представляющую промежуточную таблицу, и может использоваться как любая другая модель Eloquent.
По умолчанию на pivot
объекте будут присутствовать только ключи модели . Если ваша сводная таблица содержит дополнительные атрибуты, вы должны указать их при определении отношения:
return $this->belongsToMany('App\Role')->withPivot('column1', 'column2');
Если вы хотите , чтобы ваша сводная таблица, что поддерживается автоматически created_at
и updated_at
временные метки, используйте withTimestamps
метод по определению отношений:
return $this->belongsToMany('App\Role')->withTimestamps();
Настройка имени pivot
атрибута
Как отмечалось ранее, атрибуты из промежуточной таблицы могут быть доступны в моделях, использующих этот pivot
атрибут. Однако вы можете настроить имя этого атрибута, чтобы оно лучше отражало его назначение в вашем приложении.
Например, если в вашем приложении есть пользователи, которые могут подписаться на подкасты, вероятно, у вас есть отношение «многие ко многим» между пользователями и подкастами. Если это так, вы можете переименовать свой метод доступа к промежуточной таблице subscription
вместо pivot
. Это можно сделать с помощью as
метода при определении отношения:
return $this->belongsToMany('App\Podcast')
->as('subscription')
->withTimestamps();
Как только это будет сделано, вы можете получить доступ к данным промежуточной таблицы, используя настроенное имя:
$users = User::with('podcasts')->get();
foreach ($users->flatMap->podcasts as $podcast) {
echo $podcast->subscription->created_at;
}
Фильтрация отношений через промежуточные столбцы таблицы
Вы также можете отфильтровать результаты , возвращаемые с belongsToMany
помощью wherePivot
и wherePivotIn
методов при определении отношений:
return $this->belongsToMany('App\Role')->wherePivot('approved', 1);
return $this->belongsToMany('App\Role')->wherePivotIn('priority', [1, 2]);
Определение пользовательских промежуточных табличных моделей
Если вы хотите определить пользовательскую модель для представления промежуточной таблицы ваших отношений, вы можете вызвать using
метод при определении отношений. Настраиваемые модели множества-ко-многим должны расширять класс, тогда как настраиваемые полиморфные модели множества-ко-многим должны расширять класс. Например, мы можем определить, который использует пользовательскую модель разворота:Illuminate\Database\Eloquent\Relations\Pivot
Illuminate\Database\Eloquent\Relations\MorphPivot
Role
RoleUser
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Role extends Model
{
/**
* The users that belong to the role.
*/
public function users()
{
return $this->belongsToMany('App\User')->using('App\RoleUser');
}
}
При определении RoleUser
модели мы расширим Pivot
класс:
<?php
namespace App;
use Illuminate\Database\Eloquent\Relations\Pivot;
class RoleUser extends Pivot
{
//
}
Вы можете объединить using
и withPivot
для того, чтобы извлечь столбцы из промежуточной таблицы. Например, вы можете извлечь created_by
и updated_by
столбцы из RoleUser
сводной таблицы, передавая имена столбцов в withPivot
методе:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Role extends Model
{
/**
* The users that belong to the role.
*/
public function users()
{
return $this->belongsToMany('App\User')
->using('App\RoleUser')
->withPivot([
'created_by',
'updated_by'
]);
}
}
Примечание: модели Pivot могут не использовать эту
SoftDeletes
черту. Если вам нужно мягко удалить сводные записи, рассмотрите возможность преобразования вашей сводной модели в реальную модель Eloquent.
Пользовательские модели Pivot и увеличивающиеся идентификаторы
Если вы определили отношение «многие ко многим», в котором используется настраиваемая сводная модель, и эта сводная модель имеет автоинкрементный первичный ключ, вы должны убедиться, что класс настраиваемой сводной модели определяет incrementing
свойство, для которого установлено значение true
.
/**
* Indicates if the IDs are auto-incrementing.
*
* @var bool
*/
public $incrementing = true;
Имеет один сквозной
Связи «имеет один сквозной» моделирует модели через одно промежуточное отношение. Например, если у каждого поставщика есть один пользователь, и каждый пользователь связан с одной записью истории пользователя, то модель поставщика может получить доступ к истории пользователя через пользователя. Давайте посмотрим на таблицы базы данных, необходимые для определения этого отношения:
users
id - integer
supplier_id - integer
suppliers
id - integer
history
id - integer
user_id - integer
Хотя history
таблица не содержит supplier_id
столбца, hasOneThrough
отношение может предоставить доступ к истории пользователя к модели поставщика. Теперь, когда мы проверили структуру таблицы для отношения, давайте определим ее на Supplier
модели:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Supplier extends Model
{
/**
* Get the user's history.
*/
public function userHistory()
{
return $this->hasOneThrough('App\History', 'App\User');
}
}
Первый аргумент, передаваемый hasOneThrough
методу, - это имя последней модели, к которой мы хотим получить доступ, а второй аргумент - это имя промежуточной модели.
Типичные соглашения Eloquent по внешним ключам будут использоваться при выполнении запросов отношения. Если вы хотите настроить ключи отношения, вы можете передать их в качестве третьего и четвертого аргументов hasOneThrough
методу. Третий аргумент - это имя внешнего ключа промежуточной модели. Четвертый аргумент - это имя внешнего ключа в окончательной модели. Пятый аргумент - это локальный ключ, а шестой аргумент - это локальный ключ промежуточной модели:
class Supplier extends Model
{
/**
* Get the user's history.
*/
public function userHistory()
{
return $this->hasOneThrough(
'App\History',
'App\User',
'supplier_id', // Foreign key on users table...
'user_id', // Foreign key on history table...
'id', // Local key on suppliers table...
'id' // Local key on users table...
);
}
}
Имеет много через
Отношение «есть много через» обеспечивает удобный ярлык для доступа к удаленным отношениям через промежуточное отношение. Например, Country
модель может иметь много Post
моделей через промежуточную User
модель. В этом примере вы можете легко собрать все сообщения в блогах для данной страны. Давайте посмотрим на таблицы, необходимые для определения этого отношения:
countries
id - integer
name - string
users
id - integer
country_id - integer
name - string
posts
id - integer
user_id - integer
title - string
Хотя posts
и не содержит country_id
столбец, hasManyThrough
отношение обеспечивает доступ к сообщениям страны через . Чтобы выполнить этот запрос, Eloquent проверяет промежуточную таблицу. После нахождения соответствующих идентификаторов пользователей они используются для запроса таблицы.$country->posts
country_id
users
posts
Теперь, когда мы проверили структуру таблицы для отношения, давайте определим ее на Country
модели:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Country extends Model
{
/**
* Get all of the posts for the country.
*/
public function posts()
{
return $this->hasManyThrough('App\Post', 'App\User');
}
}
Первый аргумент, передаваемый hasManyThrough
методу, - это имя последней модели, к которой мы хотим получить доступ, а второй аргумент - это имя промежуточной модели.
Типичные соглашения Eloquent по внешним ключам будут использоваться при выполнении запросов отношения. Если вы хотите настроить ключи отношения, вы можете передать их в качестве третьего и четвертого аргументов hasManyThrough
методу. Третий аргумент - это имя внешнего ключа промежуточной модели. Четвертый аргумент - это имя внешнего ключа в окончательной модели. Пятый аргумент - это локальный ключ, а шестой аргумент - это локальный ключ промежуточной модели:
class Country extends Model
{
public function posts()
{
return $this->hasManyThrough(
'App\Post',
'App\User',
'country_id', // Foreign key on users table...
'user_id', // Foreign key on posts table...
'id', // Local key on countries table...
'id' // Local key on users table...
);
}
}
Полиморфные отношения
Полиморфная связь позволяет целевой модели принадлежать более чем одному типу модели с использованием одной ассоциации.
Один в Один (Полиморфный)
Структура таблицы
Полиморфное отношение один к одному аналогично простому отношению один к одному; однако целевая модель может принадлежать более чем одному типу модели в одной ассоциации. Например, блог Post
и a User
могут иметь полиморфное отношение к Image
модели. Использование полиморфных отношений «один к одному» позволяет получить единый список уникальных изображений, которые используются как для сообщений в блоге, так и для учетных записей пользователей. Сначала давайте рассмотрим структуру таблицы:
posts
id - integer
name - string
users
id - integer
name - string
images
id - integer
url - string
imageable_id - integer
imageable_type - string
Примите во внимание imageable_id
и imageable_type
столбцы на images
столе. imageable_id
Столбец будет содержать значение ID поста или пользователя, в то время как imageable_type
колонок будет содержать имя класса родительской модели. imageable_type
Колонка используется красноречив , чтобы определить , какой «тип» родительской модели для возврата при обращении к imageable
соотношению.
Структура модели
Далее, давайте рассмотрим определения модели, необходимые для построения этих отношений:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Image extends Model
{
/**
* Get all of the owning imageable models.
*/
public function imageable()
{
return $this->morphTo();
}
}
class Post extends Model
{
/**
* Get the post's image.
*/
public function image()
{
return $this->morphOne('App\Image', 'imageable');
}
}
class User extends Model
{
/**
* Get the user's image.
*/
public function image()
{
return $this->morphOne('App\Image', 'imageable');
}
}
Получение Отношения
После определения таблицы и моделей базы данных вы можете получить доступ к отношениям через свои модели. Например, чтобы получить изображение для поста, мы можем использовать image
динамическое свойство:
$post = App\Post::find(1);
$image = $post->image;
Вы также можете извлечь родителя из полиморфной модели, обратившись к имени метода, который выполняет вызов morphTo
. В нашем случае это imageable
метод на Image
модели. Итак, мы будем обращаться к этому методу как к динамическому свойству:
$image = App\Image::find(1);
$imageable = $image->imageable;
imageable
Отношение на Image
модели будет возвращать Post
или User
экземпляр, в зависимости от того, какого типа модели имеет изображение.
Один ко многим (полиморфный)
Структура таблицы
Полиморфное отношение один-ко-многим похоже на простое отношение один-ко-многим; однако целевая модель может принадлежать более чем одному типу модели в одной ассоциации. Например, представьте, что пользователи вашего приложения могут «комментировать» как посты, так и видео. Используя полиморфные отношения, вы можете использовать одну comments
таблицу для обоих этих сценариев. Сначала давайте рассмотрим структуру таблицы, необходимую для построения этого отношения:
posts
id - integer
title - string
body - text
videos
id - integer
title - string
url - string
comments
id - integer
body - text
commentable_id - integer
commentable_type - string
Структура модели
Далее, давайте рассмотрим определения модели, необходимые для построения этих отношений:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Comment extends Model
{
/**
* Get all of the owning commentable models.
*/
public function commentable()
{
return $this->morphTo();
}
}
class Post extends Model
{
/**
* Get all of the post's comments.
*/
public function comments()
{
return $this->morphMany('App\Comment', 'commentable');
}
}
class Video extends Model
{
/**
* Get all of the video's comments.
*/
public function comments()
{
return $this->morphMany('App\Comment', 'commentable');
}
}
Получение Отношения
После определения таблицы и моделей базы данных вы можете получить доступ к отношениям через свои модели. Например, чтобы получить доступ ко всем комментариям к сообщению, мы можем использовать comments
динамическое свойство:
$post = App\Post::find(1);
foreach ($post->comments as $comment) {
//
}
Вы также можете получить владельца полиморфного отношения из полиморфной модели, обратившись к имени метода, который выполняет вызов morphTo
. В нашем случае это commentable
метод на Comment
модели. Итак, мы будем обращаться к этому методу как к динамическому свойству:
$comment = App\Comment::find(1);
$commentable = $comment->commentable;
commentable
Отношение на Comment
модели будет возвращать Post
или Video
экземпляр, в зависимости от того, какого типа модели имеет свой комментарий.
Много ко многим (полиморфный)
Структура таблицы
Многим-многим полиморфные отношения немного сложнее, чем morphOne
и morphMany
отношения. Например, блог 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
будут иметь tags
метод, который вызывает morphToMany
метод базового класса Eloquent:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
/**
* Get all of the tags for the post.
*/
public function tags()
{
return $this->morphToMany('App\Tag', 'taggable');
}
}
Определение обратной связи
Затем в Tag
модели вы должны определить метод для каждой из связанных с ней моделей. Итак, для этого примера мы определим posts
метод и videos
метод:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Tag extends Model
{
/**
* Get all of the posts that are assigned this tag.
*/
public function posts()
{
return $this->morphedByMany('App\Post', 'taggable');
}
/**
* Get all of the videos that are assigned this tag.
*/
public function videos()
{
return $this->morphedByMany('App\Video', 'taggable');
}
}
Получение Отношения
После определения таблицы и моделей базы данных вы можете получить доступ к отношениям через свои модели. Например, чтобы получить доступ ко всем тегам сообщения, вы можете использовать tags
динамическое свойство:
$post = App\Post::find(1);
foreach ($post->tags as $tag) {
//
}
Вы также можете получить владельца полиморфного отношения из полиморфной модели, обратившись к имени метода, который выполняет вызов morphedByMany
. В нашем случае это методы posts
или videos
на Tag
модели. Итак, вы получите доступ к этим методам как к динамическим свойствам:
$tag = App\Tag::find(1);
foreach ($tag->videos as $video) {
//
}
Пользовательские Полиморфные Типы
По умолчанию Laravel будет использовать полное имя класса для хранения типа связанной модели. Например, учитывая приведенный выше пример «один ко многим», где a Comment
может принадлежать a Post
или a Video
, значением по умолчанию commentable_type
будет либо или , соответственно. Однако вы можете отделить базу данных от внутренней структуры вашего приложения. В этом случае вы можете определить «карту морфинга», чтобы дать Eloquent команду использовать произвольное имя для каждой модели вместо имени класса:App\Post
App\Video
use Illuminate\Database\Eloquent\Relations\Relation;
Relation::morphMap([
'posts' => 'App\Post',
'videos' => 'App\Video',
]);
Вы можете зарегистрироваться morphMap
в boot
функции вашего AppServiceProvider
или создать отдельного поставщика услуг, если хотите.
Опрос отношений
Поскольку все типы отношений Eloquent определяются с помощью методов, вы можете вызывать эти методы для получения экземпляра отношения без фактического выполнения запросов отношения. Кроме того, все типы отношений Eloquent также служат построителями запросов , позволяя вам продолжать связывать ограничения на запрос отношений, прежде чем наконец выполнить SQL для вашей базы данных.
Например, представьте систему блогов, в которой User
модель имеет много связанных Post
моделей:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* Get all of the posts for the user.
*/
public function posts()
{
return $this->hasMany('App\Post');
}
}
Вы можете запросить posts
отношения и добавить дополнительные ограничения к отношениям следующим образом:
$user = App\User::find(1);
$user->posts()->where('active', 1)->get();
Вы можете использовать любой из методов построителя запросов в отношениях, поэтому обязательно изучите документацию построителя запросов, чтобы узнать обо всех доступных вам методах.
Цепные предложения orWhere
после отношений
Как показано в примере выше, вы можете добавлять дополнительные ограничения к отношениям при запросе их. Однако будьте осторожны при объединении orWhere
предложений в отношения, так как orWhere
предложения будут логически сгруппированы на том же уровне, что и ограничение отношений:
$user->posts()
->where('active', 1)
->orWhere('votes', '>=', 100)
->get();
// select * from posts
// where user_id = ? and active = 1 or votes >= 100
В большинстве случаев вы, вероятно, намереваетесь использовать группы ограничений для логической группировки условных проверок в скобках:
$user->posts()
->where(function ($query) {
return $query->where('active', 1)
->orWhere('votes', '>=', 100);
})
->get();
// select * from posts
// where user_id = ? and (active = 1 or votes >= 100)
Методы отношений против. Динамические Свойства
Если вам не нужно добавлять дополнительные ограничения в запрос отношения Eloquent, вы можете получить доступ к отношениям, как если бы это было свойство. Например, продолжая использовать наши User
и Post
примеры моделей, мы можем получить доступ ко всем сообщениям пользователя, например так:
$user = App\User::find(1);
foreach ($user->posts as $post) {
//
}
Динамические свойства являются «отложенной загрузкой», то есть они будут загружать данные своих отношений только тогда, когда вы на самом деле получите к ним доступ. Из-за этого разработчики часто используют готовую загрузку, чтобы предварительно загрузить отношения, которые, как они знают, будут доступны после загрузки модели. Стремительная загрузка обеспечивает значительное сокращение запросов SQL, которые должны быть выполнены для загрузки отношений модели.
Запрос наличия отношений
При доступе к записям для модели вы можете захотеть ограничить свои результаты в зависимости от наличия отношений. Например, представьте, что вы хотите получить все сообщения блога, в которых есть хотя бы один комментарий. Для этого, вы можете передать имя отношения к has
и orHas
методам:
// Retrieve all posts that have at least one comment...
$posts = App\Post::has('comments')->get();
Вы также можете указать оператора и количество для дальнейшей настройки запроса:
// Retrieve all posts that have three or more comments...
$posts = App\Post::has('comments', '>=', 3)->get();
Вложенные has
операторы также могут быть построены с использованием «точечной» записи. Например, вы можете получить все сообщения, которые имеют хотя бы один комментарий и голосуют:
// Retrieve posts that have at least one comment with votes...
$posts = App\Post::has('comments.votes')->get();
Если вам нужно еще больше мощности, вы можете использовать whereHas
и orWhereHas
методы поставить « где» условия на ваших has
запросов. Эти методы позволяют добавлять настраиваемые ограничения в ограничение отношений, например, проверять содержимое комментария:
use Illuminate\Database\Eloquent\Builder;
// Retrieve posts with at least one comment containing words like foo%...
$posts = App\Post::whereHas('comments', function ($query) {
$query->where('content', 'like', 'foo%');
})->get();
// Retrieve posts with at least ten comments containing words like foo%...
$posts = App\Post::whereHas('comments', function ($query) {
$query->where('content', 'like', 'foo%');
}, '>=', 10)->get();
Запросы Отношения Отсутствие
При доступе к записям для модели вы можете захотеть ограничить свои результаты из-за отсутствия связи. Например, представьте, что вы хотите получить все сообщения блога, в которых нет комментариев. Для этого, вы можете передать имя отношения к doesntHave
и orDoesntHave
методам:
$posts = App\Post::doesntHave('comments')->get();
Если вам нужно еще больше мощности, вы можете использовать whereDoesntHave
и orWhereDoesntHave
методы поставить « где» условия на ваших doesntHave
запросов. Эти методы позволяют добавлять настраиваемые ограничения в ограничение отношений, например, проверять содержимое комментария:
use Illuminate\Database\Eloquent\Builder;
$posts = App\Post::whereDoesntHave('comments', function (Builder $query) {
$query->where('content', 'like', 'foo%');
})->get();
Вы можете использовать «точечную» нотацию для выполнения запроса к вложенным отношениям. Например, следующий запрос извлечет все сообщения с комментариями от авторов, которые не заблокированы:
use Illuminate\Database\Eloquent\Builder;
$posts = App\Post::whereDoesntHave('comments.author', function (Builder $query) {
$query->where('banned', 1);
})->get();
Подсчет связанных моделей
Если вы хотите подсчитать количество результатов в отношениях без фактической загрузки, вы можете использовать withCount
метод, который поместит столбец в ваши полученные модели. Например:{relation}_count
$posts = App\Post::withCount('comments')->get();
foreach ($posts as $post) {
echo $post->comments_count;
}
Вы можете добавить «счетчики» для нескольких отношений, а также добавить ограничения к запросам:
$posts = App\Post::withCount(['votes', 'comments' => function ($query) {
$query->where('content', 'like', 'foo%');
}])->get();
echo $posts[0]->votes_count;
echo $posts[0]->comments_count;
Вы также можете присвоить псевдониму результат подсчета отношений, что позволяет использовать несколько подсчетов для одного отношения:
$posts = App\Post::withCount([
'comments',
'comments as pending_comments_count' => function ($query) {
$query->where('approved', false);
}
])->get();
echo $posts[0]->comments_count;
echo $posts[0]->pending_comments_count;
Если вы комбинируете withCount
с select
оператором, убедитесь, что вы вызываете withCount
после select
метода:
$posts = App\Post::select(['title', 'body'])->withCount('comments')->get();
echo $posts[0]->title;
echo $posts[0]->body;
echo $posts[0]->comments_count;
Нетерпеливая загрузка
При доступе к отношениям Eloquent в качестве свойств данные отношений «загружаются лениво». Это означает, что данные отношения фактически не загружаются, пока вы не получите первый доступ к свойству. Однако Eloquent может «загружать» отношения во время запроса родительской модели. Стремительная загрузка облегчает проблему запроса N + 1. Чтобы проиллюстрировать проблему запроса N + 1, рассмотрим Book
модель, которая связана с Author
:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Book extends Model
{
/**
* Get the author that wrote the book.
*/
public function author()
{
return $this->belongsTo('App\Author');
}
}
Теперь давайте вернем все книги и их авторов:
$books = App\Book::all();
foreach ($books as $book) {
echo $book->author->name;
}
Этот цикл выполнит 1 запрос для получения всех книг в таблице, а затем еще один запрос для каждой книги для поиска автора. Таким образом, если у нас 25 книг, этот цикл будет выполнять 26 запросов: 1 для исходной книги и 25 дополнительных запросов для поиска автора каждой книги.
К счастью, мы можем использовать готовую загрузку, чтобы сократить эту операцию до 2 запросов. При запросе вы можете указать, какие отношения следует загружать с помощью with
метода:
$books = App\Book::with('author')->get();
foreach ($books as $book) {
echo $book->author->name;
}
Для этой операции будут выполнены только два запроса:
select * from books
select * from authors where id in (1, 2, 3, 4, 5, ...)
Стремление загружать несколько отношений
Иногда вам может потребоваться загрузить несколько различных отношений в одной операции. Для этого просто передайте дополнительные аргументы with
методу:
$books = App\Book::with(['author', 'publisher'])->get();
Вложенная нетерпеливая загрузка
Чтобы загружать вложенные отношения, вы можете использовать синтаксис «точка». Например, давайте стремимся загрузить всех авторов книги и все личные контакты автора в одно высказывание Eloquent:
$books = App\Book::with('author.contacts')->get();
Стремление загружать определенные столбцы
Вам не всегда может понадобиться каждый столбец из отношений, которые вы извлекаете. По этой причине Eloquent позволяет указать, какие столбцы отношений вы хотите получить:
$books = App\Book::with('author:id,name')->get();
При использовании этой функции вы всегда должны включать
id
столбец и все соответствующие столбцы внешнего ключа в список столбцов, которые вы хотите получить.
Eager Загрузка по умолчанию
Иногда вы можете захотеть всегда загружать некоторые отношения при получении модели. Для этого вы можете определить $with
свойство модели:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Book extends Model
{
/**
* The relationships that should always be loaded.
*
* @var array
*/
protected $with = ['author'];
/**
* Get the author that wrote the book.
*/
public function author()
{
return $this->belongsTo('App\Author');
}
}
Если вы хотите удалить элемент из $with
свойства для одного запроса, вы можете использовать without
метод:
$books = App\Book::without('author')->get();
Сдерживая энергичные нагрузки
Иногда вы можете захотеть загрузить отношения, но также указать дополнительные условия запроса для запроса загрузки. Вот пример:
$users = App\User::with(['posts' => function ($query) {
$query->where('title', 'like', '%first%');
}])->get();
В этом примере Eloquent будет загружать сообщения только в том случае, если в title
столбце сообщения содержится слово first
. Вы можете вызвать другие методы построителя запросовдля дальнейшей настройки операции загрузки:
$users = App\User::with(['posts' => function ($query) {
$query->orderBy('created_at', 'desc');
}])->get();
limit
Иtake
методы построителя запросов не может быть использован , когда сдерживая нетерпеливые нагрузки.
Lazy Eager Загрузка
Иногда вам может потребоваться загрузить отношения после того, как родительская модель уже найдена. Например, это может быть полезно, если вам нужно динамически решить, загружать ли связанные модели:
$books = App\Book::all();
if ($someCondition) {
$books->load('author', 'publisher');
}
Если вам нужно установить дополнительные ограничения запроса для активного запроса на загрузку, вы можете передать массив с ключами, которые вы хотите загрузить. Значения массива должны быть Closure
экземплярами, которые получают экземпляр запроса:
$books->load(['author' => function ($query) {
$query->orderBy('published_date', 'asc');
}]);
Чтобы загрузить отношение только тогда, когда оно еще не загружено, используйте loadMissing
метод:
public function format(Book $book)
{
$book->loadMissing('author');
return [
'name' => $book->name,
'author' => $book->author->name
];
}
Вложенный Ленивый Стремительный Загрузка & morphTo
Если вы хотите загружать morphTo
отношения, а также вложенные отношения на различных объектах, которые могут быть возвращены этими отношениями, вы можете использовать loadMorph
метод.
Этот метод принимает имя morphTo
отношения в качестве первого аргумента, а массив пар модель / отношение - в качестве второго аргумента. Чтобы проиллюстрировать этот метод, давайте рассмотрим следующую модель:
<?php
use Illuminate\Database\Eloquent\Model;
class ActivityFeed extends Model
{
/**
* Get the parent of the activity feed record.
*/
public function parentable()
{
return $this->morphTo();
}
}
В этом примере, допустим Event
, Photo
и Post
модели могут создавать ActivityFeed
модели. Кроме того, давайте предположим, что Event
модели принадлежат Calendar
модели, Photo
модели связаны с Tag
моделями, а Post
модели принадлежат Author
модели.
Используя эти определения моделей и взаимосвязи, мы можем извлечь ActivityFeed
экземпляры parentable
моделей и загрузить все модели и их соответствующие вложенные взаимосвязи:
$activities = ActivityFeed::with('parentable')
->get()
->loadMorph('parentable', [
Event::class => ['calendar'],
Photo::class => ['tags'],
Post::class => ['author'],
]);
Вставка и обновление связанных моделей
Метод сохранения
Eloquent предоставляет удобные методы для добавления новых моделей в отношения. Например, возможно, вам нужно вставить новый Comment
для Post
модели. Вместо ручной установки post_id
атрибута в Comment
, вы можете вставить Comment
непосредственно из save
метода отношения :
$comment = new App\Comment(['message' => 'A new comment.']);
$post = App\Post::find(1);
$post->comments()->save($comment);
Обратите внимание, что мы не получили доступ к comments
связи как к динамическому свойству. Вместо этого мы вызвали comments
метод для получения экземпляра отношения. save
Метод будет автоматически добавить соответствующее post_id
значение для новой Comment
модели.
Если вам нужно сохранить несколько связанных моделей, вы можете использовать saveMany
метод:
$post = App\Post::find(1);
$post->comments()->saveMany([
new App\Comment(['message' => 'A new comment.']),
new App\Comment(['message' => 'Another comment.']),
]);
Рекурсивно сохраняющие модели и отношения
Если вам нужна save
ваша модель и все связанные с ней отношения, вы можете использовать push
метод:
$post = App\Post::find(1);
$post->comments[0]->message = 'Message';
$post->comments[0]->author->name = 'Author Name';
$post->push();
Метод создания
В дополнении к save
и saveMany
методам, вы можете также использовать create
метод, который принимает массив атрибутов, создает модель, и вставляет его в базу данных. Опять же, разница между save
и create
заключается в том, что он save
принимает полный экземпляр модели Eloquent, а create
принимает простой PHP array
:
$post = App\Post::find(1);
$comment = $post->comments()->create([
'message' => 'A new comment.',
]);
Перед использованием
create
метода обязательно ознакомьтесь с документацией по массовому присвоению атрибутов .
Вы можете использовать createMany
метод для создания нескольких связанных моделей:
$post = App\Post::find(1);
$post->comments()->createMany([
[
'message' => 'A new comment.',
],
[
'message' => 'Another new comment.',
],
]);
Вы также можете использовать findOrNew
, firstOrNew
, firstOrCreate
и updateOrCreate
методы создания и модель обновления на отношениях .
Принадлежит к отношениям
При обновлении belongsTo
отношений вы можете использовать associate
метод. Этот метод установит внешний ключ на дочерней модели:
$account = App\Account::find(10);
$user->account()->associate($account);
$user->save();
При удалении belongsTo
отношений вы можете использовать dissociate
метод. Этот метод установит внешний ключ отношения в null
:
$user->account()->dissociate();
$user->save();
Модели по умолчанию
В belongsTo
, hasOne
, hasOneThrough
и morphOne
отношения позволяют определить модель по умолчанию , который будет возвращен , если данная взаимосвязь null
. Этот шаблон часто называется шаблоном Null Object и может помочь удалить условные проверки в вашем коде. В следующем примере user
отношение вернет пустую модель, если к сообщению не прикреплено no :App\User
user
/**
* Get the author of the post.
*/
public function user()
{
return $this->belongsTo('App\User')->withDefault();
}
Чтобы заполнить модель по умолчанию атрибутами, вы можете передать массив или Closure withDefault
методу:
/**
* Get the author of the post.
*/
public function user()
{
return $this->belongsTo('App\User')->withDefault([
'name' => 'Guest Author',
]);
}
/**
* Get the author of the post.
*/
public function user()
{
return $this->belongsTo('App\User')->withDefault(function ($user) {
$user->name = 'Guest Author';
});
}
Много ко многим отношениям
Присоединение / отсоединение
Eloquent также предоставляет несколько дополнительных вспомогательных методов, чтобы сделать работу со связанными моделями более удобной. Например, давайте представим, что у пользователя может быть много ролей, а у роли может быть много пользователей. Чтобы прикрепить роль к пользователю, вставив запись в промежуточную таблицу, которая объединяет модели, используйте attach
метод:
$user = App\User::find(1);
$user->roles()->attach($roleId);
Прикрепляя отношение к модели, вы также можете передать массив дополнительных данных для вставки в промежуточную таблицу:
$user->roles()->attach($roleId, ['expires' => $expires]);
Иногда может потребоваться удалить роль у пользователя. Чтобы удалить запись отношения «многие ко многим», используйте detach
метод. detach
Метод удаляет соответствующую запись из промежуточной таблицы; однако обе модели останутся в базе данных:
// Detach a single role from the user...
$user->roles()->detach($roleId);
// Detach all roles from the user...
$user->roles()->detach();
Для удобства, attach
а detach
также принимаем массивы идентификаторов в качестве входных данных:
$user = App\User::find(1);
$user->roles()->detach([1, 2, 3]);
$user->roles()->attach([
1 => ['expires' => $expires],
2 => ['expires' => $expires]
]);
Синхронизация ассоциаций
Вы также можете использовать этот sync
метод для построения ассоциаций «многие ко многим». sync
Метод принимает массив идентификаторов для размещения на промежуточной таблице. Любые идентификаторы, которых нет в данном массиве, будут удалены из промежуточной таблицы. Таким образом, после завершения этой операции в промежуточной таблице будут существовать только идентификаторы в данном массиве:
$user->roles()->sync([1, 2, 3]);
Вы также можете передать дополнительные промежуточные значения таблицы с идентификаторами:
$user->roles()->sync([1 => ['expires' => true], 2, 3]);
Если вы не хотите отсоединять существующие идентификаторы, вы можете использовать syncWithoutDetaching
метод:
$user->roles()->syncWithoutDetaching([1, 2, 3]);
Тоглинг Ассоциации
Отношение «многие ко многим» также предоставляет toggle
метод, который «переключает» статус вложения данных идентификаторов. Если данный идентификатор в настоящее время прикреплен, он будет отсоединен. Аналогично, если он в данный момент отсоединен, он будет прикреплен:
$user->roles()->toggle([1, 2, 3]);
Сохранение дополнительных данных в сводной таблице
При работе с отношением «многие ко многим» save
метод принимает массив дополнительных атрибутов промежуточной таблицы в качестве второго аргумента:
App\User::find(1)->roles()->save($role, ['expires' => $expires]);
Обновление записи в сводной таблице
Если вам нужно обновить существующую строку в сводной таблице, вы можете использовать updateExistingPivot
метод. Этот метод принимает внешний ключ сводной записи и массив атрибутов для обновления:
$user = App\User::find(1);
$user->roles()->updateExistingPivot($roleId, $attributes);
Касаясь родительских отметок времени
Когда модель belongsTo
или belongsToMany
другая модель, например, Comment
принадлежащая a Post
, иногда полезно обновить метку времени родителя при обновлении дочерней модели. Например, при Comment
обновлении модели может потребоваться автоматически «коснуться» updated_at
отметки времени владения Post
. Красноречивый делает это легко. Просто добавьте touches
свойство, содержащее имена отношений в дочернюю модель:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Comment extends Model
{
/**
* All of the relationships to be touched.
*
* @var array
*/
protected $touches = ['post'];
/**
* Get the post that the comment belongs to.
*/
public function post()
{
return $this->belongsTo('App\Post');
}
}
Теперь, когда вы обновляете a Comment
, у владельца Post
будет также updated_at
обновляться его столбец, чтобы было удобнее узнать, когда делать недействительным кеш Post
модели:
$comment = App\Comment::find(1);
$comment->text = 'Edit to this comment!';
$comment->save();
0 комментариев