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

Так как в этом курсе мы делаем блог, нам понадобится сущность Post. Пользователи связаны с постами "один ко многим":

  • Один пользователь может быть автором многих постов
  • У одного поста всегда один автор

Структура

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

<?php

Capsule::schema()->create('posts', function ($table) {
    $table->bigIncrements('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;

use \Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    public function creator()
    {
        // Принадлежит пользователю
        // belongsTo определяется у модели содержащей внешний ключ
        return $this->belongsTo('App\User');
    }
}

Вторым параметром метод belongsTo ожидает имя внешнего ключа, по которой строится связь: $this->belongsTo('App\User', 'creator_id'). Имя ключа можно (и желательно) не указывать. В таком случае, Eloquent определяет его самостоятельно, используя имя метода связи и добавляя к нему суффикс _id.

<?php

// User.php
namespace App;

use \Illuminate\Database\Eloquent\Model;

class User extends Model
{
    // Во множественном числе потому что это коллекция
    public function posts()
    {
        // У каждого пользователя много постов
        // hasMany определяется у модели, имеющей внешние ключи в других таблицах
        return $this->hasMany('App\Post', 'creator_id');
    }
}

Метод hasMany также поддерживает соглашение для определения имени внешнего ключа. Только здесь оно определяется не по имени метода, а по имени модели, в которой описывается связь. Для модели User это будет creator_id. В нашем случае такая логика не работает, поэтому имя свойства указано явно.

CRUD

Теперь Eloquent знает о связях и даёт работать с ними напрямую:

<?php

$user = App\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 добавится user_id = <идентификатор пользователя>
$post2 = $user->posts()->find(1);

$post == $post2; // true

// Удаление всех постов одним запросом
$user->posts()->delete();

То же самое происходит и с другой стороны связи:

<?php

$post = App\Post::find(1);
$post->creator->first_name;

// Установка пользователя
$post->creator()->associate($user);
$post->save();

При работе со связями важно переключиться от мышления через таблицы и ключи к сущностям и связям (почему). Технически это значит, что код опирается на сами сущности, а не их идентификаторы:

<?php

// Плохо
$post->user_id = $user->id;

// Хорошо
$post->user->associate($user);

Выборки

Все типы связей в Eloquent поддерживают построение запросов на выборку:

<?php

// В запрос будет включено условие по user_id равным текущему пользователю
// SELECT * FROM posts WHERE user_id = 1 AND state = 'active'
$user->posts()->where('state', 'active')->get();

Самостоятельная работа

  1. Заполните $fillable у Post.
  2. Изучите связи внутри Post.
  3. Откройте REPL и создайте несколько постов.

Дополнительные материалы

  1. Один ко Многим
Мы учим программированию с нуля до стажировки и работы. Попробуйте наш бесплатный курс «Введение в программирование» или полные программы обучения по Node, PHP, Python и Java.

Хекслет

Подробнее о том, почему наше обучение работает →