Вступление

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

  • Один к одному
  • Один ко многим
  • Много ко многим
  • Имеет один сквозной
  • Имеет много через
  • Один в Один (Полиморфный)
  • Один ко многим (полиморфный)
  • Много ко многим (полиморфный)

 

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

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

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

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

 

Один к одному

Отношения один-к-одному - это очень основное отношение. Например, Userмодель может быть связана с ней Phone. Чтобы определить это отношение, мы помещаем phoneметод в Userмодель. phoneМетод должен вызвать hasOneметод и возвращает результат:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * Get the phone record associated with the user.
     */
    public function phone()
    {
        return $this->hasOne('App\Phone');
    }
}

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

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

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

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

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

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

Определение обратной связи

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

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Phone extends Model
{
    /**
     * Get the user that owns the phone.
     */
    public function user()
    {
        return $this->belongsTo('App\User');
    }
}

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

/**
 * Get the user that owns the phone.
 */
public function user()
{
    return $this->belongsTo('App\User', 'foreign_key');
}

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

/**
 * Get the user that owns the phone.
 */
public function user()
{
    return $this->belongsTo('App\User', 'foreign_key', 'other_key');
}

 

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

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

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    /**
     * Get the comments for the blog post.
     */
    public function comments()
    {
        return $this->hasMany('App\Comment');
    }
}

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

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

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

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

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

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

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

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

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

 

Один ко многим (обратный)

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

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Comment extends Model
{
    /**
     * Get the post that owns the comment.
     */
    public function post()
    {
        return $this->belongsTo('App\Post');
    }
}

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

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

echo $comment->post->title;

В приведенном выше примере, Красноречивый будет пытаться соответствовать post_idот Commentмодели к модели idна Postмодели. Eloquent определяет имя внешнего ключа по умолчанию, проверяя имя метода взаимосвязи и добавляя суффикс имени метода с _именем столбца первичного ключа. Однако, если внешний ключ в Commentмодели отсутствует post_id, вы можете передать имя пользовательского ключа в качестве второго аргумента belongsToметода:

/**
 * Get the post that owns the comment.
 */
public function post()
{
    return $this->belongsTo('App\Post', 'foreign_key');
}

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

/**
 * Get the post that owns the comment.
 */
public function post()
{
    return $this->belongsTo('App\Post', 'foreign_key', 'other_key');
}

 

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

Многие-ко-многим отношения немного сложнее , чем hasOneи hasManyотношения. Примером такого отношения является пользователь со многими ролями, где роли также разделяются другими пользователями. Например, многие пользователи могут иметь роль «Администратор». Для определения этого отношения, необходимы три таблицы базы данных: usersroles, и role_userrole_userТаблица выводится из алфавитного порядка соответствующих названий модели, и содержит user_idи role_idстолбцы.

Отношения «многие ко многим» определяются путем написания метода, который возвращает результат belongsToManyметода. Например, давайте определим rolesметод для нашей Userмодели:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * The roles that belong to the user.
     */
    public function roles()
    {
        return $this->belongsToMany('App\Role');
    }
}

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

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

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

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

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

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

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

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

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

Определение обратной связи

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

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Role extends Model
{
    /**
     * The users that belong to the role.
     */
    public function users()
    {
        return $this->belongsToMany('App\User');
    }
}

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

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

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

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

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

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

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

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

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

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

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

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

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

return $this->belongsToMany('App\Podcast')
                ->as('subscription')
                ->withTimestamps();

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

$users = User::with('podcasts')->get();

foreach ($users->flatMap->podcasts as $podcast) {
    echo $podcast->subscription->created_at;
}

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

Вы также можете отфильтровать результаты , возвращаемые с belongsToManyпомощью wherePivotи wherePivotInметодов при определении отношений:

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

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

 

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

Если вы хотите определить пользовательскую модель для представления промежуточной таблицы ваших отношений, вы можете вызвать usingметод при определении отношений. Настраиваемые модели множества-ко-многим должны расширять класс, тогда как настраиваемые полиморфные модели множества-ко-многим должны расширять класс. Например, мы можем определить, который использует пользовательскую модель разворота:Illuminate\Database\Eloquent\Relations\PivotIlluminate\Database\Eloquent\Relations\MorphPivotRoleRoleUser

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Role extends Model
{
    /**
     * The users that belong to the role.
     */
    public function users()
    {
        return $this->belongsToMany('App\User')->using('App\RoleUser');
    }
}

При определении RoleUserмодели мы расширим Pivotкласс:

<?php

namespace App;

use Illuminate\Database\Eloquent\Relations\Pivot;

class RoleUser extends Pivot
{
    //
}

Вы можете объединить usingи withPivotдля того, чтобы извлечь столбцы из промежуточной таблицы. Например, вы можете извлечь created_byи updated_byстолбцы из RoleUserсводной таблицы, передавая имена столбцов в withPivotметоде:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Role extends Model
{
    /**
     * The users that belong to the role.
     */
    public function users()
    {
        return $this->belongsToMany('App\User')
                        ->using('App\RoleUser')
                        ->withPivot([
                            'created_by',
                            'updated_by'
                        ]);
    }
}

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

Пользовательские модели Pivot и увеличивающиеся идентификаторы

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

/**
 * Indicates if the IDs are auto-incrementing.
 *
 * @var bool
 */
public $incrementing = true;

 

Имеет один сквозной

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

users
    id - integer
    supplier_id - integer

suppliers
    id - integer

history
    id - integer
    user_id - integer

Хотя historyтаблица не содержит supplier_idстолбца, hasOneThroughотношение может предоставить доступ к истории пользователя к модели поставщика. Теперь, когда мы проверили структуру таблицы для отношения, давайте определим ее на Supplierмодели:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Supplier extends Model
{
    /**
     * Get the user's history.
     */
    public function userHistory()
    {
        return $this->hasOneThrough('App\History', 'App\User');
    }
}

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

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

class Supplier extends Model
{
    /**
     * Get the user's history.
     */
    public function userHistory()
    {
        return $this->hasOneThrough(
            'App\History',
            'App\User',
            'supplier_id', // Foreign key on users table...
            'user_id', // Foreign key on history table...
            'id', // Local key on suppliers table...
            'id' // Local key on users table...
        );
    }
}

 

Имеет много через

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

countries
    id - integer
    name - string

users
    id - integer
    country_id - integer
    name - string

posts
    id - integer
    user_id - integer
    title - string

Хотя postsи не содержит country_idстолбец, hasManyThroughотношение обеспечивает доступ к сообщениям страны через . Чтобы выполнить этот запрос, Eloquent проверяет промежуточную таблицу. После нахождения соответствующих идентификаторов пользователей они используются для запроса таблицы.$country->postscountry_idusersposts

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

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Country extends Model
{
    /**
     * Get all of the posts for the country.
     */
    public function posts()
    {
        return $this->hasManyThrough('App\Post', 'App\User');
    }
}

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

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

class Country extends Model
{
    public function posts()
    {
        return $this->hasManyThrough(
            'App\Post',
            'App\User',
            'country_id', // Foreign key on users table...
            'user_id', // Foreign key on posts table...
            'id', // Local key on countries table...
            'id' // Local key on users table...
        );
    }
}

 

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

Полиморфная связь позволяет целевой модели принадлежать более чем одному типу модели с использованием одной ассоциации.

Один в Один (Полиморфный)

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

Полиморфное отношение один к одному аналогично простому отношению один к одному; однако целевая модель может принадлежать более чем одному типу модели в одной ассоциации. Например, блог Postи a Userмогут иметь полиморфное отношение к Imageмодели. Использование полиморфных отношений «один к одному» позволяет получить единый список уникальных изображений, которые используются как для сообщений в блоге, так и для учетных записей пользователей. Сначала давайте рассмотрим структуру таблицы:

posts
    id - integer
    name - string

users
    id - integer
    name - string

images
    id - integer
    url - string
    imageable_id - integer
    imageable_type - string

Примите во внимание imageable_idи imageable_typeстолбцы на imagesстоле. imageable_idСтолбец будет содержать значение ID поста или пользователя, в то время как imageable_typeколонок будет содержать имя класса родительской модели. imageable_typeКолонка используется красноречив , чтобы определить , какой «тип» родительской модели для возврата при обращении к imageableсоотношению.

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

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

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Image extends Model
{
    /**
     * Get all of the owning imageable models.
     */
    public function imageable()
    {
        return $this->morphTo();
    }
}

class Post extends Model
{
    /**
     * Get the post's image.
     */
    public function image()
    {
        return $this->morphOne('App\Image', 'imageable');
    }
}

class User extends Model
{
    /**
     * Get the user's image.
     */
    public function image()
    {
        return $this->morphOne('App\Image', 'imageable');
    }
}

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

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

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

$image = $post->image;

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

$image = App\Image::find(1);

$imageable = $image->imageable;

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

 

Один ко многим (полиморфный)

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

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

posts
    id - integer
    title - string
    body - text

videos
    id - integer
    title - string
    url - string

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

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

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

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Comment extends Model
{
    /**
     * Get all of the owning commentable models.
     */
    public function commentable()
    {
        return $this->morphTo();
    }
}

class Post extends Model
{
    /**
     * Get all of the post's comments.
     */
    public function comments()
    {
        return $this->morphMany('App\Comment', 'commentable');
    }
}

class Video extends Model
{
    /**
     * Get all of the video's comments.
     */
    public function comments()
    {
        return $this->morphMany('App\Comment', 'commentable');
    }
}

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

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

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

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

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

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

$commentable = $comment->commentable;

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

 

Много ко многим (полиморфный)

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

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

posts
    id - integer
    name - string

videos
    id - integer
    name - string

tags
    id - integer
    name - string

taggables
    tag_id - integer
    taggable_id - integer
    taggable_type - string

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

Далее мы готовы определить отношения на модели. Модели Postи Videoбудут иметь tagsметод, который вызывает morphToManyметод базового класса Eloquent:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    /**
     * Get all of the tags for the post.
     */
    public function tags()
    {
        return $this->morphToMany('App\Tag', 'taggable');
    }
}

Определение обратной связи

Затем в Tagмодели вы должны определить метод для каждой из связанных с ней моделей. Итак, для этого примера мы определим postsметод и videosметод:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Tag extends Model
{
    /**
     * Get all of the posts that are assigned this tag.
     */
    public function posts()
    {
        return $this->morphedByMany('App\Post', 'taggable');
    }

    /**
     * Get all of the videos that are assigned this tag.
     */
    public function videos()
    {
        return $this->morphedByMany('App\Video', 'taggable');
    }
}

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

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

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

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

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

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

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

 

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

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

use Illuminate\Database\Eloquent\Relations\Relation;

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

Вы можете зарегистрироваться morphMapв bootфункции вашего AppServiceProviderили создать отдельного поставщика услуг, если хотите.

 

Опрос отношений

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

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

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * Get all of the posts for the user.
     */
    public function posts()
    {
        return $this->hasMany('App\Post');
    }
}

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

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

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

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

Цепные предложения orWhereпосле отношений

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

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

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

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

$user->posts()
        ->where(function ($query) {
            return $query->where('active', 1)
                         ->orWhere('votes', '>=', 100);
        })
        ->get();

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

 

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

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

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

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

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

 

Запрос наличия отношений

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

// Retrieve all posts that have at least one comment...
$posts = App\Post::has('comments')->get();

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

// Retrieve all posts that have three or more comments...
$posts = App\Post::has('comments', '>=', 3)->get();

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

// Retrieve posts that have at least one comment with votes...
$posts = App\Post::has('comments.votes')->get();

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

use Illuminate\Database\Eloquent\Builder;

// Retrieve posts with at least one comment containing words like foo%...
$posts = App\Post::whereHas('comments', function ($query) {
    $query->where('content', 'like', 'foo%');
})->get();

// Retrieve posts with at least ten comments containing words like foo%...
$posts = App\Post::whereHas('comments', function ($query) {
    $query->where('content', 'like', 'foo%');
}, '>=', 10)->get();

 

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

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

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

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

use Illuminate\Database\Eloquent\Builder;

$posts = App\Post::whereDoesntHave('comments', function (Builder $query) {
    $query->where('content', 'like', 'foo%');
})->get();

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

use Illuminate\Database\Eloquent\Builder;

$posts = App\Post::whereDoesntHave('comments.author', function (Builder $query) {
    $query->where('banned', 1);
})->get();

 

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

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

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

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

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

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

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

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

$posts = App\Post::withCount([
    'comments',
    'comments as pending_comments_count' => function ($query) {
        $query->where('approved', false);
    }
])->get();

echo $posts[0]->comments_count;

echo $posts[0]->pending_comments_count;

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

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

echo $posts[0]->title;
echo $posts[0]->body;
echo $posts[0]->comments_count;

 

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

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

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Book extends Model
{
    /**
     * Get the author that wrote the book.
     */
    public function author()
    {
        return $this->belongsTo('App\Author');
    }
}

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

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

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

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

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

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

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

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

select * from books

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

Стремление загружать несколько отношений

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

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

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

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

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

Стремление загружать определенные столбцы

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

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

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

Eager Загрузка по умолчанию

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

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Book extends Model
{
    /**
     * The relationships that should always be loaded.
     *
     * @var array
     */
    protected $with = ['author'];

    /**
     * Get the author that wrote the book.
     */
    public function author()
    {
        return $this->belongsTo('App\Author');
    }
}

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

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

 

Сдерживая энергичные нагрузки

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

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

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

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

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

 

Lazy Eager Загрузка

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

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

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

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

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

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

public function format(Book $book)
{
    $book->loadMissing('author');

    return [
        'name' => $book->name,
        'author' => $book->author->name
    ];
}

Вложенный Ленивый Стремительный Загрузка & morphTo

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

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

<?php

use Illuminate\Database\Eloquent\Model;

class ActivityFeed extends Model
{
    /**
     * Get the parent of the activity feed record.
     */
    public function parentable()
    {
        return $this->morphTo();
    }
}

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

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

$activities = ActivityFeed::with('parentable')
    ->get()
    ->loadMorph('parentable', [
        Event::class => ['calendar'],
        Photo::class => ['tags'],
        Post::class => ['author'],
    ]);

 

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

Метод сохранения

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

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

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

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

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

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

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

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

 

Рекурсивно сохраняющие модели и отношения

Если вам нужна saveваша модель и все связанные с ней отношения, вы можете использовать pushметод:

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

$post->comments[0]->message = 'Message';
$post->comments[0]->author->name = 'Author Name';

$post->push();

 

Метод создания

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

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

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

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

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

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

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

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

 

Принадлежит к отношениям

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

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

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

$user->save();

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

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

$user->save();

 

Модели по умолчанию

В belongsTohasOnehasOneThroughи morphOneотношения позволяют определить модель по умолчанию , который будет возвращен , если данная взаимосвязь null. Этот шаблон часто называется шаблоном Null Object и может помочь удалить условные проверки в вашем коде. В следующем примере userотношение вернет пустую модель, если к сообщению не прикреплено no :App\Useruser

/**
 * Get the author of the post.
 */
public function user()
{
    return $this->belongsTo('App\User')->withDefault();
}

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

/**
 * Get the author of the post.
 */
public function user()
{
    return $this->belongsTo('App\User')->withDefault([
        'name' => 'Guest Author',
    ]);
}

/**
 * Get the author of the post.
 */
public function user()
{
    return $this->belongsTo('App\User')->withDefault(function ($user) {
        $user->name = 'Guest Author';
    });
}

 

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

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

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

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

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

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

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

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

// Detach a single role from the user...
$user->roles()->detach($roleId);

// Detach all roles from the user...
$user->roles()->detach();

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

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

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

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

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

Вы также можете использовать этот syncметод для построения ассоциаций «многие ко многим». syncМетод принимает массив идентификаторов для размещения на промежуточной таблице. Любые идентификаторы, которых нет в данном массиве, будут удалены из промежуточной таблицы. Таким образом, после завершения этой операции в промежуточной таблице будут существовать только идентификаторы в данном массиве:

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

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

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

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

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

Тоглинг Ассоциации

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

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

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

При работе с отношением «многие ко многим» saveметод принимает массив дополнительных атрибутов промежуточной таблицы в качестве второго аргумента:

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

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

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

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

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

 

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

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

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Comment extends Model
{
    /**
     * All of the relationships to be touched.
     *
     * @var array
     */
    protected $touches = ['post'];

    /**
     * Get the post that the comment belongs to.
     */
    public function post()
    {
        return $this->belongsTo('App\Post');
    }
}

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

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

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

$comment->save();