Сущности предметной области существуют не сами по себе. Они часто зависят друг от друга. На уровне базы данных такие связи задаются через внешние ключи или даже промежуточные таблицы, как в случае связи "многие ко многим". ORM, в свою очередь, используют эти ключи, для работы со связями. Добавляет множество полезных методов, которые упрощают работу с зависимыми сущностями: выборкой, добавлением, модификацией и удалением.
Так как в этом курсе мы делаем блог, нам понадобится сущность Post. Пользователи связаны с постами "один ко многим":
- Один пользователь может быть автором многих постов
- У одного поста всегда один автор
Структура
Для поддержки такой связи при создании таблицы постов, нужно добавить внешний ключ на таблицу пользователей:
<?php
Capsule::schema()->create('posts', function ($table) {
$table->id();
$table->string('state')->nullable();
$table->string('title');
$table->text('body');
// Поле которое будет внешним ключом
$table->bigInteger('creator_id');
// Добавление внешнего ключа (ограничения)
$table->foreign('creator_id')->references('id')->on('users');
$table->timestamps();
});
По умолчанию Eloquent не воспринимает внешние ключи как что-то особенное. Она требует (как и большинство ORM) явного указания связи на уровне моделей. Для этого в каждой из моделей определяется специальный метод, через который будет происходить всё взаимодействие между связанными сущностями. Имя этого метода произвольно и выбирается так, чтобы лучше отражать суть связи: У поста есть автор, у каждого автора есть посты. Каждый такой метод должен вернуть вызов другого метода, отвечающего за связь. В примере ниже это belongsTo
и hasMany
.
<?php
// Post.php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
public function creator()
{
// Принадлежит пользователю
// belongsTo определяется у модели содержащей внешний ключ
return $this->belongsTo('App\Models\User');
}
}
Вторым параметром метод belongsTo
ожидает имя внешнего ключа, по которому строится связь: $this->belongsTo('App\Models\User', 'creator_id')
. Имя ключа можно не указывать, потому что Eloquent может сформировать его автоматически. В этом случае имя ключа сформируется по шаблону имя метода_id
.
Так происходит потому, что в Eloquent принято соглашение в именовании сущностей и методов. Подробнее об этом можно почитать в документации.
<?php
// User.php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
// Во множественном числе потому что это коллекция
public function posts()
{
// У каждого пользователя много постов
// hasMany определяется у модели, имеющей внешние ключи в других таблицах
return $this->hasMany('App\Models\Post', 'creator_id');
}
}
Метод hasMany
также поддерживает соглашение для определения имени внешнего ключа. Только здесь оно определяется не по имени метода, а по имени модели, в которой описывается связь. Для модели User это будет user_id. В нашем случае такая логика не работает, поэтому имя свойства указано явно.
CRUD
Теперь Eloquent знает о связях и даёт работать с ними напрямую:
<?php
$user = App\Models\User::find(1);
// SELECT * FROM posts WHERE creator_id = 1
foreach($user->posts as $post) {
echo $post;
}
Обращение к коллекции зависимых сущностей возвращает специальный объект, который может использоваться как массив.
Есть и другой способ взаимодействовать с зависимостями. Вызов постов как метода, позволяет управлять этой коллекцией, например удалить или добавить новый пост:
<?php
// В пост автоматически устанавливается пользователь
$post = $user->posts()->make(); // Параметры поста можно передать в make
$post->title = 'title';
$post->body = 'body';
$post->save();
$user->posts; // [['id' => 1, 'title' => 'title', 'body' => 'body', 'creator_id' => 1, ...]]
// В WHERE добавится creator_id = <идентификатор пользователя>
$post2 = $user->posts()->find(1);
$post->is($post2); // true
// Удаление всех постов одним запросом
$user->posts()->delete();
То же самое происходит и с другой стороны связи:
<?php
$post = App\Models\Post::find(1);
$post->creator->first_name;
// Установка пользователя
$post->creator()->associate($user);
$post->save();
При работе со связями важно переключиться от мышления через таблицы и ключи к сущностям и связям (почему). Технически это значит, что код опирается на сами сущности, а не их идентификаторы:
<?php
// Плохо
$post->user_id = $user->id;
// Хорошо
$post->creator()->associate($user);
Выборки
Все типы связей в Eloquent поддерживают построение запросов на выборку:
<?php
// В запрос будет включено условие по creator_id равным текущему пользователю
// SELECT * FROM posts WHERE creator_id = 1 AND state = 'active'
$user->posts()->where('state', 'active')->get();
Самостоятельная работа
- Заполните
$fillable
у Post. - Изучите связи внутри Post.
- Откройте REPL и создайте несколько постов.
Дополнительные материалы
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
- Статья «Как учиться и справляться с негативными мыслями»
- Статья «Ловушки обучения»
- Статья «Сложные простые задачи по программированию»
- Вебинар «Как самостоятельно учиться»
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.