Laravel. Eloquent связи

screenshot-laravel com 2016-03-28 10-36-38
То, как работают связи в Eloquent ORM очень похоже на то, как они работали в Kohana ORM. Но есть мелкие отличия.

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

Связь один-ко-многим
————————————

Связь один-ко-многим

Вот пример, как в модели задаётся связь один-ко-многим на примере пары таблиц авторы-статьи:

class Author extends Model {

   public function articles()
   {
      return $this->hasMany('App\Article');
   }

}

Теперь мы можем обращаться к статьям автора через его свойство:

$articles = Author::find(1)->articles;

Если нужны дополнительные условия — можно добавить и их. Используйте Query Builder:

$Articles = Author::find(1)->articles()->where('title', '=', 'foo')->first();

articles vs articles()

Обратите внимание, в последнем примере использован метод articles(), а в предыдущем примере использовалось динамическое свойство articles (без скобок!!). При всей схожести есть существенные отличия. Метод используется для построения запроса с последующим включением фильтров и всего такого, а свойство для получения готового результата.

Для наглядности пример №1

Вот такая команда (используем свойство, которое без скобок)

$author->articles->first()
// Ну или чтобы избежать кеширования 
$author->fresh()->articles->first()

Генерирует такие запросы

// Вытаскиваем автора по ID
select * from `authors` 
  where `id` = ? limit 1
// А потом тащим все-все-все его статьи,
// хоть и нужна только одна
select * from `articles` 
  where `articles`.`author_id` = ? 
  and `articles`.`author_id` is not null

А вот такая команда (используем метод, который со скобками)

$author->fresh()->articles()->first()

даёт такой же результат, но генерирует совершенно другие SQL запросы

// Вытаскиваем автора по ID
select * from `authors` 
  where `id` = ? limit 1
// Потом вытаскиваем только одну статью
select * from `articles` 
  where `articles`.`author_id` = ? 
  and `articles`.`author_id` is not null 
  limit 1

Собственно разница в том, что первый пример тащит всю коллекцию и потом средствами PHP её обрабатывает, а второй обрабатывает данные на стороне SQL сервера.

Для наглядности пример №2

Усложняем запрос

$author->fresh()->articles()->where('id','<','10')->get()

Получаем запросы к БД

select * from `authors` 
  where `id` = ? limit 1
select * from `articles` 
  where `articles`.`author_id` = ?
  and `articles`.`author_id` is not null 
  and `id` < ?

А теперь делаем то же самое без скобок

$author->fresh()->articles->where('id','<','10')->get()
// У коллекции другой метод get() нежели у Query Builder,
// и он должен использоваться как-то по-другому
PHP warning:  Missing argument 1 for Illuminate\Support\Collection::get(), called in ...

// Поэтому делаем без get()
$author->fresh()->articles->where('id','<','10')   

Получаем запросы:

select * from `authors` 
  where `id` = ? limit 1
select * from `articles` 
  where `articles`.`author_id` = ? 
  and `articles`.`author_id` is not null

То есть , условие совершенно не передалось в запрос, а вся фильтрация выполняется средствами PHP.
Вот собственно об этом и речь.

Умолчания

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

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

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

Связь многие к одному
---------------------------------------------
Связь многие-к-одному

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

class Article extends Model {

    public function author()
    {
        return $this->belongsTo('App\Author');
    }

}

Используя эту связь можно обращаться к автору напрямую через свойство статьи.

$article->author

Связать конкретную статью и конкретного автора можно либо напрямую присвоив полю author_id нужное значение, либо с использованием функции

$author->articles()->save($article);

Связь один-к-одному
-----------------------------------------
Связь один-к-одному

Тут всё примерно так же как и один-ко-многим, только вместо hasMany используется hasOne.

class User extends Model {

    public function userinfo()
    {
        return $this->hasOne('App\Userinfo');
    }

}

Обратная связь создаётся так же как и связь "многие-к-одному". Работа с моделями идентична.

Связь многие-ко-многим
-----------------------------------------
Например, статья (таблица 'articles')может иметь много тэгов (таблица 'tags'), но и тег может быть связано с разными статьями.

Для хранения связи многие-ко-многим нужна промежуточная таблица. Если использовать соглашения то она должна называться 'articles_tags' - таблицы в алфавитном порядке. И эта таблица должна иметь поля 'article_id' и 'tag_id'

Связь многие-ко-многим
На этой картинке название промежуточной таблицы неверное.

Так вот, если соглашения соблюдены, то модели выглядят так

class Article extends Model {

    public function tags()
    {
        return $this->belongsToMany('App\Tag');
    }

}

Аналогичная модель у тэгов

Для получения всех тегов у страницы или страниц у тегов выполняется следующий код

$tags = Article::find(1)->tags;
$articles = Tag::find(1)->articles;

Добавить/убрать тэг

$article->tags()->attach(1);
$article->tags()->detach(1);

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