{{TOC}} **ORM** - довольно полезная штука. ORM расшифровывается как "Объектно-реляционное отображение" ("Object Relational Mapper") - звучит запутанно, верно? Давайте разобъём его на части (//барабанная дробь//): **отображение** означает, что мы связываем наши PHP-**объекты**, или классы, с таблицами и строчками базы данных. При чём здесь **реляционное** станет понятно в разделе об ((#зависимост+и))ях. Существует множество ORM-библиотек, но нет ни одной такой же красноречивой как... как сам //Eloquent// (!!(tl_note) игра слов - название "eloquent" переводится как "красноречивый". - //прим. пер.//!!). //Eloquent// входит в ядро Laravel, поэтому может быть использован в стандартном комплекте. В данной статье я предполагаю, что вы уже знакомы с ((52 миграциями)) и с моей статьёй "((50))" - вам понадобятся эти знания. Хотя //Eloquent// - альтернатива для **Fluent**, они имеют много общих методов и для нас разница только в том, что //Eloquent// возвращает нам результаты запросов в виде собственного объекта, а не стандартного объекта PHP **stdClass** - это делает наш код ещё чище. Итак, приступим к изучению //модели Eloquent//. == Создание и использование модели == %% class User extends Eloquent { } %% В общем-то это всё. Нет, я серьёзно. Видите ли, //Eloquent// доверяет вам - он знает, что вы уже успели создать нужную //((52 миграцию))// для таблицы **users**, он не будет вам сообщать, какие поля у неё есть - он верит, что вы их знаете, ведь вы должны знать, ведь вы выполнили //миграцию// и создали нужную таблицу. Ах, кстати, вы ведь не забыли добавить в неё %%increments('id')%%? Это обычная практика и она требуется //Eloquent// для работы. Как вы видите, класс этой //модели// называется **User**, а её таблица - **users**. Это не опечатка - //Eloquent// различает слова в единственном и множественном числе для английского языка (!!(tl_note) см. %%Plurizer%% - //прим. пер.//!!). Наш объект - единственного числа, поэтому мы назвали его **User**, но наша таблица содержит //множество// пользователей - поэтому мы назвали её **users**. //Laravel// ищет таблицу в БД по имени класса модели во множественном числе. Преобразование из одиночной формы слова во множественную работает только для английского языка, но если вы хотите добавить поддержку другого - добавьте формы нужного слова в массив в файле %%(t) application/config/strings.php%% и впредь оно будет преобразовываться, как нужно. Хотя, возможно, вам не нравится, что таблица названа во множественном числе? Нет проблем, просто определите статическое свойство **$table** и укажите в нём нужное имя: %% class User extends Eloquent { public static $table = 'app_users' } %% Отлично, вы наверное уже устали набирать это громоздкое определение модели (пожалуйста, обратите внимание на сарказм)... Давайте теперь попробуем её использовать: %% $user = User::find(1); %% Постойте, мы ведь уже это видели? Помните %%find()%% из ((50 статьи про //Fluent//)) - он возвращает одну строку результата по значению первичного ключа. Как вы видите, //Eloquent// использует множество методов, предоставляемых //Fluent// - это очень удобно, потому что вы можете теперь использовать обе библиотеки для работы с БД без раздумий над тем, какой метод чему принадлежит. Хорошо! Для использования //Eloquent// достаточно просто написать имя объекта, %%DB::table()%% для этого не требуется. Вы можете использовать полученный результат-объект похожим способом - хотя, сказать по правде, вы сейчас используете объект //Eloquent//, но в данном случае нет никакой разницы. Давайте прочитаем имя пользователя: %% echo $user->name; %% Верно, всё точно так же, как и в прошлый раз - коротко и ясно. Что, если мы захотим получить наш объект %%User%% в виде массива? Просто вызовем метод **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()**, связанный с ((50#uw50-trig триггером)) **get()** - ничего нового, лишь больше лаконичности. Я мог бы перечислить все методы для ((50#uw50-trig получения)) результата, но мне не нужно этого делать - они все уже были описаны в статье про //((50 Fluent))// - замените все "Fluent" на "Eloquent" и вы получите нужный результат. Вместо этого давайте посмотрим, что появилось нового. Создание и обновление объектов (записей) стало куда лучше: %% $user = new User(); $user->name = 'Дон Жуан'; $user->mojo = '100%'; $user->save(); %% Мы создали новый объект %%User%%, установили его свойства в нужные значения существующих полей (нам не нужно задавать ID, //Eloquent// сделает это сам) и вызываем метод **save()** для записи изменений в БД. Этот шаблон проектирования известен под названием ((ВП:ActiveRecord)) и он очень удобен для работы со строками, потому что они такие же объекты, как и все остальные в нашем приложении. Звучит так, как будто база данных и весь этот ужасный SQL просто не существуют. ((#mass)) Если у нас уже есть массив с парами %%поле => значение%%, описывающий нашего друга "Дон Жуана", то мы можем либо передать его в конструктор объекта, либо использовать метод //массового присваивания// - **fill()** - для добавления данных в БД. Например: %% $user = new User(array( 'name' => 'Дон Жуан', 'mojo' => '100%' )); $user->save(); %% Или: %% $arr = array( 'name' => 'Дон Жуан', 'mojo' => '100%' ); $user = new User(); $user->fill($arr); $user->save(); %% Будьте внимательны и не допускайте непроверенные данные к этому механизму - это может создать проблемы с безопасностью. .(tl_note) Массовое присваивание можно безопасно использовать, если в модели определено свойство **$accessible**. В ((http://heybigname.com/2012/03/08/mass-assignment-security-vulnerability-explanation/ этом видеоролике)) наглядно продемонстрировано, чем может обернуться массовое присваивание, если для него используются внешние данные. - //прим. пер.// Как насчёт изменения записи? Это просто - сначала нам нужно её //выбрать//, а затем изменить: %% $user = User::where('name', '=', 'Дон Жуан')->first(); %% Здесь мы используем **first()** для получения одного объекта в результате запроса - мы не можем изменить все значения в массиве разом и нам прошлось бы использовать цикл и пример бы получился более длинным. %% $user->mojo = '5%'; $user->save(); %% Я не смог забрать всё его обаяние, это было бы не честно - по крайней мере теперь он может прийти на вечеринку. В общем, вы видите, что обновление записи происходит точно так же, как и её добавление за исключением того, что сперва нам нужно найти объект, который следует обновить. ((#timestamp)) Помните, как мы создали поля %%(t)updated_at%% и %%(t)created_at%% с помощью %%$table->timestamps()%% в статье про //((52 миграции))//? //Eloquent// автоматически устанавливает эти поля в текущее время (//timestamp//) когда вы добавляете или обновляете запись. Для отключения этой возможности просто установите статическое свойство **$timestamps** класса модели в **false**: %% class User extends Eloquent { public static $timestamps = false; } %% == Зависимости (отношения) == **Зависимости** в //Eloquent// - красивейшая вещь. Больше не нужно этих %%(t)JOIN%%, мы можем явно определять отношения "один к одному", "один ко мноим" и "многие ко многим" простым вызовом методов в объекте модели. Давайте начнём с кода - так вы научитесь гораздо быстрее. === ((#oo)) Один к одному === Начнём: %% class User extends Eloquent { public function invite() { return $this->has_one('Invite'); } } %% Давайте предположим, что мы уже сделали таблицу %%(t)invites%% и модель %%Invite%%, где мы будем хранить приглашения на нашу вечеринку. Теперь мы можем выдать приглашение пользователю с помощью простого и выразительного синтаксиса: %% $invite = User::find(1)->invite()->first(); %% Мы снова используем метод **first()**, потому что нам нужен только один результат. Добавив метод **invite()** к нашей //((50 цепочке вызовов))// мы, конечно, имели в виду название //отношения// (зависимости) - таким образом, мы получили объект %%Invite%%, который относится к нашему пользователю (%%(t)user%%). Пример выше выполнит два SQL-запроса: %%(sql) SELECT * FROM "users" WHERE "id" = 1; SELECT * FROM "invites" WHERE "user_id" = 1; %% Мы видим, что //Eloquent// автоматически ищет нужный %%(t)user_id%% по //внешнему ключу// ("foreign key"), поэтому если нам нужна эта зависимость, то требуется добавить числовое поле %%(t)user_id%% в нашу //миграцию// для таблицы %%(t)invites%%. ((#the_id)) Как быть, если мы хотим назвать наш //внешний ключ// иначе? В этом случае мы должны передать в метод **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'); } } %% Таким образом, это похожий на %%has_one()%% синтаксис, но использующий %%belongs_to()%% для указания, что в этой таблице существует //внешний ключ//. Теперь мы можем сделать так: %% $user = Invite::find(1)->user()->first(); %% Прощё простого! === ((#om)) Один ко многим === Что, если мы хотим связать многие записи с одним полем? Как вы могли уже догадаться по заголовку, для этого есть специальный метод. Давайте посмотрим (я так часто говорю это, верно? Возможно, это могло бы стать моим девизом): %% 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; %% А так ещё лучше! Как и до этого, вы можете передать имя другого ключа в качестве второго параметра, чтобы использовать именно его. Двинемся дальше. .(tl_note) Речь, видимо, идёт о вызове %%$this->has_many('Hat', 'the_hat_id');%% - см. ((#the_id выше)). - //прим. пер.// === ((#mm)) Многие ко многим === Здесь всё немного более сложно, чем до этого, но не волнуйтесь - я здесь, чтобы провести вас; возьмите мою руку... Представим, что у нас есть две модели //Eloquent// - %%(t)User%% и %%(t)Task%%; //пользователь// (%%User%%) имеет множество задач ("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'); } } %% Как обычно, прежний синтаксис, только с более длинным именем метода. По ((#the_id аналогии)) с одношениями //один к одному// и //один ко многим// мы можем передать ему **второй параметр** - здесь он будет обозначать имя //связующей таблицы// (%%(t)tasks_users%%). == Связанные модели == Добавляя записи в таблицы с ((#зависимост+и))ями мы могли бы установить %%user_id%% или %%hat_id%% в нужные значения вручную, но это не очень элегантно - почему бы просто не передать вместо них объект? %% $hat = Hat::find(1); $user = User::find(1); $user->hats()->insert($hat); %% Так оно выглядит намного лучше - вместо обращения к числовым //внешним ключам// напрямую мы просто передаём сами объекты. С зависимостью //((#om один ко многим))// вы можете передать массив пар %%поле => значение%% в метод **save()** для добавления или обновления зависимой записи. Например: %% $hat = array( 'name' => 'Dennis', 'style' => 'Fedora' ); $user = User::find(1); $user->hats()->save($hat); %% Коротко и понятно :) Если мы добавляем запись в таблицу с зависимостью //((#mm многие ко многим))// с помощью **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)); %% .(tl_note) Как именно работает **sync()** было сказано в одном из комментариев к оригинальной статье: "%%sync()%% удалит ID, не перечисленные во входном массиве, а также добавит новые ID, которых изначально не было. При этом он не изменит ((#timestamp))-поля для существующих записей, что удобно при отслеживании времени, когда отношение было впервые создано. - //Eric Barnes//". - //прим. пер.// == Связующие таблицы == Что, если мы хотим получить доступ к связующей таблице напрямую, а не только к таблицам, которые она связывает? Для этого //Eloquent// предоставляет метод **pivot()**: %% $pivot = $user->hats()->pivot(); %% Теперь у нас есть массив объектов-строк - точно так же, как и после других запросов, только здесь они ссылаются на записи-отношения шляп пользователей (%%(t) 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()**. == Активная загрузка == .(tl_note) Существует два термина - "lazy loading" и "eager loading"; первый знаком и легко переводим - "ленивая загрузка" или "загрузка по требованию", в то время как последний можно перевести как "активная загрузка" или "предзагрузка". - //прим. пер.// //Eloquent// даёт нам возможность использовать **активные загрузки** для разрешения извечного спора о проблеме %%N+1%%. Если вы ещё не знаете, что это такое, позвольте мне разложить всё по полочкам на основе того, что вы уже узнали. Посмотрим на следующий пример: %% $users = User::all(); foreach ($users as $user) { echo $user->hat->size(); } %% Для каждого цикла //Eloquent// должен выполнить новый SQL-запрос для получения //шляпы// (**hat**) очередного пользователя, и вернуть её //размер// (**size**). Это не представляет серьёзной проблемы на маленьких наборах данных, но с сотнями записей это может серьёзно повлиять на производительность. С помощью //активной загрузки// мы можем сообщить //Eloquent//, что при получении объекта %%User%% он должен выполнить запрос %%SELECT * 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)); } %% Мы создаём метод с именем, начинающимся с %%(t)set_%%, после которого идёт имя поле; метод принимает параметр %%$password%% - значение поля. Мы использовуем метод %%set_attribute()%% для установки поля в новое значение, таким образом мы можем преобразовать его нужным образом. В примере выше полю %%(t)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; %% ...и в результате получим строку "PandaDayle".