Может войдёшь?
Черновики Написать статью Профиль

Eloquent ORM - работа с БД

перевод

  1. 1. Создание и использование модели
  2. 2. Зависимости (отношения)
    1. 2.1. Один к одному
    2. 2.2. Один ко многим
    3. 2.3. Многие ко многим
  3. 3. Связанные модели
  4. 4. Связующие таблицы
  5. 5. Активная загрузка
  6. 6. Чтение и запись

ORM — довольно полезная штука. ORM расшифровывается как «Объектно-реляционное отображение» («Object Relational Mapper») — звучит запутанно, верно? Давайте разобъём его на части (барабанная дробь): отображение означает, что мы связываем наши PHP-объекты, или классы, с таблицами и строчками базы данных. При чём здесь реляционное станет понятно в разделе об зависимостях.

Существует множество ORM-библиотек, но нет ни одной такой же красноречивой как... как сам Eloquent (игра слов — название «eloquent» переводится как «красноречивый». — прим. пер.). Eloquent входит в ядро Laravel, поэтому может быть использован в стандартном комплекте.

В данной статье я предполагаю, что вы уже знакомы с миграциями и с моей статьёй «Fluent — создание запросов к БД» — вам понадобятся эти знания.

Хотя Eloquent — альтернатива для Fluent, они имеют много общих методов и для нас разница только в том, что Eloquent возвращает нам результаты запросов в виде собственного объекта, а не стандартного объекта PHP stdClass — это делает наш код ещё чище. Итак, приступим к изучению модели Eloquent.

Создание и использование модели

PHP
class User extends Eloquent {
}

В общем-то это всё. Нет, я серьёзно. Видите ли, Eloquent доверяет вам — он знает, что вы уже успели создать нужную миграцию для таблицы users, он не будет вам сообщать, какие поля у неё есть — он верит, что вы их знаете, ведь вы должны знать, ведь вы выполнили миграцию и создали нужную таблицу. Ах, кстати, вы ведь не забыли добавить в неё PHPincrements('id')? Это обычная практика и она требуется Eloquent для работы.

Как вы видите, класс этой модели называется User, а её таблица — users. Это не опечатка — Eloquent различает слова в единственном и множественном числе для английского языка (см. PHPPlurizerприм. пер.). Наш объект — единственного числа, поэтому мы назвали его User, но наша таблица содержит множество пользователей — поэтому мы назвали её users. Laravel ищет таблицу в БД по имени класса модели во множественном числе.

Преобразование из одиночной формы слова во множественную работает только для английского языка, но если вы хотите добавить поддержку другого — добавьте формы нужного слова в массив в файле application/config/strings.php и впредь оно будет преобразовываться, как нужно.

Хотя, возможно, вам не нравится, что таблица названа во множественном числе? Нет проблем, просто определите статическое свойство $table и укажите в нём нужное имя:

PHP
class User extends Eloquent {
  public static 
$table 'app_users'
}

Отлично, вы наверное уже устали набирать это громоздкое определение модели (пожалуйста, обратите внимание на сарказм)... Давайте теперь попробуем её использовать:

PHP
$user User::find(1);

Постойте, мы ведь уже это видели? Помните PHPfind() из статьи про Fluent — он возвращает одну строку результата по значению первичного ключа. Как вы видите, Eloquent использует множество методов, предоставляемых Fluent — это очень удобно, потому что вы можете теперь использовать обе библиотеки для работы с БД без раздумий над тем, какой метод чему принадлежит. Хорошо!

Для использования Eloquent достаточно просто написать имя объекта, PHPDB::table() для этого не требуется.

Вы можете использовать полученный результат-объект похожим способом — хотя, сказать по правде, вы сейчас используете объект Eloquent, но в данном случае нет никакой разницы. Давайте прочитаем имя пользователя:

PHP
echo $user->name;

Верно, всё точно так же, как и в прошлый раз — коротко и ясно.

Что, если мы захотим получить наш объект PHPUser в виде массива? Просто вызовем метод to_array() на любой из моделей Eloquent для получения массива вместо объекта:

PHP
$user $user->to_array();

Если вы хотите исключить определённые поля из результирующего массива добавьте статическое свойство $hidden к классу модели и перечислите в нём нужные имена:

PHP
class User extends Eloquent {
  public static 
$hidden = array('nickname');
}

А как получить несколько результатов? Используем all() для получения записей всех существующих пользователей:

PHP
$users User::all();

Теперь у нас есть объект объектов и мы можем пройти по нему в цикле — например, так:

PHP
foreach ($users as $user) {
  
add_to_party($user);
}

Всё хорошо, только что-то слишком много парней... давайте увеличим наши шансы и избавимся от них:

PHP
$sexy_users User::where('sex''=''female')->get();

Ну вот, другое дело! Планка явно пошла вниз, теперь мы в центре внимания. Как вы видите, нам нужен всё тот же where(), связанный с триггером get() — ничего нового, лишь больше лаконичности.

Я мог бы перечислить все методы для получения результата, но мне не нужно этого делать — они все уже были описаны в статье про Fluent — замените все «Fluent» на «Eloquent» и вы получите нужный результат.

Вместо этого давайте посмотрим, что появилось нового. Создание и обновление объектов (записей) стало куда лучше:

PHP
$user = new User();

$user->name 'Дон Жуан';
$user->mojo '100%';

$user->save();

Мы создали новый объект PHPUser, установили его свойства в нужные значения существующих полей (нам не нужно задавать ID, Eloquent сделает это сам) и вызываем метод save() для записи изменений в БД. Этот шаблон проектирования известен под названием ActiveRecord и он очень удобен для работы со строками, потому что они такие же объекты, как и все остальные в нашем приложении. Звучит так, как будто база данных и весь этот ужасный SQL просто не существуют.

Если у нас уже есть массив с парами PHPполе => значение, описывающий нашего друга «Дон Жуана», то мы можем либо передать его в конструктор объекта, либо использовать метод массового присваиванияfill() — для добавления данных в БД. Например:

PHP
$user = new User(array(
  
'name' => 'Дон Жуан',
  
'mojo' => '100%'
));

$user->save();

Или:

PHP
$arr = array(
  
'name' => 'Дон Жуан',
  
'mojo' => '100%'
);

$user = new User();
$user->fill($arr);
$user->save();

Будьте внимательны и не допускайте непроверенные данные к этому механизму — это может создать проблемы с безопасностью.

Массовое присваивание можно безопасно использовать, если в модели определено свойство $accessible. В этом видеоролике наглядно продемонстрировано, чем может обернуться массовое присваивание, если для него используются внешние данные. — прим. пер.

Как насчёт изменения записи? Это просто — сначала нам нужно её выбрать, а затем изменить:

PHP
$user User::where('name''=''Дон Жуан')->first();

Здесь мы используем first() для получения одного объекта в результате запроса — мы не можем изменить все значения в массиве разом и нам прошлось бы использовать цикл и пример бы получился более длинным.

PHP
$user->mojo '5%';
$user->save();

Я не смог забрать всё его обаяние, это было бы не честно — по крайней мере теперь он может прийти на вечеринку. В общем, вы видите, что обновление записи происходит точно так же, как и её добавление за исключением того, что сперва нам нужно найти объект, который следует обновить.

Помните, как мы создали поля updated_at и created_at с помощью PHP$table->timestamps() в статье про миграции? Eloquent автоматически устанавливает эти поля в текущее время (timestamp) когда вы добавляете или обновляете запись. Для отключения этой возможности просто установите статическое свойство $timestamps класса модели в false:

PHP
class User extends Eloquent {
  public static 
$timestamps false;
}

Зависимости (отношения)

Зависимости в Eloquent — красивейшая вещь. Больше не нужно этих JOIN, мы можем явно определять отношения «один к одному», «один ко мноим» и «многие ко многим» простым вызовом методов в объекте модели. Давайте начнём с кода — так вы научитесь гораздо быстрее.

Один к одному

Начнём:

PHP
class User extends Eloquent {
  public function 
invite() {
    return 
$this->has_one('Invite');
  }
}

Давайте предположим, что мы уже сделали таблицу invites и модель PHPInvite, где мы будем хранить приглашения на нашу вечеринку.

Теперь мы можем выдать приглашение пользователю с помощью простого и выразительного синтаксиса:

PHP
$invite User::find(1)->invite()->first();

Мы снова используем метод first(), потому что нам нужен только один результат. Добавив метод invite() к нашей цепочке вызовов мы, конечно, имели в виду название отношения (зависимости) — таким образом, мы получили объект PHPInvite, который относится к нашему пользователю (user).

Пример выше выполнит два SQL-запроса:

sqlSELECT * FROM "users" WHERE "id" = 1;
SELECT * FROM "invites" WHERE "user_id" = 1;

Мы видим, что Eloquent автоматически ищет нужный user_id по внешнему ключу («foreign key»), поэтому если нам нужна эта зависимость, то требуется добавить числовое поле user_id в нашу миграцию для таблицы invites.

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

PHP
class User extends Eloquent {
  public function 
invite() {
    return 
$this->has_one('Invite''the_invite_id');
  }
}

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

PHP
class Invite extends Eloquent {
  public function 
user() {
    return 
$this->belongs_to('User');
  }
}

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

Теперь мы можем сделать так:

PHP
$user Invite::find(1)->user()->first();

Прощё простого!

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

Что, если мы хотим связать многие записи с одним полем? Как вы могли уже догадаться по заголовку, для этого есть специальный метод. Давайте посмотрим (я так часто говорю это, верно? Возможно, это могло бы стать моим девизом):

PHP
Class User extends Eloquent {
  public function 
hats() {
    return 
$this->has_many('Hat');
  }
}

Мы вновь передаём методу имя класса в виде строки, начинающейся с заглавной буквы.

В этом примере таблице hats понадобится внешний ключ user_id, после чего мы сможем сделать так:

PHP
$hats User::find(1)->hats()->get();

Мы можем использовать «магические свойства» PHP для получения того же результата в краткой форме:

PHP
$hats User::find(1)->hats;

А так ещё лучше!

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

Речь, видимо, идёт о вызове PHP$this->has_many('Hat''the_hat_id'); — см. выше. — прим. пер.

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

Здесь всё немного более сложно, чем до этого, но не волнуйтесь — я здесь, чтобы провести вас; возьмите мою руку... Представим, что у нас есть две модели EloquentUser и Task; пользователь (PHPUser) имеет множество задач («task»), а каждая задача может иметь множество пользователей. Для этого типа отношений нам нужна третья таблица.

Эту таблицу называют по-разному: центральная таблица («pivot table»), таблица-индекс («lookup table»), промежуточная таблица («intermediate table») и т.п.

На самом же деле это просто таблица с двумя числовыми полями — user_id и task_id, которые связывают две таблицы вместе, образуя зависимость многие ко многим.

Название таблицы состоит из названий обоих связуемых таблиц во множественном числе, разделённых подчёркиванием — причём они стоят в алфавитном порядке: tasks_users.

Отлично, теперь у нас есть эта таблица, так что давайте создадим саму зависимость:

PHP
class User extends Eloquent {
  public function 
tasks() {
    return 
$this->has_many_and_belongs_to('Task');
  }
}

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

Связанные модели

Добавляя записи в таблицы с зависимостями мы могли бы установить PHPuser_id или PHPhat_id в нужные значения вручную, но это не очень элегантно — почему бы просто не передать вместо них объект?

PHP
  $hat Hat::find(1);
  
$user User::find(1);
  
$user->hats()->insert($hat);

Так оно выглядит намного лучше — вместо обращения к числовым внешним ключам напрямую мы просто передаём сами объекты.

С зависимостью один ко многим вы можете передать массив пар PHPполе => значение в метод save() для добавления или обновления зависимой записи. Например:

PHP
$hat = array(
  
'name' => 'Dennis',
  
'style' => 'Fedora'
);

$user User::find(1);

$user->hats()->save($hat);

Коротко и понятно :)

Если мы добавляем запись в таблицу с зависимостью многие ко многим с помощью insert(), Eloquent не только создаст саму запись, но и обновит связующую таблицу:

PHP
$hat = array(
  
'name' => 'Dennis',
  
'style' => 'Fedora'
);

$user User::find(1);

$user->hats()->insert($hat);

Как быть, если объекты (записи) уже существуют и мы просто хотим создать новое отношение между ними? Это делается с помощью метода attach(), который принимает ID записи или её объект. Давайте посмотрим:

PHP
$user->hats()->attach($hat_id);

Связующая таблица будет обновлена автоматически.

Дальше у нас метод, о котором я сам только недавно узнал — судя по всему, он появился в Eloquent с Laravel 3.1. Мы можем использовать метод sync(), передав ему массив ID записей — после его выполнения в связующей таблице останутся только переданные ID. Это весьма удобно — вот пример:

PHP
$user->hats()->sync(array(478));

Как именно работает sync() было сказано в одном из комментариев к оригинальной статье: "PHPsync() удалит ID, не перечисленные во входном массиве, а также добавит новые ID, которых изначально не было. При этом он не изменит timestamp-поля для существующих записей, что удобно при отслеживании времени, когда отношение было впервые создано. — Eric Barnes". — прим. пер.

Связующие таблицы

Что, если мы хотим получить доступ к связующей таблице напрямую, а не только к таблицам, которые она связывает? Для этого Eloquent предоставляет метод pivot():

PHP
$pivot $user->hats()->pivot();

Теперь у нас есть массив объектов-строк — точно так же, как и после других запросов, только здесь они ссылаются на записи-отношения шляп пользователей (hats_users).

Что, если мы хотим получить конкретную запись, которая используется для взаимоотношения записей? Это легко делается с помощью «магических свойств» pivot() — официальная документация содержит отличный пример на эту тему и я позаимствую его — хотя я немного изменив, примерно так же, как вы делали со своими домашними заданиями в средней школе:

PHP
$user User::find(1);

foreach (
$user->hats as $hat) {
  echo 
$hat->pivot->created_at;
}

Нам доступно поле created_at, содержащее время (timestamp) создания этого отношения.

Ладно, мне уже всё это порядком надоело — на самом деле я хочу удалить все свои шляпы, все до единой. К счастью, я помню свой пользовательский ID — это число 7, счастливое число 7. Так что давайте сделаем это прямо сейчас, давайте delete() все мои шляпы:

PHP
User::find(1)->hats()->delete();

Это произошло, отныне у меня всегда будет непокрытая голова — но это стоит того, ведь теперь вы знаете, как использовать метод delete().

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

Существует два термина — «lazy loading» и «eager loading»; первый знаком и легко переводим — «ленивая загрузка» или «загрузка по требованию», в то время как последний можно перевести как «активная загрузка» или «предзагрузка». — прим. пер.

Eloquent даёт нам возможность использовать активные загрузки для разрешения извечного спора о проблеме PHPN+1. Если вы ещё не знаете, что это такое, позвольте мне разложить всё по полочкам на основе того, что вы уже узнали. Посмотрим на следующий пример:

PHP
$users User::all();

foreach (
$users as $user) {
  echo 
$user->hat->size();
}

Для каждого цикла Eloquent должен выполнить новый SQL-запрос для получения шляпы (hat) очередного пользователя, и вернуть её размер (size). Это не представляет серьёзной проблемы на маленьких наборах данных, но с сотнями записей это может серьёзно повлиять на производительность. С помощью активной загрузки мы можем сообщить Eloquent, что при получении объекта PHPUser он должен выполнить запрос PHPSELECT FROM hatsтаким образом у нас будут все данные, которые нам нужны. Это снизит число запросов до двух — как раз то, что нам нужно.

Посмотрим, как мы можем активно загрузить набор отношений:

PHP
$users User::with('hat')->get();

Хорошо, теперь зависимость hat загружена. Если вы хотите загрузить несколько отношений просто передайте методу with() массив. Обратите внимание, что это статический метод, поэтому он всегда должен быть в начале «цепочки».

Вы можете использовать активную загрузку на вложенных зависимостяхх. Допустим, наш объект Hat имеет зависимость hatstand() («вешалка»), с помощью которой мы узнаём, на какой вешалке висит нужная шляпа. Мы можем загрузить оба этих отношения таким образом:

PHP
$users User::with(array('hat''hat.hatstand'))->get();

Просто поставьте имя вложенного отношения после имени объекта с точкой.

Теперь мы добавим в загрузку некоторое условие — скажем, нам нужны только синие шляпы. Конечно, Eloquent позволяет это сделать! Просто передадим имя отношения как ключ в массиве, а значением будет анонимная функция (Closure), которая установит нужные критерии. Лучше всего это видно на примере:

PHP
$users User::with(array('hat' => function ($query) {
  
$query->where('color''=''blue');
}))->
get();

Вы можете добавить столько условий, сколько вам нужно.

Чтение и запись

Методы set и get позволяют нам преобразовывать значения полей модели, когда происходит их записи или чтение. Давайте посмотрим на следующий код:

PHP
public function set_password($password)   {
  
$this->set_attribute('hashed_password'Hash::make($password));
}

Мы создаём метод с именем, начинающимся с set_, после которого идёт имя поле; метод принимает параметр PHP$password — значение поля. Мы использовуем метод PHPset_attribute() для установки поля в новое значение, таким образом мы можем преобразовать его нужным образом. В примере выше полю hashed_password будет присвоен хеш пароля, а не сам пароль:

PHP
$user->password "secret_panda_ninja";

Это очень удобная возможность.

Методы get — противоположность методам set, они используются для изменения возвращаемого значения. Например:

PHP
public function get_panda_name()   {
  return 
'Panda'.$this->get_attribute('name');
}

Теперь если имя записи — «Dayle», мы можем вызвать:

PHP
echo $user->panda_name;

...и в результате получим строку «PandaDayle».

Как вы считаете, полезен ли этот материал? Да Нет

Комментарии (1)

macik

а как такой запрос написать Eloquent

SELECT * FROM `user_names` WHERE `firm_id` != 38 AND CONCAT( YEAR( NOW( ) ) , '—', DATE_FORMAT( `birthday` , '%m-%d %T')) BETWEEN DATE_SUB( NOW( ) , INTERVAL 1 DAY ) AND DATE_ADD( NOW( ) , INTERVAL 1 DAY )?

WHERE понятно, а вот далеше не понимаю

Написать комментарий

Разметка: ? ?

Авторизуйся, чтобы прокомментировать.