Документация Laravel 10.x
Здесь ты найдешь сниппеты по 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();
Иногда модель может иметь много связанных моделей, но вы хотите легко получить "последнюю" или "старшую" связанную модель отношения. Например, модель 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" определяет однозначное отношение к другой модели. Однако это отношение указывает, что объявляющая модель может быть сопоставлена с одним экземпляром другой модели, пройдя через третью модель.
Например, в приложении автомастерской каждая модель 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.
Если вы определили отношение многие ко многим, которое использует пользовательскую модель промежуточной таблицы, и эта модель промежуточной таблицы имеет автоинкрементируемый первичный ключ, убедитесь, что ваша пользовательская модель промежуточной таблицы определяет свойство 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 postswhere 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 postswhere 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();
Если вы хотите предварительно загрузить отношение "морф к", а также количество связанных моделей для различных сущностей, которые могут быть возвращены этим отношением, вы можете использовать метод 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.