Дисциплины - Объектно-ориентированное программирование

ООП в PHP - Фрэймворк Laravel - Связи в моделях

Логика связей в Laravel осуществляется с помощью класса Model, который упрощает работу и управление такими отношениями и поддерживает следующие типы связей:

  • Один к одному
  • Принадлежность
  • Один ко многим
  • Многие ко многим
  • Ко многим через
  • Полиморфные связи
  • Полиморфные связи многие ко многим

Один к одному / hasOne

Связь вида «один к одному» является очень простой. К примеру, если пользователь может содержать один телефон, то модель User связана с моделью Phone связью hasOne(). Мы можем определить такое отношение:

 class User extends Model{
  public function phone()
  {
    return $this->hasOne('Phone');
  }
 }

По умолчанию считается, что поле в связующей таблице называется по имени модели плюс _id. В данном случае предполагается, что таблица phones содержит поле user_id. Если мы хотим переопределить стандартное имя, необходимо передать второй параметр методу hasOne(). Кроме того, мы можем передать в метод третий аргумент, чтобы указать, какие локальные столбцы (модели User) следует использовать для объединения:

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

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

Принадлежность / belongsTo

Для создания обратного отношения в модели имеется специальный метод belongsTo() («принадлежит к»). Если пользователь hasOne телефон, то телефон belongsTo пользователю.

 class Phone extends Model{
  public function user()
  {
    return $this->belongsTo('User');
  }
 }

В примере выше модель Phone будет искать поле user_id в таблице phones. Если вы хотите назвать внешний ключ по другому, передайте это имя вторым параметром к методу belongsTo():

 return $this->belongsTo('App\User', 'local_key');

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

 return $this->belongsTo('App\User', 'local_key', 'parent_key');

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

Один пользователь может содержать множество телефонов. Тогда следует использовать связь hasMany(). Другими примерами данного отношения «один ко многим» является статья в блоге, которая имеет «много» комментариев или мама, имеющая детей. Мы можем смоделировать это отношение таким образом:

 class User extends Model{
  public function phones()
  {
    return $this->hasMany('Phone');
  }
 }

Теперь мы можем получить все телефоны пользователя с помощью динамического свойства:

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

В этом случае, мы получили массив телефонов, и для просмотра всех телефонов, нужно пройтись по элементам массива.

 foreach($phones as $one){
  echo $one->body;
  echo '\n';
 }

Если нужно добавить ограничения на получаемые телефоны, можно вызвать метод phones() и продолжить добавлять условия:

$phone = User::find(1)->phones()->where('title', '=', 'foo')->first();

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

 $phone->body

Рассмотрим еще один пример, который выявит недостатки связи hasMany. Предположим, у нас есть модель Book и модель Author. Book принадлежит (связь belongsTo) Author. И наоборот, Author содержит (связь hasMany) множество Book.

Определение связи в модели Book:

class Book extends Model {
    public function author()
{
        return $this->belongsTo('Author');
    }
}

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

foreach (Book::all() as $book)
{
    echo $book->name;
}

Мы обошлись всего одним заросом. Даже если будет 100 книг, это всё-равно один запрос:

SELECT * FROM books

Однако, если к выводу названия книги добавим автора:

foreach (Book::all() as $book)
{
    echo $book->name;
    echo $book->author->name;
}

То, получаем 101 запрос. Первым запросом получаем все книги, а потом в цикле делаем запрос на получение автора книги.

SELECT * FROM books;
SELECT * FROM authors WHERE id = 1;
SELECT * FROM authors WHERE id = 2;
SELECT * FROM authors WHERE id = 3;
...

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

Активная загрузка

Акнивная загрузка подразумевает подключение связующей модели с помощью метода with.

foreach (Book::with('author')->get() as $book)
{
    echo $book->author->name;
}

Таким образом будут выполнены всего два запроса:

 SELECT * FROM books;
 SELECT * FROM authors WHERE id IN(1,2,3);

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

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

Разумное использование активной загрузки поможет сильно повысить производительность приложения.

Многие ко многим / belongsToMany

Отношения типа «многие ко многим» сложнее отношений hasOne() и hasMany(). Примером может служить пользователь, имеющий много ролей, где роли также относятся ко многим пользователям. Например, несколько пользователей могут иметь роль «Admin». Нужны три таблицы для этой связи: users, roles и role_user. Имя таблицы role_user получается из упорядоченных по алфавиту имён связанных моделей, она должна иметь поля user_id и role_id.

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

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
  /**
   * Роли, принадлежащие пользователю.
   */
  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();

Вы можете переопределить имя таблицы, передав второй параметр методу belongsToMany():

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

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

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

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

namespace App;

use Illuminate\Database\Eloquent\Model;

class Role extends Model
{
  /**
   * Пользователи, принадлежащие роли.
   */
  public function users()
  {
    return $this->belongsToMany('App\User');
  }
}

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

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

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

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

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

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

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

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

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

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

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

Ко многим через / hasManyThrough

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

countries
    id - integer
    name - string

users
    id - integer
    country_id - integer
    name - string

posts
    id - integer
    user_id - integer
    title - string

Несмотря на то, что таблица posts не содержит столбца country_id, отношение «ко многим через» позволит нам получить доступ к posts через country с помощью $country->posts. Для выполнения этого запроса Eloquent ищет country_id в промежуточной таблице users. После нахождения совпадающих ID пользователей они используются в запросе к таблице posts.

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

namespace App;

use Illuminate\Database\Eloquent\Model;

class Country extends Model
{
  /**
   * Получить все статьи по заданной области.
   */
  public function posts()
  {
    return $this->hasManyThrough('App\Post', 'App\User');
  }
}

Первый параметр, переданный в метод hasManyThrough() является именем конечной модели, которую мы получаем, а второй параметр — это имя промежуточной модели.  Третий параметр — имя внешнего ключа для промежуточной модели, четвертый параметр — имя внешнего ключа для конечной модели, а пятый аргумент (для версии 5.2 и выше) — локальный ключ:

class Country extends Model
{
  public function posts()
  {
    return $this->hasManyThrough(
      'App\Post', 'App\User', 'country_id', 'user_id', 'id'
    );
  }
}

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

Полиморфные отношения позволяют модели быть связанной с более чем одной моделью.

Пример: три модели Мan (Мужчина), Woman (Женщина) и Car (Автомобиль), соответствующие таблицам men, women и cars. Мужчина (buyer) может купить несколько автомобилей, женщина (buyer) может купить несколько автомобилей, автомобиль может быть приобретен одним покупателем (мужчиной или женщиной). Где buyer - это условное название группы моделей (Man и Woman). Причем, количество моделей в ней не ограничено двумя.

Автомобиль должен хранить идентификатор покупателя (Buyer ID) и вид покупателя (Buyer Type).

class Man extends Model
{
   public function cars()
   {
     return $this->morphMany(Car::class, 'buyer');
   }
}
class Woman extends Model
{
   public function cars()
   {
     return $this->morphMany(Car::class, 'buyer');
   }
}
class Car extends Model
{
   public function buyer()
   {
     return $this->morphTo();
   }
}

Сохранение записей:

// Создание отношений между покупателем (Man/Woman) и Car.
$man->cars()->saveMany([
   $car1, 
   $car2,
]);
$woman->cars()->saveMany([
   $car1, 
   $car2,
]);
 
// Или используйте метод save() 
$man->cars()->save($car);
$woman->cars()->save($car);
 
// Создание отношений между Car и buyer (Men/Women).
$car1->buyer()->associate($man)->save();
$car2->buyer()->associate($woman)->save();

Извлечение записей:

// Получение автомобилей покупателя
$men->cars
$women->cars
// Получение покупателя автомобиля
$car->buyer

Полиморфные связи многие ко многим

В дополнение к традиционным полиморфным связям вы можете также задать полиморфные связи многие ко многим. Например, модели блогов 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 будут иметь связь morphToMany() через метод tags():

namespace App;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
  /**
   * Получить все теги статьи.
   */
  public function tags()
  {
    return $this->morphToMany('App\Tag', 'taggable');
  }
}

Теперь для модели Tag вы должны определить метод для каждой из моделей отношения. Для нашего примера мы определим метод posts() и метод videos():

namespace App;

use Illuminate\Database\Eloquent\Model;

class Tag extends Model
{
  /**
   * Получить все сообщения, связанные с тегом.
   */
  public function posts()
  {
    return $this->morphedByMany('App\Post', 'taggable');
  }

  /**
   * Получить все видео, связанные с тегом.
   */
  public function videos()
  {
    return $this->morphedByMany('App\Video', 'taggable');
  }
}

Извлечение отношений

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

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

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

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

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

Количество комментариев: 0

Для того, чтобы оставить коментарий необходимо зарегистрироваться
814301 БГУИР
814302 БГУИР
814303 БГУИР
894351 БГУИР
90421 БГУИР


Изображения Видео

1. Абстрактная фабрика https://www.youtube.com/watch?v=1mVONOCxfLg
2. Фабричный метод https://www.youtube.com/watch?v=5UqUDR6_2cY
3. Шаблон декоратор https://www.youtube.com/watch?v=Lwb9bm8yKD0
4. Dessign patterns on PHP https://github.com/domnikl/DesignPatternsPHP
5. Приёмы объектно-ориентированного проектирования. Паттерны проектирования Э. Гамма, Р. Хелм, Р. Джонсон, Д. Влиссидес; [пер. с англ.: А. Слинкин науч. ред.: Н. Шалаев]. — Санкт-Петербург [и др.] : Питер, 2014. — 366 с. : ил. ; 24 см.
6. Приемы объектно-ориентированного проектирования. Паттерны проектирования Э. Гамма, Р. Хелм, Р. Джонсон, Д. Влиссидес; [пер. с англ.: А. Слинкин науч. ред.: Н. Шалаев]. — Санкт-Петербург [и др.] : Питер, 2014. — 366 с. : ил. ; 24 см.
7. Ajax http://erud.by/ajax
8. Ajax http://erud.by/ajax
9. Ajax http://erud.by/ajax
10. Документация Laravel http://laravel.com
Задание к курсовой работе
Задание к курсовой работе
Вопросы к экзамену