Skip to content

Instantly share code, notes, and snippets.

@2bj
Forked from greabock/Волшебный Eloquent.md
Last active August 29, 2015 14:10
Show Gist options
  • Save 2bj/54c6b0aa821fdaaf2b64 to your computer and use it in GitHub Desktop.
Save 2bj/54c6b0aa821fdaaf2b64 to your computer and use it in GitHub Desktop.

#Волшебный Eloquent. ##Дисклеймер Данный материал абсолютно не претендует на уникальность, и не является попыткой открыть для кого-то Америку. Все ниже изложенное (прямо или косвенно) можно легко почерпнуть из официального мануала. А для чего же оно тогда написано? Попытка подать информацию в чуть более развернутом виде, систематезировть собственные знания, и снять острый приступ графомаства. Если это вдруг окажется кому-то полезным, то мне будет приятно.

##Введение TL;DR
Так уж сложилось, что слоняясь по "интернетам", в поисках сообщников в ограблении банка единомышленников в изучении framework'a Laravel, я забрел в чат хоть и праздно прозябающего, но (страниями Алексея) живого и дружелюбного Cообщества, и плотно там осел. А через какое-то время заметил, что отвечаю на чьи-то вопросы гораздо чаще, чем задаю их. Хотя мой замысел был иной: изначально, я хотел добраться до "знающих людей" и, как вампир, высосать через чат все их "знания тайной силы"... Но не тут-то было. Как оказалось, таких же как я "акакиев", там хватало и до меня и без меня. Ну что же делать? Будем решать вопросы.

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

##Ребят, а куда я попал?.. Ох, и классно же тут у вас! Когда я впервые читал мануал по "Ларе", я думал: "хм, это прикольно", "ну, и это так ничего". Ну то есть, я вовсе не был чем-то шокирован. Про Composer я знал и раньше, Artisan мне не казался чем-то уж особенным. Не было для меня какой-то киллер-фичи, из-за которой бы я слез с CodeIgniter'a. Все изменилось, когда я добрался до спецификации моделей... То, как устроены модели в Laravel - это просто магия и волшебство! Но как и со всякой магией, с ней нужно уметь обращаться.

Часть 1. Постотроение отношений в моделях данных.

###Eloquent Если забить в гугл-переводчик слово eloquent, то мы увидим, что наиболее употребляемым переводом этого слова является красноречивый. Но я не думаю, что создатели Laravel вкладывали именно этот смысл в название своей ORM. Так сложислось, что для русскоязычного мира "красноречивый" - почти синоним слова (простите) "пиздабол". А это совсем не то, что хотелось бы слышать об изучаемом инструменте.
Другой, менее употребляемый, вариант перевода - Выразительный. И он подходит куда больше. Мы часто слышим: "выразительный взгляд", "выразительный портрет", "выразительная речь". Выразительно - это то, что не требует пояснения, дополнительных комментариев - оно понятно само по себе.
Но чтобы ощутить всю эту выразительность, применительно к коду в Laravel, нужно соблюдать некоторые несложные правила.

###Строгое именование (строгая нотация) Один программист, назвает модель данных статей PostModel, другой PostsModel, десятый пишет в змеиной нотации Post_Model.
В Laravel простая самостоятельная модель данных для таблицы posts может быть создана приблизительно так:

Шаг первый:

<?php
class Post extends Eloquent{
}

Шаг второй:
Осознать, что больше ничего делать не нужно, и все уже и так работает.

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

magic
Но, как всегда, есть одно "но". Для того, чтобы эта (и другая подобная) "магия" работала, нужно соблюдать соглашение строго именования:

  1. Одна модель данных соответствует лишь одной таблице.
  2. Модель данных называется в единственном числе, в ВерхнейВерблюжейНотации: Category, ShopCategory
  3. Таблица данных называется во множественном числе в нижней_змеиной_нотации: categories, shop_categories
  4. Название полей, являющихся внешнии ключами, ссылающимися на определитель во внешней таблице, пишутся в нижней_змеиной_нотации, единственном числе по имени модели и постфиксом по имени референсного поля "product_id" : categоry_id.
  5. Пивотные (стержневые) таблицы, выражающие отношение "Многие ко многим", называются в единственном числе, нижней змеиной нотации по именам связанных моедлей, в произвольном порядке: user_role, или role_user.
  6. Таблица данных должна содержать поля:
  • id(int, unsigned, auto-increment)
  • created_at(timestamp) опционально, при использовании таймштампов
  • updated_at(timestamp) опционально, при использовании таймштампов
  • deleted_at(timestamp) опционально, при использовании трейта "мягкого удаления"
  1. При создании полиморфических связей, поля типа морфемы и (условного) вешнего ключа морфемы, должны называться идентично по имени морфирующей функции и заканчиваться постфиксами "_type" и "_id"(или другого референсного поля-определителя), кроме того, они должны иметь тип данных varchar и integer соответствнно: morph_type, morph_id
  2. При посеве данных, в полиморфических таблицах, поле типа морфемы должно заполняться названием связанной модели буквально, включая путь к пространству имен: User, Shop\Product

Соблюдение всех этих правил не является абсолютно обязательным для работы с Eloquent, более того - в "Ларе" предусмотрено все необходимое для обхода этих правил. Так, что именование полей, таблиц и моделей по своему в кусу не будет "сверх-гемороем". Но, соблюдая их (правила), Вы сможете избежать ненужных уточнений и добавить немного волшебства. Кроме того, это поможет в общении с другими разработчиками под Laravel, и людьми его изучающими.

Немного о внешних ключах:
Строго говоря, Laravel не требует (но и не запрещает) буквального наличия внешних ключей на полях таблиц, как такового; и ему вполне достаточно правильно именованых полей, или точного их указания в связях моделей. Благодаря этой своей особенности, "Лара" одинаково хорошо работает как с реляционными, так и нерляционными базами данных. Здесь и далее, говоря "внешний ключ", я не буду подразумевать буквального его наличия в таблице - я буду иметь ввиду поля в таблицах, по которым будет реализовываться связь моделей.

###Построение отношений Здесь я напомню какие типы связей бывают, и то как они выражаются в Eloquent.

И так, типы связей бывают:

  1. Один к одному
  2. Многие к одному
  3. Многие ко многим
  4. Полиморфические

Первые три связи могут быть реализованы, в реляционных базах данных, а последняя - лишь эмулируется.

####Один к одному

В школе есть ученики, каждый год приходят новые ученики, старые уходят. Текучка, в общем. За каждым учеником в школе закреплен шкафчик. У шкафчиков есть какие-то свои параметры: инвентарный (не порядковый) номер или степень износа. В каждый момент времени за одним учеником может быть закреплен один шкафчик. Шкафчиков меньше, чем учеников, по этой причине, на всех не хватает, и они всегда заняты.

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

Таблица students:

id name
1 Василий
2 Геннадий
3 Евлампий

Таблица cabinets

id inventory_number student_id
1 AS-23 3
2 AS-65 1
3 BG-15 2

Связи в моделях Student и Cabinet будут обозначены соответственно:

class Student extends Eloquent{
    
    public function cabinet()
    {
        returnt $this->hasOne('Cabinet');
    }
}
class Cabinet extends Eloquent{
    
    public function student()
    {
        returnt $this->belongsTo('Student');
    }
}

один к одному

Эти модели связываются по внешнему ключу в поле student_id в таблице cabintets. Внешний ключ автоматически (на уровне ORM) завязывается на поле id таблицы students. Метод hasOne(), говрит нам о том, что объект Student может иметь (в прочем, может и не иметь) лишь один подчиненнный объект Cabinet. Не смотря на то, что связь "один к одному" звучит как равнаправная, на самом деле она такой не является. В данном конкретном случае объект Cabinet подчинен/принадлежит объекту Student. Это означает, что именно в таблице cabinets мы будем искать внешний ключ на таблицу users, а не наоборот.

Если перевести belongs to на русский язык, то мы получим принадележит к, а has one = имеет один соответственно. Эти два метода моделей и выражают отношения между моделями.

Ученик (Student) имеет один (hasOne()) Шкафчик (Cabinet).
Шкафчик (Cabinet) принадлежит (belongsTo()) Ученику (Student).

Я думаю, что это вполне Выразительно.

####Многие к одному

Повторим тоже самое для классов в школе и учеников в классах.

В школе есть классы (я не имею ввиду аудитории, я имею ввиду группы в потоке). В одном классе много учеников, один ученик принадлежит лишь к одному классу.

Многие к одному. Приведем таблицы. Таблица classes:

id title
1 11А
2
3

Таблица students:

id name class_id
1 Василий 1
2 Геннадий 3
3 Евлампий 2

И модели со связями:

class Class extends Eloquent{
    
    public function students()
    {
        returnt $this->hasMany('Student');
    }
}
class Student extends Eloquent{
    
    public function cabinet()
    {
        returnt $this->belongsTo('Class');
    }
}

многие к одному

Как можно заметить, этот код немногим отличается от предыдущего. Единственное отличие - метод hasMany() вместо hasOne(). Разница между ними лишь в том, что отношение выраженное через hasOne() при выборке ищет один единственный объект и возвращает его. В то время как hasMany() возвращает коллекцию объектов, даже если в выборку попадет один единственный результат, или результатов не будет вообще - в этом случае коллекция будет иметь один объект или будет пуста, соответственно.

Стоит отметить, что я называю эту связь именно "Многие к одному", а не "Один ко многим" (последнее выражение встречается гораздо чаще и вводит людей в заблуждение). Дело в том, что именно ученики принадлежат к классам а не наоборот. К слову сказать, связь "Один ко многим" так же может существовать, но ввиду избыточности (требуется дополнительная таблица) и слабого логического обоснования, она испльзуется крайне редко, если вообще используется. За все время, что я изучаю структуры данных, мне еще ниразу не приходилось с ней столкнуться. Связь "Многие к одному" вполне достаточна, для всех юзкейсов выражающих отношения с общей вершиной (я имею ввиду Adjacency. Это термин из теории древовидных структур, о нем мы поговорим в другой раз).

Итого:
Класс(Class) имеет много (hasMany()) учеников (Student).
Ученик(Student) принадлежит к (belongsTo()) классу (Class).

Стоит обратить внимание, что метод belongsTo() пишется в подчиненных моделях, вне зависимости от того, является ли их отношение c подчиняющией моделю "Один к одному" или "Многие к одному". Как я уже писал выше, это поиск единственного соответствия по внешнему ключу.

###Многие ко многим Немного о пивотах
В то время, как связи "Один к одному" и "Один ко многим" выражают подчиненность объектов, связь "Многие ко многим", является обоюдной и равноправной. Это не означает, что обе таблицы будут иметь внешние ключи (это было бы неудобно, ведь тогда пришлось бы дублировать записи). Вместо этого, внешние ключи выносятся в третью - "пивотную" (стержневую) таблицу. Пивотная таблица не обязана (но может) иметь собственную модель. Как правило, она не несет в себе никаких данных кроме связи между моделями. Реже, она может содержать идентификатор связи, некоторые данные о порядке сортировки (приоретете вывода), или уровне вложенности для таблиц-замыканий (о таблицах замыканий будет отдельный разговор, когда мы будем обсуждать древовидные структуры). Но в большинстве случаев, она содержит лишь два поля со внешними ключами.

В школе есть кружки дополнительных занятий (танцы, рисование, самбо). Каждый ученик может посещать несколько кружков или не посещать их вообще. Каждый кружок может обучать множество учеников. Назовем эти кружки группами по интересам. Или просто группами (Group).

И так, для выражения связи "Многие ко многим", понадобиться три таблицы:

  1. students
  2. groups
  3. student_group - пивот

пивотная таблица должна содержать соответствующие внешние ключи: student_id и group_id.

Таблица students:

id name
1 Василий
2 Геннадий
3 Евлампий

Таблица groups:

id title
1 Рисование
2 Плавание
3 Сделай сам

Таблица student_group:

student_id group_id
1 2
1 3
1 1
2 1

Модели данных, в этом случае будут выглядеть так:

class Student extends Eloquent{
    
    public function cabinet()
    {
        returnt $this->belongsToMany('Cabinet');
    }
}
class Group extends Eloquent{
    
    public function students()
    {
        returnt $this->belongsToMany('Student');
    }
}

многие ко многим

Ученик (Student) принадлежит ко множеству (belongsToMany()) Групп (Group).
Группа (Group) принадлежит множеству (belongsToMany()) Учеников (Student).

Согласно примеру из приведенных выше таблиц: Василий посещает все три кружка, а Геннадий ходит лишь на рисование.

###Полиморфические (полиморфные) связи

Как я писал выше, полиморфические связи не поддерживаются реляционными структурами данных и лишь выражают логическое отношение между моделями данных на уровне понимания (а в случае с Eloquent и на уровне ORM). По сути, наличие подобных отношений в моделях данных означает сниженный (намернно или по недосмртру) уровень абстракции самих моделей данных и/или намеренное упрощение структуры данных для понимания. Кроме того, при попытке избавится от полиморфической связи и ввести дополнительный слой абстракции, в базе данных появляется множество таблиц (а в коде - множество моделей), отношения между которыми не всегда очевидны и поняны. Так или иначе, полиморфические связи бывают удобны, и нужно уметь их использовать. В Eloquent предусмотрен функционал для работы с таким связями.

В школе есть билиотека, в библиотеке выдают книги. Книги могут былть выданы как ученику так и учителю. Одна книга одновременно может принадлежать одному человеку, один человек может иметь много книг.

К сожалению, на этапе проектирования базы данных, мы не предусмотрели наличия подобного функционала. И у нас нет модели "Человек", и отдельной модели "Роль" - как было бы "по уму". Сейчас у нас есть модель учителя и модель ученика. Переделывать всю базу данных и переписывать код нет никакого желания. Как же быть? Все верно - нужно ввести полиморфическую связь. Допустим у нас есть модель Book и соответствующая ей таблица books:

id title student_id
15 Вий 22
13 Отцы и дети 14
32 Пушкин. Поэмы 19

Сейчас мы выдаем книги только учникам. Как же сделать так, чтобы можно было выдавть книги и учителям? Допустим, что есть модель учитля Teacher и соответствующая таблица teachers. Мы могли бы ввести дополнительное поле teacher_id в нашу таблицу:

book_id student_id teacher_id
1 22 null
13 null 25

А если у нас появятся другие модели, к которым можно отнести книги? Снова добавлять поля? Бред. Мы могли бы выделить отношения между книгами и учителями в отдельную пивотную таблицу book_teacher:

book_id teacher_id
15 22
13 25
32 11

Но тогда при движении книги (я имею ввиду передачу от одного объекта другому) пришлось бы отслеживать состояние и других таблиц, чтобы книга не оказалась у двоих сразу или вообще пропала. Это все неправильно... А правильно будет вот так:

таблица books:

id title holder_type holder_id
1 Вий Student 17
13 Выстрел Teacher 25

В данном случае, поле holder_id не завязано на определенное поле определенной таблицы, но оно будет привязано к соответствующему полю соответствующей таблицы, на основании значения поля holder_type. То есть, каждый экземпляр объекта Book может принадлежать как объекту Student так и объекту Teacher (но не обоим сразу), в заисимости от значения поля holder_type. Модели данных в этом случае будут выглядеть приблизительно так:

class Book extends Eloquent {

  public function holder()
  {
    return $this->morphTo();
  }

}
class Student extends Eloquent {

  public function books()
  {
    return $this->morphMany('Book', 'holder');
  }

}
class Teacher extends Eloquent {

  public function books()
  {
    return $this->morphMany('Book', 'holder');
  }

}

полиморфическая связь

вот такая она - "Полиморфия".

###Другие виды связей По мимо основных, уже перечисленных видов связей, в "Ларе" есть нобычные "Сквозные" виды связей. Но они требуют отдельного разговора.

###Заключение

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

#####З.Ы. Что бы узнать о том, как правильно указывать поля в отношенияx без соблюдения строгой нотации - курим мануал.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment