ORM — довольно полезная штука. ORM расшифровывается как «Объектно-реляционное отображение» («Object Relational Mapper») — звучит запутанно, верно? Давайте разобъём его на части (барабанная дробь): отображение означает, что мы связываем наши PHP-объекты, или классы, с таблицами и строчками базы данных. При чём здесь реляционное станет понятно в разделе об зависимостях.
Существует множество ORM-библиотек, но нет ни одной такой же красноречивой как... как сам Eloquent (игра слов — название «eloquent» переводится как «красноречивый». — прим. пер.). Eloquent входит в ядро Laravel, поэтому может быть использован в стандартном комплекте.
В данной статье я предполагаю, что вы уже знакомы с миграциями и с моей статьёй «Fluent — создание запросов к БД» — вам понадобятся эти знания.
Хотя Eloquent — альтернатива для Fluent, они имеют много общих методов и для нас разница только в том, что Eloquent возвращает нам результаты запросов в виде собственного объекта, а не стандартного объекта PHP stdClass — это делает наш код ещё чище. Итак, приступим к изучению модели Eloquent.
Создание и использование модели
class User extends Eloquent {
}
В общем-то это всё. Нет, я серьёзно. Видите ли, Eloquent доверяет вам — он знает, что вы уже успели создать нужную миграцию для таблицы users, он не будет вам сообщать, какие поля у неё есть — он верит, что вы их знаете, ведь вы должны знать, ведь вы выполнили миграцию и создали нужную таблицу. Ах, кстати, вы ведь не забыли добавить в неё PHPincrements('id')
? Это обычная практика и она требуется Eloquent для работы.
Как вы видите, класс этой модели называется User, а её таблица — users. Это не опечатка — Eloquent различает слова в единственном и множественном числе для английского языка (см. PHPPlurizer
— прим. пер.). Наш объект — единственного числа, поэтому мы назвали его User, но наша таблица содержит множество пользователей — поэтому мы назвали её users. Laravel ищет таблицу в БД по имени класса модели во множественном числе.
Преобразование из одиночной формы слова во множественную работает только для английского языка, но если вы хотите добавить поддержку другого — добавьте формы нужного слова в массив в файле application/config/strings.php и впредь оно будет преобразовываться, как нужно.
Хотя, возможно, вам не нравится, что таблица названа во множественном числе? Нет проблем, просто определите статическое свойство $table и укажите в нём нужное имя:
class User extends Eloquent {
public static $table = 'app_users'
}
Отлично, вы наверное уже устали набирать это громоздкое определение модели (пожалуйста, обратите внимание на сарказм)... Давайте теперь попробуем её использовать:
$user = User::find(1);
Постойте, мы ведь уже это видели? Помните PHPfind()
из статьи про Fluent — он возвращает одну строку результата по значению первичного ключа. Как вы видите, Eloquent использует множество методов, предоставляемых Fluent — это очень удобно, потому что вы можете теперь использовать обе библиотеки для работы с БД без раздумий над тем, какой метод чему принадлежит. Хорошо!
Для использования Eloquent достаточно просто написать имя объекта, PHPDB::table()
для этого не требуется.
Вы можете использовать полученный результат-объект похожим способом — хотя, сказать по правде, вы сейчас используете объект Eloquent, но в данном случае нет никакой разницы. Давайте прочитаем имя пользователя:
echo $user->name;
Верно, всё точно так же, как и в прошлый раз — коротко и ясно.
Что, если мы захотим получить наш объект PHPUser
в виде массива? Просто вызовем метод to_array() на любой из моделей Eloquent для получения массива вместо объекта:
$user = $user->to_array();
Если вы хотите исключить определённые поля из результирующего массива добавьте статическое свойство $hidden к классу модели и перечислите в нём нужные имена:
class User extends Eloquent {
public static $hidden = array('nickname');
}
А как получить несколько результатов? Используем all() для получения записей всех существующих пользователей:
$users = User::all();
Теперь у нас есть объект объектов и мы можем пройти по нему в цикле — например, так:
foreach ($users as $user) {
add_to_party($user);
}
Всё хорошо, только что-то слишком много парней... давайте увеличим наши шансы и избавимся от них:
$sexy_users = User::where('sex', '=', 'female')->get();
Ну вот, другое дело! Планка явно пошла вниз, теперь мы в центре внимания. Как вы видите, нам нужен всё тот же where(), связанный с триггером get() — ничего нового, лишь больше лаконичности.
Я мог бы перечислить все методы для получения результата, но мне не нужно этого делать — они все уже были описаны в статье про Fluent — замените все «Fluent» на «Eloquent» и вы получите нужный результат.
Вместо этого давайте посмотрим, что появилось нового. Создание и обновление объектов (записей) стало куда лучше:
$user = new User();
$user->name = 'Дон Жуан';
$user->mojo = '100%';
$user->save();
Мы создали новый объект PHPUser
, установили его свойства в нужные значения существующих полей (нам не нужно задавать ID, Eloquent сделает это сам) и вызываем метод save() для записи изменений в БД. Этот шаблон проектирования известен под названием ActiveRecord и он очень удобен для работы со строками, потому что они такие же объекты, как и все остальные в нашем приложении. Звучит так, как будто база данных и весь этот ужасный SQL просто не существуют.
Если у нас уже есть массив с парами PHPполе => значение
, описывающий нашего друга «Дон Жуана», то мы можем либо передать его в конструктор объекта, либо использовать метод массового присваивания — fill() — для добавления данных в БД. Например:
$user = new User(array(
'name' => 'Дон Жуан',
'mojo' => '100%'
));
$user->save();
$arr = array(
'name' => 'Дон Жуан',
'mojo' => '100%'
);
$user = new User();
$user->fill($arr);
$user->save();
Будьте внимательны и не допускайте непроверенные данные к этому механизму — это может создать проблемы с безопасностью.
Массовое присваивание можно безопасно использовать, если в модели определено свойство $accessible. В этом видеоролике наглядно продемонстрировано, чем может обернуться массовое присваивание, если для него используются внешние данные. — прим. пер.
Как насчёт изменения записи? Это просто — сначала нам нужно её выбрать, а затем изменить:
$user = User::where('name', '=', 'Дон Жуан')->first();
Здесь мы используем first() для получения одного объекта в результате запроса — мы не можем изменить все значения в массиве разом и нам прошлось бы использовать цикл и пример бы получился более длинным.
$user->mojo = '5%';
$user->save();
Я не смог забрать всё его обаяние, это было бы не честно — по крайней мере теперь он может прийти на вечеринку. В общем, вы видите, что обновление записи происходит точно так же, как и её добавление за исключением того, что сперва нам нужно найти объект, который следует обновить.
Помните, как мы создали поля updated_at и created_at с помощью PHP$table->timestamps()
в статье про миграции? Eloquent автоматически устанавливает эти поля в текущее время (timestamp) когда вы добавляете или обновляете запись. Для отключения этой возможности просто установите статическое свойство $timestamps класса модели в false:
class User extends Eloquent {
public static $timestamps = false;
}
Зависимости (отношения)
Зависимости в Eloquent — красивейшая вещь. Больше не нужно этих JOIN, мы можем явно определять отношения «один к одному», «один ко мноим» и «многие ко многим» простым вызовом методов в объекте модели. Давайте начнём с кода — так вы научитесь гораздо быстрее.
Один к одному
class User extends Eloquent {
public function invite() {
return $this->has_one('Invite');
}
}
Давайте предположим, что мы уже сделали таблицу invites и модель PHPInvite
, где мы будем хранить приглашения на нашу вечеринку.
Теперь мы можем выдать приглашение пользователю с помощью простого и выразительного синтаксиса:
$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() (или другой метод определения отношений) второй параметр — имя поля. Например:
class User extends Eloquent {
public function invite() {
return $this->has_one('Invite', 'the_invite_id');
}
}
Мы также можем определить обратную зависимость — скажем, нам нужно определять владельца приглашения. Для этого в модели используется метод belongs_to():
class Invite extends Eloquent {
public function user() {
return $this->belongs_to('User');
}
}
Таким образом, это похожий на PHPhas_one()
синтаксис, но использующий PHPbelongs_to()
для указания, что в этой таблице существует внешний ключ.
$user = Invite::find(1)->user()->first();
Один ко многим
Что, если мы хотим связать многие записи с одним полем? Как вы могли уже догадаться по заголовку, для этого есть специальный метод. Давайте посмотрим (я так часто говорю это, верно? Возможно, это могло бы стать моим девизом):
Class User extends Eloquent {
public function hats() {
return $this->has_many('Hat');
}
}
Мы вновь передаём методу имя класса в виде строки, начинающейся с заглавной буквы.
В этом примере таблице hats понадобится внешний ключ user_id, после чего мы сможем сделать так:
$hats = User::find(1)->hats()->get();
Мы можем использовать «магические свойства» PHP для получения того же результата в краткой форме:
$hats = User::find(1)->hats;
Как и до этого, вы можете передать имя другого ключа в качестве второго параметра, чтобы использовать именно его. Двинемся дальше.
Речь, видимо, идёт о вызове PHP$this->has_many('Hat', 'the_hat_id');
— см. выше. — прим. пер.
Многие ко многим
Здесь всё немного более сложно, чем до этого, но не волнуйтесь — я здесь, чтобы провести вас; возьмите мою руку... Представим, что у нас есть две модели Eloquent — User и Task; пользователь (PHPUser
) имеет множество задач («task»), а каждая задача может иметь множество пользователей. Для этого типа отношений нам нужна третья таблица.
Эту таблицу называют по-разному: центральная таблица («pivot table»), таблица-индекс («lookup table»), промежуточная таблица («intermediate table») и т.п.
На самом же деле это просто таблица с двумя числовыми полями — user_id и task_id, которые связывают две таблицы вместе, образуя зависимость многие ко многим.
Название таблицы состоит из названий обоих связуемых таблиц во множественном числе, разделённых подчёркиванием — причём они стоят в алфавитном порядке: tasks_users.
Отлично, теперь у нас есть эта таблица, так что давайте создадим саму зависимость:
class User extends Eloquent {
public function tasks() {
return $this->has_many_and_belongs_to('Task');
}
}
Как обычно, прежний синтаксис, только с более длинным именем метода. По аналогии с одношениями один к одному и один ко многим мы можем передать ему второй параметр — здесь он будет обозначать имя связующей таблицы (tasks_users).
Связанные модели
Добавляя записи в таблицы с зависимостями мы могли бы установить PHPuser_id
или PHPhat_id
в нужные значения вручную, но это не очень элегантно — почему бы просто не передать вместо них объект?
$hat = Hat::find(1);
$user = User::find(1);
$user->hats()->insert($hat);
Так оно выглядит намного лучше — вместо обращения к числовым внешним ключам напрямую мы просто передаём сами объекты.
С зависимостью один ко многим вы можете передать массив пар PHPполе => значение
в метод save() для добавления или обновления зависимой записи. Например:
$hat = array(
'name' => 'Dennis',
'style' => 'Fedora'
);
$user = User::find(1);
$user->hats()->save($hat);
Если мы добавляем запись в таблицу с зависимостью многие ко многим с помощью insert(), Eloquent не только создаст саму запись, но и обновит связующую таблицу:
$hat = array(
'name' => 'Dennis',
'style' => 'Fedora'
);
$user = User::find(1);
$user->hats()->insert($hat);
Как быть, если объекты (записи) уже существуют и мы просто хотим создать новое отношение между ними? Это делается с помощью метода attach(), который принимает ID записи или её объект. Давайте посмотрим:
$user->hats()->attach($hat_id);
Связующая таблица будет обновлена автоматически.
Дальше у нас метод, о котором я сам только недавно узнал — судя по всему, он появился в Eloquent с Laravel 3.1. Мы можем использовать метод sync(), передав ему массив ID записей — после его выполнения в связующей таблице останутся только переданные ID. Это весьма удобно — вот пример:
$user->hats()->sync(array(4, 7, 8));
Как именно работает sync() было сказано в одном из комментариев к оригинальной статье: "PHPsync()
удалит ID, не перечисленные во входном массиве, а также добавит новые ID, которых изначально не было. При этом он не изменит timestamp-поля для существующих записей, что удобно при отслеживании времени, когда отношение было впервые создано. — Eric Barnes". — прим. пер.
Связующие таблицы
Что, если мы хотим получить доступ к связующей таблице напрямую, а не только к таблицам, которые она связывает? Для этого Eloquent предоставляет метод pivot():
$pivot = $user->hats()->pivot();
Теперь у нас есть массив объектов-строк — точно так же, как и после других запросов, только здесь они ссылаются на записи-отношения шляп пользователей (hats_users).
Что, если мы хотим получить конкретную запись, которая используется для взаимоотношения записей? Это легко делается с помощью «магических свойств» pivot() — официальная документация содержит отличный пример на эту тему и я позаимствую его — хотя я немного изменив, примерно так же, как вы делали со своими домашними заданиями в средней школе:
$user = User::find(1);
foreach ($user->hats as $hat) {
echo $hat->pivot->created_at;
}
Нам доступно поле created_at, содержащее время (timestamp) создания этого отношения.
Ладно, мне уже всё это порядком надоело — на самом деле я хочу удалить все свои шляпы, все до единой. К счастью, я помню свой пользовательский ID — это число 7, счастливое число 7. Так что давайте сделаем это прямо сейчас, давайте delete() все мои шляпы:
User::find(1)->hats()->delete();
Это произошло, отныне у меня всегда будет непокрытая голова — но это стоит того, ведь теперь вы знаете, как использовать метод delete().
Активная загрузка
Существует два термина — «lazy loading» и «eager loading»; первый знаком и легко переводим — «ленивая загрузка» или «загрузка по требованию», в то время как последний можно перевести как «активная загрузка» или «предзагрузка». — прим. пер.
Eloquent даёт нам возможность использовать активные загрузки для разрешения извечного спора о проблеме PHPN+1
. Если вы ещё не знаете, что это такое, позвольте мне разложить всё по полочкам на основе того, что вы уже узнали. Посмотрим на следующий пример:
$users = User::all();
foreach ($users as $user) {
echo $user->hat->size();
}
Для каждого цикла Eloquent должен выполнить новый SQL-запрос для получения шляпы (hat) очередного пользователя, и вернуть её размер (size). Это не представляет серьёзной проблемы на маленьких наборах данных, но с сотнями записей это может серьёзно повлиять на производительность. С помощью активной загрузки мы можем сообщить Eloquent, что при получении объекта PHPUser
он должен выполнить запрос PHPSELECT * FROM hats
— таким образом у нас будут все данные, которые нам нужны. Это снизит число запросов до двух — как раз то, что нам нужно.
Посмотрим, как мы можем активно загрузить набор отношений:
$users = User::with('hat')->get();
Хорошо, теперь зависимость hat загружена. Если вы хотите загрузить несколько отношений просто передайте методу with() массив. Обратите внимание, что это статический метод, поэтому он всегда должен быть в начале «цепочки».
Вы можете использовать активную загрузку на вложенных зависимостяхх. Допустим, наш объект Hat имеет зависимость hatstand() («вешалка»), с помощью которой мы узнаём, на какой вешалке висит нужная шляпа. Мы можем загрузить оба этих отношения таким образом:
$users = User::with(array('hat', 'hat.hatstand'))->get();
Просто поставьте имя вложенного отношения после имени объекта с точкой.
Теперь мы добавим в загрузку некоторое условие — скажем, нам нужны только синие шляпы. Конечно, Eloquent позволяет это сделать! Просто передадим имя отношения как ключ в массиве, а значением будет анонимная функция (Closure), которая установит нужные критерии. Лучше всего это видно на примере:
$users = User::with(array('hat' => function ($query) {
$query->where('color', '=', 'blue');
}))->get();
Вы можете добавить столько условий, сколько вам нужно.
Чтение и запись
Методы set и get позволяют нам преобразовывать значения полей модели, когда происходит их записи или чтение. Давайте посмотрим на следующий код:
public function set_password($password) {
$this->set_attribute('hashed_password', Hash::make($password));
}
Мы создаём метод с именем, начинающимся с set_, после которого идёт имя поле; метод принимает параметр PHP$password
— значение поля. Мы использовуем метод PHPset_attribute()
для установки поля в новое значение, таким образом мы можем преобразовать его нужным образом. В примере выше полю hashed_password будет присвоен хеш пароля, а не сам пароль:
$user->password = "secret_panda_ninja";
Это очень удобная возможность.
Методы get — противоположность методам set, они используются для изменения возвращаемого значения. Например:
public function get_panda_name() {
return 'Panda'.$this->get_attribute('name');
}
Теперь если имя записи — «Dayle», мы можем вызвать:
echo $user->panda_name;
Комментарии (1)
а как такой запрос написать 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 понятно, а вот далеше не понимаю