1. Eloquent ORM
  2. Eloquent: Отношения

Присоединяйся к нашему Telegram сообществу @webblend!

Здесь ты найдешь сниппеты по Laravel и полезные советы по веб-разработке.

Введение

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

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

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

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

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

Один к Одному

Отношение один к одному - это очень базовый тип отношений в базе данных. Например, модель User может быть связана с одной моделью Phone. Для определения этого отношения мы разместим метод phone в модели User. Метод phone должен вызывать метод hasOne и возвращать его результат. Метод hasOne доступен вашей модели через базовый класс модели Illuminate\Database\Eloquent\Model:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasOne;
 
class User extends Model
{
/**
* Получить телефон, связанный с пользователем.
*/
public function phone(): HasOne
{
return $this->hasOne(Phone::class);
}
}

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

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

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

return $this->hasOne(Phone::class, 'foreign_key');

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

return $this->hasOne(Phone::class, 'foreign_key', 'local_key');

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

Таким образом, мы можем получить доступ к модели Phone из нашей модели User. Теперь давайте определим отношение в модели Phone, которое позволит нам получить доступ к пользователю, которому принадлежит телефон. Мы можем определить инверсию отношения hasOne с помощью метода belongsTo:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
 
class Phone extends Model
{
/**
* Получить пользователя, которому принадлежит телефон.
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
}

При вызове метода user Eloquent попытается найти модель User с id, который соответствует столбцу user_id модели Phone.

Eloquent определяет имя внешнего ключа, изучая имя метода отношения и добавляя к имени метода суффикс _id. Таким образом, в данном случае Eloquent предполагает, что у модели Phone есть столбец user_id. Однако, если внешний ключ на модели Phone не является user_id, вы можете передать пользовательское имя ключа вторым аргументом методу belongsTo:

/**
* Получить пользователя, которому принадлежит телефон.
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class, 'foreign_key');
}

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

/**
* Получить пользователя, которому принадлежит телефон.
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class, 'foreign_key', 'owner_key');
}

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

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

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
 
class Post extends Model
{
/**
* Получить комментарии для блог-поста.
*/
public function comments(): HasMany
{
return $this->hasMany(Comment::class);
}
}

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

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

use App\Models\Post;
 
$comments = Post::find(1)->comments;
 
foreach ($comments as $comment) {
// ...
}

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

$comment = Post::find(1)->comments()
->where('title', 'foo')
->first();

Как и метод hasOne, вы также можете переопределить внешние и локальные ключи, передав дополнительные аргументы методу hasMany:

return $this->hasMany(Comment::class, 'foreign_key');
 
return $this->hasMany(Comment::class, 'foreign_key', 'local_key');

Один ко Многим (Инверсия) / Принадлежит

Теперь, когда у нас есть доступ ко всем комментариям к посту, давайте определим отношение, позволяющее комментарию получить доступ к своему родительскому посту. Для определения обратного отношения hasMany определите метод отношения в дочерней модели, который вызывает метод belongsTo:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
 
class Comment extends Model
{
/**
* Получить сообщение, которому принадлежит комментарий.
*/
public function post(): BelongsTo
{
return $this->belongsTo(Post::class);
}
}

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

use App\Models\Comment;
 
$comment = Comment::find(1);
 
return $comment->post->title;

В приведенном выше примере Eloquent попытается найти модель Post, у которой id соответствует столбцу post_id модели Comment.

Eloquent определяет имя внешнего ключа по умолчанию, осматривая имя метода отношения и добавляя к нему суффикс _, за которым следует имя столбца первичного ключа родительской модели. Таким образом, в этом примере Eloquent предположит, что внешний ключ модели Post в таблице comments - post_id.

Однако, если внешний ключ для вашего отношения не следует этим соглашениям, вы можете передать имя собственного внешнего ключа вторым аргументом методу belongsTo:

/**
* Получить сообщение, которому принадлежит комментарий.
*/
public function post(): BelongsTo
{
return $this->belongsTo(Post::class, 'foreign_key');
}

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

/**
* Получить сообщение, которому принадлежит комментарий.
*/
public function post(): BelongsTo
{
return $this->belongsTo(Post::class, 'foreign_key', 'owner_key');
}

Модели По Умолчанию

Отношения belongsTo, hasOne, hasOneThrough и morphOne позволяют определить модель по умолчанию, которая будет возвращена, если данное отношение равно null. Этот шаблон часто называется шаблоном Null Object и может помочь убрать условные проверки в вашем коде. В следующем примере отношение user вернет пустую модель App\Models\User, если к Post модели не прикреплен пользователь:

/**
* Получить автора сообщения.
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class)->withDefault();
}

Чтобы заполнить модель по умолчанию атрибутами, вы можете передать массив или замыкание методу withDefault:

/**
* Получить автора сообщения.
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class)->withDefault([
'name' => 'Guest Author',
]);
}
 
/**
* Получить автора сообщения.
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class)->withDefault(function (User $user, Post $post) {
$user->name = 'Guest Author';
});
}

Запросы к Отношениям "Принадлежит"

При запросе детей отношения "принадлежит", вы можете вручную создавать where-условие для получения соответствующих моделей Eloquent:

use App\Models\Post;
 
$posts = Post::where('user_id', $user->id)->get();

Однако вам может быть удобнее использовать метод whereBelongsTo, который автоматически определит соответствующее отношение и внешний ключ для данной модели:

$posts = Post::whereBelongsTo($user)->get();

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

$users = User::where('vip', true)->get();
 
$posts = Post::whereBelongsTo($users)->get();

По умолчанию Laravel определит отношение, связанное с данной моделью, на основе имени класса модели; однако вы можете вручную указать имя отношения, предоставив его вторым аргументом методу whereBelongsTo:

$posts = Post::whereBelongsTo($user, 'author')->get();

Has One Of Many

Иногда модель может иметь много связанных моделей, но вы хотите легко получить "последнюю" или "старшую" связанную модель отношения. Например, модель User может быть связана с многими моделями Order, но вы хотите определить удобный способ взаимодействия с самым последним заказом, который пользователь сделал. Это можно сделать, используя отношение hasOne в сочетании с методами ofMany:

/**
* Получить самый последний заказ пользователя.
*/
public function latestOrder(): HasOne
{
return $this->hasOne(Order::class)->latestOfMany();
}

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

/**
* Получить самый старый заказ пользователя.
*/
public function oldestOrder(): HasOne
{
return $this->hasOne(Order::class)->oldestOfMany();
}

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

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

/**
* Получить самый крупный заказ пользователя.
*/
public function largestOrder(): HasOne
{
return $this->hasOne(Order::class)->ofMany('price', 'max');
}

Внимание Поскольку PostgreSQL не поддерживает выполнение функции MAX для столбцов UUID, в настоящее время невозможно использовать отношения один-ко-многим в сочетании с столбцами UUID PostgreSQL.

Преобразование "Многие" Отношений в "Один К Одному"

Часто, при получении одной модели с использованием методов latestOfMany, oldestOfMany или ofMany, у вас уже есть определенное отношение "has many" для той же модели. Для удобства Laravel позволяет легко преобразовать это отношение в отношение "has one", вызвав метод one для отношения:

/**
* Получить заказы пользователя.
*/
public function orders(): HasMany
{
return $this->hasMany(Order::class);
}
 
/**
* Получить самый крупный заказ пользователя.
*/
public function largestOrder(): HasOne
{
return $this->orders()->one()->ofMany('price', 'max');
}

Расширенные Отношения "Один из многих"

Возможно создание более сложных отношений "has one of many". Например, модель Product может иметь много связанных моделей Price, которые сохраняются в системе даже после публикации новых цен. Кроме того, новые данные о ценах на продукт могут быть опубликованы заранее, чтобы вступить в силу в будущем через столбец published_at.

Таким образом, в краткости, нам нужно извлечь последние опубликованные цены, где дата публикации не находится в будущем. Кроме того, если две цены имеют одинаковую дату публикации, мы предпочтем цену с наибольшим ID. Для достижения этой цели нам нужно передать массив методу ofMany, который содержит сортируемые столбцы, определяющие последнюю цену. Кроме того, вторым аргументом методу ofMany будет предоставлено замыкание. Это замыкание будет отвечать за добавление дополнительных ограничений даты публикации к запросу отношения:

/**
* Получить текущую цену на продукт.
*/
public function currentPricing(): HasOne
{
return $this->hasOne(Price::class)->ofMany([
'published_at' => 'max',
'id' => 'max',
], function (Builder $query) {
$query->where('published_at', '<', now());
});
}

Has One Through

Отношение "has-one-through" определяет однозначное отношение к другой модели. Однако это отношение указывает, что объявляющая модель может быть сопоставлена с одним экземпляром другой модели, пройдя через третью модель.

Например, в приложении автомастерской каждая модель Mechanic может быть связана с одной моделью Car, и каждая модель Car может быть связана с одной моделью Owner. В то время как механик и владелец не имеют прямого отношения в базе данных, механик может получить доступ к владельцу через модель Car. Рассмотрим таблицы, необходимые для определения этого отношения:

mechanics
id - integer
name - string
 
cars
id - integer
model - string
mechanic_id - integer
 
owners
id - integer
name - string
car_id - integer

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

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasOneThrough;
 
class Mechanic extends Model
{
/**
* Получить владельца автомобиля.
*/
public function carOwner(): HasOneThrough
{
return $this->hasOneThrough(Owner::class, Car::class);
}
}

Первый аргумент, передаваемый методу hasOneThrough, - это имя конечной модели, которую мы хотим получить, в то время как второй аргумент - это имя промежуточной модели.

Или, если соответствующие отношения уже определены для всех моделей, участвующих в отношении, вы можете легко определить отношение "has-one-through", вызвав метод through и предоставив имена этих отношений. Например, если у модели Mechanic есть отношение cars, и у модели Car есть отношение owner, вы можете определить отношение "has-one-through", соединяющее механика и владельца, следующим образом:

// Синтаксис на основе строки...
return $this->through('cars')->has('owner');
 
// Dynamic syntax...
return $this->throughCars()->hasOwner();

Ключевые Конвенции

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

class Mechanic extends Model
{
/**
* Получить владельца автомобиля.
*/
public function carOwner(): HasOneThrough
{
return $this->hasOneThrough(
Owner::class,
Car::class,
'mechanic_id', // Foreign key on the cars table...
'car_id', // Foreign key on the owners table...
'id', // Local key on the mechanics table...
'id' // Local key on the cars table...
);
}
}

Или, как обсуждалось ранее, если соответствующие отношения уже определены для всех моделей, участвующих в отношении, вы можете легко определить отношение "has-one-through", вызвав метод through и предоставив имена этих отношений. Этот подход предлагает преимущество повторного использования соглашений о ключах, уже определенных в существующих отношениях:

// Синтаксис на основе строки...
return $this->through('cars')->has('owner');
 
// Dynamic syntax...
return $this->throughCars()->hasOwner();

Отношение "Ко Многим Через"

Отношение "has-many-through" предоставляет удобный способ доступа к дальним отношениям через промежуточное отношение. Например, допустим, мы создаем платформу развертывания, подобную Laravel Vapor. Модель Project может получать доступ к многим моделям Deployment через промежуточную модель Environment. Используя этот пример, вы легко можете собрать все развертывания для заданного проекта. Давайте рассмотрим таблицы, необходимые для определения этого отношения:

projects
id - integer
name - string
 
environments
id - integer
project_id - integer
name - string
 
deployments
id - integer
environment_id - integer
commit_hash - string

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

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
 
class Project extends Model
{
/**
* Получить все развертывания для проекта.
*/
public function deployments(): HasManyThrough
{
return $this->hasManyThrough(Deployment::class, Environment::class);
}
}

Первый аргумент, передаваемый методу hasManyThrough, - это имя конечной модели, которую мы хотим получить, в то время как второй аргумент - это имя промежуточной модели.

Или, если соответствующие отношения уже определены для всех моделей, участвующих в отношении, вы можете легко определить отношение "has-many-through", вызвав метод through и предоставив имена этих отношений. Например, если у модели Project есть отношение environments, и у модели Environment есть отношение deployments, вы можете определить отношение "has-many-through", соединяющее проект и развертывания, следующим образом:

// Синтаксис на основе строки...
return $this->through('environments')->has('deployments');
 
// Dynamic syntax...
return $this->throughEnvironments()->hasDeployments();

Хотя таблица модели Deployment не содержит столбца project_id, отношение hasManyThrough обеспечивает доступ к развертываниям проекта через $project->deployments. Для получения этих моделей Eloquent анализирует столбец project_id на таблице промежуточной модели Environment. После нахождения соответствующих идентификаторов среды они используются для запроса таблицы модели Deployment.

Ключевые Конвенции

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

class Project extends Model
{
public function deployments(): HasManyThrough
{
return $this->hasManyThrough(
Deployment::class,
Environment::class,
'project_id', // Foreign key on the environments table...
'environment_id', // Foreign key on the deployments table...
'id', // Local key on the projects table...
'id' // Local key on the environments table...
);
}
}

Или, как обсуждалось ранее, если соответствующие отношения уже определены для всех моделей, участвующих в отношении, вы можете легко определить отношение "has-many-through", вызвав метод through и предоставив имена этих отношений. Этот подход предлагает преимущество повторного использования соглашений о ключах, уже определенных в существующих отношениях:

// Синтаксис на основе строки...
return $this->through('environments')->has('deployments');
 
// Dynamic syntax...
return $this->throughEnvironments()->hasDeployments();

Отношения Многие ко Многим

Отношения многие ко многим немного сложнее, чем отношения hasOne и hasMany. Примером отношения многие ко многим является пользователь, у которого много ролей, и эти роли также распределены между другими пользователями в приложении. Например, пользователю может быть назначена роль "Автор" и "Редактор"; однако эти роли также могут быть назначены другим пользователям. Таким образом, у пользователя много ролей, и у роли много пользователей.

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

Для определения этого отношения требуется три таблицы базы данных: users, roles и role_user. Таблица role_user производится из алфавитного порядка имен связанных моделей и содержит столбцы user_id и role_id. Эта таблица используется в качестве промежуточной таблицы, соединяющей пользователей и роли.

Помните, что так как роль может принадлежать многим пользователям, мы не можем просто поместить столбец user_id в таблицу roles. Это означало бы, что роль может принадлежать только одному пользователю. Для поддержки возможности назначения ролей нескольким пользователям необходима таблица role_user. Мы можем суммировать структуру таблицы отношения следующим образом:

users
id - integer
name - string
 
roles
id - integer
name - string
 
role_user
user_id - integer
role_id - integer

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

Отношения многие ко многим определяются путем написания метода, который возвращает результат метода belongsToMany. Метод belongsToMany предоставляется базовым классом Illuminate\Database\Eloquent\Model, который используется всеми моделями Eloquent вашего приложения. Например, давайте определим метод roles в нашей модели User. Первый аргумент, передаваемый этому методу, - это имя связанного класса модели:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
 
class User extends Model
{
/**
* Роли, принадлежащие пользователю.
*/
public function roles(): BelongsToMany
{
return $this->belongsToMany(Role::class);
}
}

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

use App\Models\User;
 
$user = User::find(1);
 
foreach ($user->roles as $role) {
// ...
}

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

$roles = User::find(1)->roles()->orderBy('name')->get();

Для определения имени таблицы промежуточного отношения Eloquent объединяет два связанных имени модели в алфавитном порядке. Тем не менее, вы вольны переопределить это соглашение. Вы можете сделать это, передав второй аргумент методу belongsToMany:

return $this->belongsToMany(Role::class, 'role_user');

Помимо настройки имени промежуточной таблицы, вы также можете настраивать имена столбцов ключей на таблице, передав дополнительные аргументы методу belongsToMany. Третий аргумент - это имя внешнего ключа модели, для которой вы определяете отношение, а четвертый аргумент - это имя внешнего ключа модели, с которой вы соединяетесь:

return $this->belongsToMany(Role::class, 'role_user', 'user_id', 'role_id');

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

Чтобы определить "обратное" отношение многие ко многим, вы должны определить метод в связанной модели, который также возвращает результат метода belongsToMany. Чтобы завершить наш пример с пользователем / ролью, давайте определим метод users в модели Role:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
 
class Role extends Model
{
/**
* Пользователи, принадлежащие роли.
*/
public function users(): BelongsToMany
{
return $this->belongsToMany(User::class);
}
}

Как видите, отношение определено точно так же, как его аналог в модели User, за исключением ссылки на модель App\Models\User. Поскольку мы используем метод belongsToMany, все обычные опции настройки таблицы и ключей доступны при определении "обратного" отношения многие ко многим.

Получение Промежуточных Колонок Таблицы

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

use App\Models\User;
 
$user = User::find(1);
 
foreach ($user->roles as $role) {
echo $role->pivot->created_at;
}

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

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

return $this->belongsToMany(Role::class)->withPivot('active', 'created_by');

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

return $this->belongsToMany(Role::class)->withTimestamps();

Внимание Промежуточные таблицы, использующие автоматически поддерживаемые временные метки Eloquent, должны иметь как столбец времени создания (created_at), так и столбец времени обновления (updated_at).

Настройка Имени Атрибута pivot

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

Например, если ваше приложение содержит пользователей, которые могут подписываться на подкасты, вероятно, у вас есть отношение многие ко многим между пользователями и подкастами. В этом случае вы можете переименовать атрибут промежуточной таблицы в subscription вместо pivot. Это можно сделать, используя метод as при определении отношения:

return $this->belongsToMany(Podcast::class)
->as('subscription')
->withTimestamps();

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

$users = User::with('podcasts')->get();
 
foreach ($users->flatMap->podcasts as $podcast) {
echo $podcast->subscription->created_at;
}

Фильтрация Запросов Через Промежуточные Колонки Таблицы

Результаты запросов к отношению belongsToMany также можно фильтровать с использованием методов wherePivot, wherePivotIn, wherePivotNotIn, wherePivotBetween, wherePivotNotBetween, wherePivotNull, и wherePivotNotNull при определении отношения:

return $this->belongsToMany(Role::class)
->wherePivot('approved', 1);
 
return $this->belongsToMany(Role::class)
->wherePivotIn('priority', [1, 2]);
 
return $this->belongsToMany(Role::class)
->wherePivotNotIn('priority', [1, 2]);
 
return $this->belongsToMany(Podcast::class)
->as('subscriptions')
->wherePivotBetween('created_at', ['2020-01-01 00:00:00', '2020-12-31 00:00:00']);
 
return $this->belongsToMany(Podcast::class)
->as('subscriptions')
->wherePivotNotBetween('created_at', ['2020-01-01 00:00:00', '2020-12-31 00:00:00']);
 
return $this->belongsToMany(Podcast::class)
->as('subscriptions')
->wherePivotNull('expired_at');
 
return $this->belongsToMany(Podcast::class)
->as('subscriptions')
->wherePivotNotNull('expired_at');

Сортировка Запросов Через Промежуточные Колонки Таблицы

Результаты запросов к отношению belongsToMany также можно упорядочивать с использованием метода orderByPivot. В следующем примере мы получим все последние значки для пользователя:

return $this->belongsToMany(Badge::class)
->where('rank', 'gold')
->orderByPivot('created_at', 'desc');

Определение Пользовательских Промежуточных Таблиц

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

Пользовательские модели промежуточной таблицы многие ко многим должны расширять класс Illuminate\Database\Eloquent\Relations\Pivot, а пользовательские полиморфные модели промежуточной таблицы многие ко многим должны расширять класс Illuminate\Database\Eloquent\Relations\MorphPivot. Например, мы можем определить модель Role, которая использует пользовательскую модель промежуточной таблицы RoleUser:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
 
class Role extends Model
{
/**
* Пользователи, принадлежащие роли.
*/
public function users(): BelongsToMany
{
return $this->belongsToMany(User::class)->using(RoleUser::class);
}
}

При определении модели RoleUser вы должны расширять класс Illuminate\Database\Eloquent\Relations\Pivot:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Relations\Pivot;
 
class RoleUser extends Pivot
{
// ...
}

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

Пользовательские Модели Pivot и Инкрементируемые ID

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

/**
* Указывает, автоинкрементируются ли идентификаторы.
*
* @var bool
*/
public $incrementing = true;

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

Полиморфное отношение позволяет дочерней модели принадлежать более чем к одному типу модели, используя единственную ассоциацию. Например, представьте, что вы создаете приложение, которое позволяет пользователям обмениваться блоговыми записями и видео. В таком приложении модель Comment может принадлежать как модели Post, так и модели Video.

Один к Одному (Полиморфные)

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

Отношение один к одному полиморфное подобно типичному отношению один к одному; однако дочерняя модель может принадлежать более чем к одному типу модели с использованием единственной ассоциации. Например, блоговая запись Post и 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 используется Eloquent для определения, который «тип» родительской модели возвращать при доступе к отношению imageable. В данном случае этот столбец будет содержать либо App\Models\Post, либо App\Models\User.

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

Теперь давайте рассмотрим определения моделей, необходимых для построения этого отношения:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphTo;
 
class Image extends Model
{
/**
* Получить родительскую модель изображения (пользователь или сообщение).
*/
public function imageable(): MorphTo
{
return $this->morphTo();
}
}
 
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphOne;
 
class Post extends Model
{
/**
* Получить изображение сообщения.
*/
public function image(): MorphOne
{
return $this->morphOne(Image::class, 'imageable');
}
}
 
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphOne;
 
class User extends Model
{
/**
* Получить изображение пользователя.
*/
public function image(): MorphOne
{
return $this->morphOne(Image::class, 'imageable');
}
}

Получение Отношения

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

use App\Models\Post;
 
$post = Post::find(1);
 
$image = $post->image;

Вы можете получить родительскую модель полиморфной модели, обратившись к имени метода, который выполняет вызов morphTo. В данном случае это метод imageable модели Image. Так что мы получим доступ к этому методу как к динамическому свойству отношения:

use App\Models\Image;
 
$image = Image::find(1);
 
$imageable = $image->imageable;

Отношение imageable модели Image вернет экземпляр либо модели Post, либо модели User, в зависимости от того, какой тип модели владеет изображением.

Ключевые Конвенции

При необходимости вы можете указать имя используемых колонок "id" и "type" в полиморфной дочерней модели. Если вы это делаете, убедитесь, что вы всегда передаете имя отношения в качестве первого аргумента методу morphTo. Обычно это значение должно соответствовать имени метода, так что вы можете использовать константу __FUNCTION__ в PHP:

/**
* Получить модель, которой принадлежит изображение.
*/
public function imageable(): MorphTo
{
return $this->morphTo(__FUNCTION__, 'imageable_type', 'imageable_id');
}

Один ко Многим (Полиморфные)

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

Отношение один ко многим полиморфное подобно типичному отношению один ко многим; однако дочерняя модель может принадлежать более чем к одному типу модели с использованием единственной ассоциации. Например, представьте, что пользователи вашего приложения могут "комментировать" сообщения и видео. Используя полиморфные отношения, вы можете использовать единственную таблицу 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\Models;
 
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphTo;
 
class Comment extends Model
{
/**
* Получить родительскую модель для комментария (сообщение или видео).
*/
public function commentable(): MorphTo
{
return $this->morphTo();
}
}
 
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphMany;
 
class Post extends Model
{
/**
* Получить все комментарии к сообщению.
*/
public function comments(): MorphMany
{
return $this->morphMany(Comment::class, 'commentable');
}
}
 
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphMany;
 
class Video extends Model
{
/**
* Получить все комментарии к видео.
*/
public function comments(): MorphMany
{
return $this->morphMany(Comment::class, 'commentable');
}
}

Получение Отношения

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

use App\Models\Post;
 
$post = Post::find(1);
 
foreach ($post->comments as $comment) {
// ...
}

Также вы можете получить родительскую модель полиморфной дочерней модели, обращаясь к имени метода, который вызывает morphTo. В данном случае это метод commentable модели Comment. Таким образом, мы получим доступ к этому методу как к динамическому свойству отношения для доступа к родительской модели комментария:

use App\Models\Comment;
 
$comment = Comment::find(1);
 
$commentable = $comment->commentable;

Отношение commentable модели Comment вернет экземпляр либо модели Post, либо Video, в зависимости от типа родительской модели комментария.

Один из Многих (Полиморфные)

Иногда модель может иметь много связанных моделей, и вам нужно легко получить "последнюю" или "первую" связанную модель отношения. Например, модель User может быть связана с многими моделями Image, но вы хотите определить удобный способ взаимодействия с последним изображением, которое пользователь загрузил. Вы можете достичь этого, используя отношение morphOne в сочетании с методами ofMany:

/**
* Получить самое последнее изображение пользователя.
*/
public function latestImage(): MorphOne
{
return $this->morphOne(Image::class, 'imageable')->latestOfMany();
}

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

/**
* Получить самое старое изображение пользователя.
*/
public function oldestImage(): MorphOne
{
return $this->morphOne(Image::class, 'imageable')->oldestOfMany();
}

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

Например, используя метод ofMany, вы можете получить "понравившееся" изображение пользователя. Метод ofMany принимает сортируемый столбец в качестве первого аргумента и функцию агрегации (min или max), которую следует применять при запросе связанной модели, в качестве второго аргумента:

/**
* Получить самое популярное изображение пользователя.
*/
public function bestImage(): MorphOne
{
return $this->morphOne(Image::class, 'imageable')->ofMany('likes', 'max');
}

Примечание Вы можете создавать более сложные отношения "один из многих". Дополнительную информацию можно найти в документации по отношениям "Has One Of Many".

Многие ко Многим (Полиморфные)

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

Отношения многие ко многим с полиморфией немного сложнее, чем отношения "один ко многим" и "многие ко многим". Например, модель 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.

Метод morphToMany принимает имя связанной модели, а также "имя отношения". Исходя из имени, которое мы присвоили нашей промежуточной таблице и ключам в ней, мы будем обращаться к отношению как "taggable":

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphToMany;
 
class Post extends Model
{
/**
* Получить все теги для сообщения.
*/
public function tags(): MorphToMany
{
return $this->morphToMany(Tag::class, 'taggable');
}
}

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

Затем в модели Tag вы должны определить метод для каждой из его возможных родительских моделей. Таким образом, в этом примере мы определим методы posts и videos. Оба эти метода должны возвращать результат метода morphedByMany.

Метод morphedByMany принимает имя связанной модели, а также "имя отношения". Исходя из имени, которое мы присвоили нашей промежуточной таблице и ключам в ней, мы будем обращаться к отношению как "taggable":

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphToMany;
 
class Tag extends Model
{
/**
* Получить все сообщения, к которым привязан этот тег.
*/
public function posts(): MorphToMany
{
return $this->morphedByMany(Post::class, 'taggable');
}
 
/**
* Получить все видео, к которым привязан этот тег.
*/
public function videos(): MorphToMany
{
return $this->morphedByMany(Video::class, 'taggable');
}
}

Получение Отношения

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

use App\Models\Post;
 
$post = Post::find(1);
 
foreach ($post->tags as $tag) {
// ...
}

Вы можете получить родителя полиморфного отношения из дочерней полиморфной модели, обращаясь к имени метода, который вызывает morphedByMany. В данном случае это методы posts или videos модели Tag:

use App\Models\Tag;
 
$tag = Tag::find(1);
 
foreach ($tag->posts as $post) {
// ...
}
 
foreach ($tag->videos as $video) {
// ...
}

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

По умолчанию Laravel будет использовать полное квалифицированное имя класса для сохранения "типа" связанной модели. Например, учитывая приведенный выше пример отношения "один ко многим", где модель Comment может принадлежать модели Post или Video, значение по умолчанию для commentable_type будет соответственно App\Models\Post или App\Models\Video. Однако вы можете захотеть изолировать эти значения от внутренней структуры вашего приложения.

Например, вместо использования имен моделей в качестве "типа", мы можем использовать простые строки, такие как post и video. Таким образом, значения столбца полиморфного "типа" в нашей базе данных останутся допустимыми даже в случае переименования моделей:

use Illuminate\Database\Eloquent\Relations\Relation;
 
Relation::enforceMorphMap([
'post' => 'App\Models\Post',
'video' => 'App\Models\Video',
]);

Вы можете вызвать метод enforceMorphMap в методе boot класса App\Providers\AppServiceProvider или создать отдельный сервис-провайдер, если хотите.

Вы можете определить морфный псевдоним данной модели во время выполнения, используя метод getMorphClass модели. Напротив, вы можете определить полное квалифицированное имя класса, связанное с морфным псевдонимом, используя метод Relation::getMorphedModel:

use Illuminate\Database\Eloquent\Relations\Relation;
 
$alias = $post->getMorphClass();
 
$class = Relation::getMorphedModel($alias);

Внимание При добавлении "морфной карты" в ваше существующее приложение каждое значение столбца *_type, в котором еще содержится полностью определенное имя класса, должно быть преобразовано в его имя "карты".

Динамические Отношения

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

Метод resolveRelationUsing принимает желаемое имя отношения в качестве первого аргумента. Вторым аргументом, переданным методу, должно быть замыкание, которое принимает экземпляр модели и возвращает допустимое определение отношения Eloquent. Обычно динамические отношения следует настраивать в методе boot провайдера услуг:

use App\Models\Order;
use App\Models\Customer;
 
Order::resolveRelationUsing('customer', function (Order $orderModel) {
return $orderModel->belongsTo(Customer::class, 'customer_id');
});

Внимание При определении динамических отношений всегда предоставляйте явные имена ключей методам отношений Eloquent.

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

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

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

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
 
class User extends Model
{
/**
* Получить все сообщения пользователя.
*/
public function posts(): HasMany
{
return $this->hasMany(Post::class);
}
}

Вы можете запрашивать отношение posts и добавлять дополнительные ограничения к отношению, подобно следующему:

use App\Models\User;
 
$user = User::find(1);
 
$user->posts()->where('active', 1)->get();

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

Цепочка orWhere после Отношений

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

$user->posts()
->where('active', 1)
->orWhere('votes', '>=', 100)
->get();

Приведенный выше пример приведет к следующему SQL. Как видите, оператор or указывает запросу вернуть любой пост с более чем 100 голосами. Запрос больше не ограничен конкретным пользователем:

select *
from posts
where user_id = ? and active = 1 or votes >= 100

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

use Illuminate\Database\Eloquent\Builder;
 
$user->posts()
->where(function (Builder $query) {
return $query->where('active', 1)
->orWhere('votes', '>=', 100);
})
->get();

Приведенный выше пример приведет к следующему SQL. Обратите внимание, что логическая группировка правильно сгруппировала ограничения, и запрос остается ограниченным для конкретного пользователя:

select *
from posts
where user_id = ? and (active = 1 or votes >= 100)

Методы Отношений против Динамических Свойств

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

use App\Models\User;
 
$user = User::find(1);
 
foreach ($user->posts as $post) {
// ...
}

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

Запрос на Существование Отношений

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

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

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

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

Вложенные операторы has можно построить с использованием "точечной" записи. Например, вы можете извлечь все сообщения, у которых есть хотя бы один комментарий с хотя бы одним изображением:

// Получить сообщения с хотя бы одним комментарием с изображениями...
$posts = Post::has('comments.images')->get();

Если вам нужна даже большая мощь, вы можете использовать методы whereHas и orWhereHas для определения дополнительных ограничений запроса к ваших запросам has, таких как проверка содержимого комментария:

use Illuminate\Database\Eloquent\Builder;
 
// Получить сообщения с хотя бы одним комментарием, содержащим слова вроде code%...
$posts = Post::whereHas('comments', function (Builder $query) {
$query->where('content', 'like', 'code%');
})->get();
 
// Получить сообщения с хотя бы десятью комментариями, содержащими слова вроде code%...
$posts = Post::whereHas('comments', function (Builder $query) {
$query->where('content', 'like', 'code%');
}, '>=', 10)->get();

Внимание Eloquent в настоящее время не поддерживает запросы на существование отношения между базами данных. Отношения должны существовать в пределах одной и той же базы данных.

Встроенные Запросы на Существование Отношений

Если вы хотите запросить существование отношения с единственным простым условием, прикрепленным к запросу отношения, вам может показаться удобнее использовать методы whereRelation, orWhereRelation, whereMorphRelation и orWhereMorphRelation. Например, мы можем запросить все сообщения, у которых есть неодобренные комментарии:

use App\Models\Post;
 
$posts = Post::whereRelation('comments', 'is_approved', false)->get();

Как и в случае вызовов метода where строителя запросов, вы также можете указать оператор:

$posts = Post::whereRelation(
'comments', 'created_at', '>=', now()->subHour()
)->get();

Запрос на Отсутствие Отношений

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

use App\Models\Post;
 
$posts = Post::doesntHave('comments')->get();

Если вам нужна даже большая мощь, вы можете использовать методы whereDoesntHave и orWhereDoesntHave для добавления дополнительных ограничений запроса к вашим запросам doesntHave, таких как проверка содержимого комментария:

use Illuminate\Database\Eloquent\Builder;
 
$posts = Post::whereDoesntHave('comments', function (Builder $query) {
$query->where('content', 'like', 'code%');
})->get();

Вы можете использовать "точечную" запись для выполнения запроса к вложенному отношению. Например, следующий запрос извлечет все сообщения, не имеющие комментариев; однако сообщения с комментариями от авторов, которые не заблокированы, будут включены в результаты:

use Illuminate\Database\Eloquent\Builder;
 
$posts = Post::whereDoesntHave('comments.author', function (Builder $query) {
$query->where('banned', 0);
})->get();

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

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

use App\Models\Comment;
use App\Models\Post;
use App\Models\Video;
use Illuminate\Database\Eloquent\Builder;
 
// Получить комментарии, связанные с сообщениями или видео с заголовком вроде code%...
$comments = Comment::whereHasMorph(
'commentable',
[Post::class, Video::class],
function (Builder $query) {
$query->where('title', 'like', 'code%');
}
)->get();
 
// Получить комментарии, связанные с сообщениями с заголовком не вроде code%...
$comments = Comment::whereDoesntHaveMorph(
'commentable',
Post::class,
function (Builder $query) {
$query->where('title', 'like', 'code%');
}
)->get();

Иногда вам может потребоваться добавить ограничения запроса на основе "типа" связанной полиморфной модели. Замыкание, переданное методу whereHasMorph, может получить $type значение в качестве второго аргумента. Этот аргумент позволяет вам проверить "тип" запроса, который строится:

use Illuminate\Database\Eloquent\Builder;
 
$comments = Comment::whereHasMorph(
'commentable',
[Post::class, Video::class],
function (Builder $query, string $type) {
$column = $type === Post::class ? 'content' : 'title';
 
$query->where($column, 'like', 'code%');
}
)->get();

Запрос Всех Связанных Моделей

Вместо передачи массива возможных полиморфных моделей вы можете указать * как подстановочное значение. Это указывает Laravel извлечь все возможные полиморфные типы из базы данных. Laravel выполнит дополнительный запрос для выполнения этой операции:

use Illuminate\Database\Eloquent\Builder;
 
$comments = Comment::whereHasMorph('commentable', '*', function (Builder $query) {
$query->where('title', 'like', 'foo%');
})->get();

Агрегация Связанных Моделей

Подсчет Связанных Моделей

Иногда вам может потребоваться подсчитать количество связанных моделей для заданного отношения, фактически не загружая модели. Для этого вы можете использовать метод withCount. Метод withCount разместит атрибут {relation}_count в результирующих моделях:

use App\Models\Post;
 
$posts = Post::withCount('comments')->get();
 
foreach ($posts as $post) {
echo $post->comments_count;
}

Передав массив методу withCount, вы можете добавить "подсчеты" для нескольких отношений, а также добавить дополнительные ограничения к запросам:

use Illuminate\Database\Eloquent\Builder;
 
$posts = Post::withCount(['votes', 'comments' => function (Builder $query) {
$query->where('content', 'like', 'code%');
}])->get();
 
echo $posts[0]->votes_count;
echo $posts[0]->comments_count;

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

use Illuminate\Database\Eloquent\Builder;
 
$posts = Post::withCount([
'comments',
'comments as pending_comments_count' => function (Builder $query) {
$query->where('approved', false);
},
])->get();
 
echo $posts[0]->comments_count;
echo $posts[0]->pending_comments_count;

Отложенная Загрузка Количества

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

$book = Book::first();
 
$book->loadCount('genres');

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

$book->loadCount(['reviews' => function (Builder $query) {
$query->where('rating', 5);
}])

Подсчет Количества и Пользовательские Выражения Выборки

Если вы объединяете withCount с оператором select, убедитесь, что вызываете withCount после метода select:

$posts = Post::select(['title', 'body'])
->withCount('comments')
->get();

Другие Агрегатные Функции

Помимо метода withCount, Eloquent предоставляет методы withMin, withMax, withAvg, withSum и withExists. Эти методы разместят атрибут {relation}_{function}_{column} в ваших результирующих моделях:

use App\Models\Post;
 
$posts = Post::withSum('comments', 'votes')->get();
 
foreach ($posts as $post) {
echo $post->comments_sum_votes;
}

Если вы хотите получить доступ к результату агрегатной функции с использованием другого имени, вы можете указать свой собственный псевдоним:

$posts = Post::withSum('comments as total_comments', 'votes')->get();
 
foreach ($posts as $post) {
echo $post->total_comments;
}

Как и метод loadCount, также доступны отложенные версии этих методов. Эти дополнительные агрегатные операции могут выполняться над Eloquent-моделями, которые уже были извлечены:

$post = Post::first();
 
$post->loadSum('comments', 'votes');

Если вы объединяете эти методы агрегации с оператором select, убедитесь, что вызываете методы агрегации после метода select:

$posts = Post::select(['title', 'body'])
->withExists('comments')
->get();

Подсчет Связанных Моделей для Отношений "morphTo"

Если вы хотите предварительно загрузить отношение "морф к", а также количество связанных моделей для различных сущностей, которые могут быть возвращены этим отношением, вы можете использовать метод with в сочетании с методом morphWithCount отношения morphTo.

Давайте предположим, что модели Photo и Post могут создавать модели ActivityFeed. Предположим, что модель ActivityFeed определяет отношение "морф к" с именем parentable, которое позволяет нам получить родительскую модель Photo или Post для заданного экземпляра ActivityFeed. Кроме того, предположим, что модели Photo "имеют много" моделей Tag, а модели Post "имеют много" моделей Comment.

Теперь представим, что мы хотим извлечь экземпляры ActivityFeed и предварительно загрузить родительские модели parentable для каждого экземпляра ActivityFeed. Кроме того, мы хотим получить количество тегов, связанных с каждой родительской фотографией, и количество комментариев, связанных с каждым родительским сообщением:

use Illuminate\Database\Eloquent\Relations\MorphTo;
 
$activities = ActivityFeed::with([
'parentable' => function (MorphTo $morphTo) {
$morphTo->morphWithCount([
Photo::class => ['tags'],
Post::class => ['comments'],
]);
}])->get();

Отложенная Загрузка Количества

Предположим, что мы уже извлекли набор моделей ActivityFeed, и теперь мы хотим загрузить количество вложенных отношений для различных моделей parentable, связанных с активностью. Вы можете использовать метод loadMorphCount, чтобы выполнить это:

$activities = ActivityFeed::with('parentable')->get();
 
$activities->loadMorphCount('parentable', [
Photo::class => ['tags'],
Post::class => ['comments'],
]);

Предварительная Загрузка

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

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
 
class Book extends Model
{
/**
* Получить автора, написавшего книгу.
*/
public function author(): BelongsTo
{
return $this->belongsTo(Author::class);
}
}

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

use App\Models\Book;
 
$books = Book::all();
 
foreach ($books as $book) {
echo $book->author->name;
}

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

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

$books = 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 = Book::with(['author', 'publisher'])->get();

Вложенная Предварительная Загрузка

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

$books = Book::with('author.contacts')->get();

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

$books = Book::with([
'author' => [
'contacts',
'publisher',
],
])->get();

Вложенная Предварительная Загрузка для Отношений morphTo

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

<?php
 
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphTo;
 
class ActivityFeed extends Model
{
/**
* Получить родителя записи в ленте активности.
*/
public function parentable(): MorphTo
{
return $this->morphTo();
}
}

В этом примере давайте предположим, что модели Event, Photo и Post могут создавать модели ActivityFeed. Кроме того, предположим, что модели Event принадлежат модели Calendar, модели Photo ассоциированы с моделями Tag, а модели Post принадлежат модели Author.

Используя эти определения моделей и отношения, мы можем извлечь экземпляры моделей ActivityFeed и жадно загрузить все модели parentable и их соответствующие вложенные отношения:

use Illuminate\Database\Eloquent\Relations\MorphTo;
 
$activities = ActivityFeed::query()
->with(['parentable' => function (MorphTo $morphTo) {
$morphTo->morphWith([
Event::class => ['calendar'],
Photo::class => ['tags'],
Post::class => ['author'],
]);
}])->get();

Предварительная Загрузка Конкретных Колонок

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

$books = Book::with('author:id,name,book_id')->get();

Внимание Метки времени родительской модели будут обновлены только в том случае, если дочерняя модель обновляется с использованием метода save Eloquent.

Предварительная Загрузка По Умолчанию

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

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
 
class Book extends Model
{
/**
* Отношения, которые всегда должны быть загружены.
*
* @var array
*/
protected $with = ['author'];
 
/**
* Получить автора, написавшего книгу.
*/
public function author(): BelongsTo
{
return $this->belongsTo(Author::class);
}
 
/**
* Получить жанр книги.
*/
public function genre(): BelongsTo
{
return $this->belongsTo(Genre::class);
}
}

Если вы хотите удалить элемент из свойства $with для одного запроса, вы можете использовать метод without:

$books = Book::without('author')->get();

Если вы хотите переопределить все элементы в свойстве $with для одного запроса, вы можете использовать метод withOnly:

$books = Book::withOnly('genre')->get();

Ограничение Предварительной Загрузки

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

use App\Models\User;
use Illuminate\Contracts\Database\Eloquent\Builder;
 
$users = User::with(['posts' => function (Builder $query) {
$query->where('title', 'like', '%code%');
}])->get();

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

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

Внимание Методы построителя запросов limit и take не могут быть использованы при ограничении жадной загрузки.

Ограничение Предварительной Загрузки Отношений morphTo

Если вы жадно загружаете отношение morphTo, Eloquent выполнит несколько запросов для извлечения каждого типа связанной модели. Вы можете добавить дополнительные ограничения к каждому из этих запросов с использованием метода constrain отношения MorphTo:

use Illuminate\Database\Eloquent\Relations\MorphTo;
 
$comments = Comment::with(['commentable' => function (MorphTo $morphTo) {
$morphTo->constrain([
Post::class => function ($query) {
$query->whereNull('hidden_at');
},
Video::class => function ($query) {
$query->where('type', 'educational');
},
]);
}])->get();

В этом примере Eloquent будет жадно загружать только те сообщения, которые не были скрыты, и видео, у которых значение type равно "educational".

Ограничение Предварительных Загрузок По Существованию Отношений

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

use App\Models\User;
 
$users = User::withWhereHas('posts', function ($query) {
$query->where('featured', true);
})->get();

Ленивая Предварительная Загрузка

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

use App\Models\Book;
 
$books = Book::all();
 
if ($someCondition) {
$books->load('author', 'publisher');
}

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

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

Чтобы загрузить отношение только в том случае, если оно еще не было загружено, используйте метод loadMissing:

$book->loadMissing('author');

Вложенная Ленивая Предварительная Загрузка & morphTo

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

Этот метод принимает имя отношения morphTo в качестве первого аргумента и массив пар модель / отношение в качестве второго аргумента. Чтобы проиллюстрировать этот метод, давайте рассмотрим следующую модель:

<?php
 
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphTo;
 
class ActivityFeed extends Model
{
/**
* Получить родителя записи в ленте активности.
*/
public function parentable(): MorphTo
{
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'],
]);

Предотвращение Ленивой Загрузки

Как было обсуждено ранее, жадная загрузка отношений часто может обеспечить значительные преимущества в производительности вашего приложения. Поэтому, если вы хотите, вы можете научить Laravel всегда предотвращать ленивую загрузку отношений. Для этого вы можете вызвать метод preventLazyLoading, предоставляемый базовым классом модели Eloquent. Обычно вы должны вызвать этот метод внутри метода boot класса AppServiceProvider вашего приложения.

Метод preventLazyLoading принимает необязательный аргумент типа boolean, указывающий, следует ли предотвращать ленивую загрузку. Например, вы можете хотеть отключить ленивую загрузку только в средах, отличных от производственной, чтобы ваше производственное окружение продолжало работать нормально, даже если лениво загруженное отношение случайно присутствует в коде для продакшена:

use Illuminate\Database\Eloquent\Model;
 
/**
* Запустить все службы приложения.
*/
public function boot(): void
{
Model::preventLazyLoading(! $this->app->isProduction());
}

После предотвращения ленивой загрузки Eloquent будет выбрасывать исключение Illuminate\Database\LazyLoadingViolationException, когда ваше приложение попытается лениво загрузить любое отношение Eloquent.

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

Model::handleLazyLoadingViolationUsing(function (Model $model, string $relation) {
$class = $model::class;
 
info("Attempted to lazy load [{$relation}] on model [{$class}].");
});

Вставка и Обновление Связанных Моделей

Метод save

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

use App\Models\Comment;
use App\Models\Post;
 
$comment = new Comment(['message' => 'A new comment.']);
 
$post = Post::find(1);
 
$post->comments()->save($comment);

Обратите внимание, что мы не обращались к отношению comments как к динамическому свойству. Вместо этого мы вызвали метод comments, чтобы получить экземпляр отношения. Метод save автоматически добавит соответствующее значение post_id для новой модели Comment.

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

$post = Post::find(1);
 
$post->comments()->saveMany([
new Comment(['message' => 'A new comment.']),
new Comment(['message' => 'Another new comment.']),
]);

Методы save и saveMany сохранят предоставленные экземпляры модели, но не добавят вновь сохраненные модели в какие-либо загруженные в память отношения, которые уже присутствуют в родительской модели. Если вы планируете обращаться к отношению после использования методов save или saveMany, вам, возможно, следует использовать метод refresh для перезагрузки модели и ее отношений:

$post->comments()->save($comment);
 
$post->refresh();
 
// Все комментарии, включая только что сохраненный комментарий...
$post->comments;

Рекурсивное Сохранение Моделей и Отношений

Если вы хотите save вашу модель и все ее связанные отношения, вы можете использовать метод push. В этом примере модель Post будет сохранена, а также ее комментарии и авторы комментариев:

$post = Post::find(1);
 
$post->comments[0]->message = 'Message';
$post->comments[0]->author->name = 'Author Name';
 
$post->push();

Метод pushQuietly можно использовать для сохранения модели и ее связанных отношений без возбуждения событий:

$post->pushQuietly();

Метод create

Помимо методов save и saveMany, вы также можете использовать метод create, который принимает массив атрибутов, создает модель и вставляет ее в базу данных. Разница между save и create заключается в том, что save принимает полноценный экземпляр модели Eloquent, в то время как create принимает простой массив PHP. Вновь созданная модель будет возвращена методом create:

use App\Models\Post;
 
$post = Post::find(1);
 
$comment = $post->comments()->create([
'message' => 'A new comment.',
]);

Вы можете использовать метод createMany для создания нескольких связанных моделей:

$post = Post::find(1);
 
$post->comments()->createMany([
['message' => 'A new comment.'],
['message' => 'Another new comment.'],
]);

Методы createQuietly и createManyQuietly могут использоваться для создания модели(ей) без отправки каких-либо событий:

$user = User::find(1);
 
$user->posts()->createQuietly([
'title' => 'Post title.',
]);
 
$user->posts()->createManyQuietly([
['title' => 'First post.'],
['title' => 'Second post.'],
]);

Вы также можете использовать методы findOrNew, firstOrNew, firstOrCreate и updateOrCreate для создания и обновления моделей в отношениях.

Примечание Перед использованием метода create, убедитесь, что вы прочитали документацию по массовому присваиванию.

Отношения "Принадлежит"

Если вы хотите присвоить дочернюю модель новой родительской модели, вы можете использовать метод associate. В этом примере модель User определяет отношение belongsTo к модели Account. Этот метод associate установит внешний ключ на дочерней модели:

use App\Models\Account;
 
$account = Account::find(10);
 
$user->account()->associate($account);
 
$user->save();

Чтобы удалить родительскую модель из дочерней модели, вы можете использовать метод dissociate. Этот метод установит внешний ключ отношения в значение null:

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

Отношения Многие ко Многим

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

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

use App\Models\User;
 
$user = User::find(1);
 
$user->roles()->attach($roleId);

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

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

Иногда может быть необходимо удалить роль у пользователя. Для удаления записи отношения многие ко многим используйте метод detach. Метод detach удалит соответствующую запись из промежуточной таблицы; однако обе модели останутся в базе данных:

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

Для удобства attach и detach также принимают массивы идентификаторов в качестве входных данных:

$user = 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]);

Если вы хотите вставить те же значения промежуточной таблицы с каждым из синхронизированных идентификаторов модели, вы можете использовать метод syncWithPivotValues:

$user->roles()->syncWithPivotValues([1, 2, 3], ['active' => true]);

Если вы не хотите отсоединять существующие идентификаторы, отсутствующие в данном массиве, вы можете использовать метод syncWithoutDetaching:

$user->roles()->syncWithoutDetaching([1, 2, 3]);

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

Отношение многие ко многим также предоставляет метод toggle, который "переключает" состояние присоединения заданных идентификаторов связанной модели. Если заданный идентификатор в настоящее время присоединен, он будет отсоединен. Точно так же, если он в настоящее время отсоединен, он будет присоединен:

$user->roles()->toggle([1, 2, 3]);

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

$user->roles()->toggle([
1 => ['expires' => true],
2 => ['expires' => true],
]);

Обновление Записи в Промежуточной Таблице

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

$user = User::find(1);
 
$user->roles()->updateExistingPivot($roleId, [
'active' => false,
]);

Обновление Временных Меток Родительских Элементов

Когда модель определяет отношение belongsTo или belongsToMany к другой модели, например, как Comment, которое принадлежит Post, иногда полезно обновить временную метку родителя при обновлении дочерней модели.

Например, при обновлении модели Comment вы можете автоматически "каснуться" временной метки updated_at владеющего Post, чтобы установить ее в текущую дату и время. Для достижения этой цели вы можете добавить свойство touches к вашей дочерней модели, содержащее имена отношений, время обновления которых должно быть обновлено при обновлении дочерней модели:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
 
class Comment extends Model
{
/**
* Все отношения, которые нужно обязательно касаться.
*
* @var array
*/
protected $touches = ['post'];
 
/**
* Получить сообщение, к которому относится комментарий.
*/
public function post(): BelongsTo
{
return $this->belongsTo(Post::class);
}
}

Внимание Временные метки родительской модели будут обновлены только в том случае, если дочерняя модель обновляется с использованием метода save в Eloquent.