{{TOC}} {{DOCVER 4.0=0da300f6445bec5a70d007f503834fce957b065b 16.10.2014 04:19:00, 4.1=efd541a0b218b1c6aafb73f0051c18ed150e3c24 25.05.2014 05:21:00, 4.2=d7b13440c003218ed79e9d508706eca01990122f 4.12.2014 5:01:15}} == Введение == Система объектно-реляционного отображения ([[ВП:ORM]]) Eloquent - красивая и простая реализация шаблона ((ВП:ActiveRecord)) в Laravel для работы с базами данных. Каждая таблица имеет соответствующий класс-модель, который используется для работы с этой таблицей. Прежде чем начать настройте ваше соединение с БД в файле %%(t)app/config/database.php%%. == Простейшее использование == Для начала создадим модель Eloquent. Модели обычно располагаются в папке %%(t)app/models%%, но вы можете поместить в любое место, в котором работает автозагрузчик в соответствии с вашим файлом %%(t)composer.json%%. **Создание модели Eloquent** %% class User extends Eloquent {} %% Заметьте, что мы не указали, какую таблицу Eloquent должен привязать к нашей модели. Если это имя не указано явно, то будет использовано имя класса в нижнем регистре и во множественном числе. В нашем случае Eloquent предположит, что модель %%User%% хранит свои данные в таблице %%(t)users%%. Вы можете указать произвольную таблицу, определив свойство %%$table%% в классе модели: %% class User extends Eloquent { protected $table = 'my_users'; } %% .(alert) **Внимание:** Eloquent также предполагает, что каждая таблица имеет первичный ключ с именем %%id%%. Вы можете определить свойство %%$primaryKey%% для изменения этого имени. Аналогичным образом, вы можете определить свойство %%$connection%% для задания имени подключения к БД, которое должно использоваться при работе с данной моделью. Как только модель определена, у вас всё готово для того, чтобы можно было выбирать и создавать записи. Обратите внимание, что вам нужно создать в этой таблице поля %%(t)updated_at%% и %%(t)created_at%%. Если вы не хотите, чтобы они были автоматически используемы, установите свойство %%$timestamps%% класса модели в %%false%%. **Получение всех моделей (записей)** %% $users = User::all(); %% **Получение записи по первичному ключу** %% $user = User::find(1); var_dump($user->name); %% .(alert) Все методы, доступные в ((docs/v4/queries конструкторе запросов)), также доступны при работе с моделями Eloquent. **Получение модели по первичному ключу с возбуждением исключения** Иногда вам нужно возбудить исключение, если определённая модель не была найдена, что позволит вам его отловить в обработчике %%App::error()%% и вывести страницу 404 ("Не найдено"). %% $model = User::findOrFail(1); $model = User::where('votes', '>', 100)->firstOrFail(); %% Для регистрации обработчика ошибки подпишитесь на событие %%(t)ModelNotFoundException%%: %% use Illuminate\Database\Eloquent\ModelNotFoundException; App::error(function (ModelNotFoundException $e) { return Response::make('Not Found', 404); }); %% **Построение запросов в моделях Eloquent** %% $users = User::where('votes', '>', 100)->take(10)->get(); foreach ($users as $user) { var_dump($user->name); } %% **Агрегатные функции в Eloquent** Конечно, вам также доступны ((docs/v4/queries#агрегатные)) функции. %% $count = User::where('votes', '>', 100)->count(); %% Если у вас не получается создать нужный запрос с помощью конструктора, то можно использовать метод %%whereRaw()%%: %% $users = User::whereRaw('age > ? and votes = 100', array(25))->get(); %% %%(DOCNEW 4.1=efd541a0b218b1c6aafb73f0051c18ed150e3c24 25.05.2014 05:21:00) **Разделение результата на блоки** Если вам нужно обработать много (тысячи) записей Eloquent, использование команды %%chunk%% (!!(tl_note) блок - //прим. пер.//!!) позволит вам избежать заполнения всей вашей оперативной памяти: ~%% User::chunk(200, function($users) { foreach ($users as $user) { // } }); ~%% Первый передаваемый в метод аргумент - число записей, получаемых в одном блоке. Передаваемая в качестве второго аргумента функция-замыкание будет вызываться для каждого блока, получаемого из БД. %% **Указание имени соединения с БД** Иногда вам нужно указать, какое подключение должно быть использовано при выполнении запроса Eloquent - просто используйте метод %%on()%%: %% $user = User::on('имя-соединения')->find(1); %% == Массовое заполнение == При создании новой модели вы передаёте её конструктору массив атрибутов. Эти атрибуты затем присваиваются модели через массовое заполнение. Это удобно, но в то же время представляет **серьёзную** проблему с безопасностью, когда вы передаёте ввод от клиента в модель без проверок - в этом случае пользователь может изменить **любое** и **каждое** поле вашей модели. По этой причине по умолчанию Eloquent защищает вас от массового заполнения. Для начала определите в классе модели свойство %%$fillable%% или %%$guarded%%. **Указание доступных к заполнению атрибутов** Свойство %%$fillable%% указывает, какие поля должны быть доступны при массовом заполнении. Их можно указать на уровне класса или объекта. %% class User extends Eloquent { protected $fillable = array('first_name', 'last_name', 'email'); } %% В этом примере только три перечисленных поля будут доступны массовому заполнению. **Указание охраняемых (//guarded//) атрибутов модели** Противоположность %%$fillable%% - свойство %%$guarded%%, которое содержит список запрещённых к заполнению полей: %% class User extends Eloquent { protected $guarded = array('id', 'password'); } %% .(alert) При использовании %%guarded%%, вы по-прежнему не должны передавать %%Input::get()%% или любые сырые массивы пользовательского ввода в методы %%save%% и %%update%%, потому что может быть обновлён любой незащищённый столбец. **Защита всех атрибутов от массового заполнения** В примере выше атрибуты %%id%% и %%password%% **не могут** быть присвоены через массовое заполнение. Все остальные атрибуты - могут. Вы также можете запретить все атрибуты для заполнения, используя символ %%*%%: %% protected $guarded = array('*'); %% == Вставка, обновление, удаление == Для создания новой записи в БД просто создайте экземпляр модели и вызовите метод %%save()%%. **Сохранение новой модели** %% $user = new User; $user->name = 'Джон'; $user->save(); %% .(alert) **Внимание:** обычно ваши модели Eloquent содержат автоматические числовые ключи (//autoincrementing//). Однако если вы хотите использовать собственные ключи, установите свойство %%$incrementing%% класса модели в значение %%false%%. Вы также можете использовать метод %%create()%% для создания и сохранения модели одной строкой. Метод вернёт добавленную модель. Однако перед этим вам нужно определить либо свойство %%$fillable%%, либо %%$guarded%% в классе модели, так как изначально все модели Eloquent защищены от ((#массовое массового заполнения)). После сохранения или создания новой модели, использующей автоматические (//autoincrementing//) ID, вы можете получать ID объектов, обращаясь к их атрибуту %%id%%: %% $insertedId = $user->id; %% **Установка охранных свойств модели** %% class User extends Eloquent { protected $guarded = array('id', 'account_id'); } %% **Создание модели** %% // Создание нового пользователя в БД... $user = User::create(array('name' => 'Джон')); // Получение пользователя по свойствам, или его создание, если такого не существует... $user = User::firstOrCreate(array('name' => 'Джон')); // Получение пользователя по свойствам, или создание нового экземпляра... $user = User::firstOrNew(array('name' => 'Джон')); %% .(tl_note) Методы %%firstOrCreate%% и %%firstOrNew%% появились в версии 4.1 - //прим. пер.// **Обновление полученной модели** Для обновления модели вам нужно получить её, изменить атрибут и вызвать метод %%save()%%: %% $user = User::find(1); $user->email = 'john@foo.com'; $user->save(); %% **Сохранение модели и её отношений** Иногда вам может быть нужно сохранить не только модель, но и все её ((#отношения)). Для этого просто используйте метод %%push()%%: %% $user->push(); %% Вы также можете выполнять обновления в виде запросов к набору моделей: %% $affectedRows = User::where('votes', '>', 100)->update(array('status' => 2)); %% .(alert) При обновлении набора моделей с помощью конструктора запросов Eloquent никакие события моделей не срабатывают. **Удаление существующей модели** Для удаления модели вызовите метод %%delete()%% на её объекте: %% $user = User::find(1); $user->delete(); %% **Удаление модели по ключу** %% User::destroy(1); User::destroy(array(1, 2, 3)); User::destroy(1, 2, 3); %% Конечно, вы также можете выполнять удаление на наборе моделей: %% $affectedRows = User::where('votes', '>', 100)->delete(); %% **Обновление времени изменения модели** Если вам нужно просто обновить время изменения записи - используйте метод %%touch()%%: %% $user->touch(); %% === Мягкое удаление == Когда вы "мягко" удаляете модель, она на самом деле остаётся в базе данных, однако устанавливается её поле %%(t)deleted_at%%. %%(DOCNEW 4.2=d7b13440c003218ed79e9d508706eca01990122f 4.12.2014 5:01:15) Для включения мягких удалений на модели примените к ней %%SoftDeletingTrait%% ~%% use Illuminate\Database\Eloquent\SoftDeletingTrait; class User extends Eloquent { use SoftDeletingTrait; protected $dates = ['deleted_at']; } ~%% %% %%(DOCNEW 4.0=0da300f6445bec5a70d007f503834fce957b065b 16.10.2014 04:19:00, 4.1=efd541a0b218b1c6aafb73f0051c18ed150e3c24 25.05.2014 05:21:00) Для включения мягких удалений на модели определите её свойство %%$softDelete%%: ~%% class User extends Eloquent { protected $softDelete = true; } ~%% %% Для добавления поля %%(t)deleted_at%% к таблице можно использовать метод %%softDeletes()%% из ((docs/v4/migrations миграции)): %% $table->softDeletes(); %% Теперь когда вы вызовите метод %%delete()%%, поле %%(t)deleted_at%% будет установлено в значение текущего времени. При запросе моделей, использующих мягкое удаление, "удалённые" модели не будут включены в результат запроса. **Включение удалённых моделей в результат выборки** Для отображения всех моделей, в том числе удалённых, используйте метод %%withTrashed()%%: %% $users = User::withTrashed()->where('account_id', 1)->get(); %% Метод %%withTrashed%% может быть использован в ((#отношения+))х: %% $user->posts()->withTrashed()->get(); %% Если вы хотите получить **только** удалённые модели, вызовите метод %%onlyTrashed()%%: %% $users = User::onlyTrashed()->where('account_id', 1)->get(); %% Для восстановления мягко удалённой модели в активное состояние используется метод %%restore()%%: %% $user->restore(); %% Вы также можете использовать его в запросе: %% User::withTrashed()->where('account_id', 1)->restore(); %% Как и метод %%withTrashed%%, метод %%restore()%% можно использовать и в ((#отношения+))х: %% $user->posts()->restore(); %% Если вы хотите полностью удалить модель из БД, используйте метод %%forceDelete()%%: %% $user->forceDelete(); %% Он также работает с ((#отношения+))ми: %% $user->posts()->forceDelete(); %% Для того, чтобы узнать, удалена ли модель, можно использовать метод %%trashed()%%: %% if ($user->trashed()) { // } %% == Поля времени == По умолчанию Eloquent автоматически поддерживает поля %%(t)created_at%% и %%(t)updated_at%%. Просто добавьте эти //timestamp//-поля к таблице и Eloquent позаботится об остальном. Если вы не хотите, чтобы он поддерживал их, добавьте свойство %%$timestamps%% к классу модели. **Отключение автоматических полей времени** %% class User extends Eloquent { protected $table = 'users'; public $timestamps = false; } %% **Использование собственного формата времени** Для настройки формата времени перекройте метод %%getDateFormat()%%: %% class User extends Eloquent { protected function getDateFormat() { return 'U'; } } %% == Заготовки запросов == **Создание заготовки запроса** Заготовки позволяют вам повторно использовать логику запросов в моделях. Для создания заготовки просто начните имя метода со %%(t)scope%%: %% class User extends Eloquent { public function scopePopular($query) { return $query->where('votes', '>', 100); } public function scopeWomen($query) { return $query->whereGender('W'); } } %% **Использование заготовки** %% $users = User::popular()->women()->orderBy('created_at')->get(); %% **Динамические заготовки** Иногда вам может потребоваться определить заготовку, которая принимает параметры. Для этого просто добавьте эти параметры к методу заготовки: %% class User extends Eloquent { public function scopeOfType($query, $type) { return $query->whereType($type); } } %% А затем передайте их при вызове метода заготовки: %% $users = User::ofType('member')->get(); %% %%(DOCNEW 4.2=d7b13440c003218ed79e9d508706eca01990122f 4.12.2014 5:01:15) == Глобальные заготовки (scopes) == Иногда вам требуется определить заготовку, которая будет применяться для всех выполняемых в модели запросов. По сути так и работает "мягкое удаление" в Eloquent. Глобальные заготовки определяются с помощью комбинации типажей PHP и реализации %%(t)Illuminate\Database\Eloquent\ScopeInterface%%. Сначала определим типаж. В этом примере мы будем использовать встроенный в Laravel %%SoftDeletingTrait%%: ~%% trait SoftDeletingTrait { /** * Загрузка типажа мягкого удаления для модели. * * @return void */ public static function bootSoftDeletingTrait() { static::addGlobalScope(new SoftDeletingScope); } } ~%% Если в модели Eloquent используется типаж, содержащий соответствующий соглашению по названиям %%(t)bootNameOfTrait%% метод, тогда этот метод типажа будет вызываться при загрузке модели Eloquent. Это даёт вам возможность зарегистрировать глобальную заготовку, или сделать ещё что-либо необходимое. Заготовка должна реализовывать %%ScopeInterface%%, который содержит два метода: %%apply()%% и %%remove()%%. Метод %%apply()%% принимает объект конструктора запросов %%(t)Illuminate\Database\Eloquent\Builder%% и отвечает за добавление любых дополнительных операторов %%(t)where%%, которые необходимы заготовке. Метод %%remove()%% также принимает объект %%(t)Builder%% и отвечает за отмену действий, произведённых методом %%apply()%%. Другими словами, %%remove()%% должен удалить добавленные операторы %%(t)where%% (или любые другие). Поэтому для нашей %%SoftDeletingScope%% методы будут такими: ~%% /** * Применение заготовки к указанному конструктору запросов Eloquent. * * @param \Illuminate\Database\Eloquent\Builder $builder * @return void */ public function apply(Builder $builder) { $model = $builder->getModel(); $builder->whereNull($model->getQualifiedDeletedAtColumn()); } /** * Удаление заготовки из указанного конструктора запросов Eloquent. * * @param \Illuminate\Database\Eloquent\Builder $builder * @return void */ public function remove(Builder $builder) { $column = $builder->getModel()->getQualifiedDeletedAtColumn(); $query = $builder->getQuery(); foreach ((array) $query->wheres as $key => $where) { // Если оператор where ограничивает мягкое удаление данных, мы удалим его из // запроса и сбросим ключи в операторах where. Это позволит разработчику // включить удалённую модель в отношения результирующего набора, который загружается "лениво". if ($this->isSoftDeleteConstraint($where, $column)) { unset($query->wheres[$key]); $query->wheres = array_values($query->wheres); } } } ~%% %% == Отношения == Конечно, ваши таблицы скорее всего как-то связаны с другими таблицами БД. Например, статья в блоге может иметь много комментариев, а заказ может быть связан с оставившим его пользователем. Eloquent упрощает работу и управление такими отношениями. Laravel поддерживает многие типы связей: 1. ((#oo Один к одному)) 2. ((#om Один ко многим)) 3. ((#mm Многие ко многим)) 4. ((#hmt Ко многим через)) 5. ((#pl Полиморфические связи)) 6. ((#mmp Полиморфические связи "многие ко многим")) === ((#oo)) Один к одному == **Создание связи "один к одному"** Связь вида "один к одному" является очень простой. К примеру, модель %%User%% может иметь один %%Phone%%. Мы можем определить такое отношение в Eloquent: %% class User extends Eloquent { public function phone() { return $this->hasOne('Phone'); } } %% Первый параметр, передаваемый %%hasOne()%% - имя связанной модели. Как только отношение установлено вы можете получить к нему доступ через ((#динамические)) свойства Eloquent: %% $phone = User::find(1)->phone; %% Сгенерированный SQL имеет такой вид: %%(sql) select * from users where id = 1 select * from phones where user_id = 1 %% Заметьте, что Eloquent считает, что поле в таблице называется по имени модели плюс %%(t)_id%%. В данном случае предполагается, что это %%(t)user_id%%. Если вы хотите перекрыть стандартное имя передайте второй параметр методу %%hasOne()%%. Кроме того вы можете передать в метод третий аргумент, чтобы указать, какие локальные столбцы следует использовать для объединения: %% return $this->hasOne('Phone', 'foreign_key'); return $this->hasOne('Phone', 'foreign_key', 'local_key'); %% **Создание обратного отношения** Для создания обратного отношения в модели %%Phone%% используйте метод %%belongsTo()%% ("принадлежит к"): %% class Phone extends Eloquent { public function user() { return $this->belongsTo('User'); } } %% В примере выше Eloquent будет искать поле %%(t)user_id%% в таблице %%(t)phones%%. Если вы хотите назвать внешний ключ по другому, передайте это имя вторым параметром к методу %%belongsTo()%%: %% class Phone extends Eloquent { public function user() { return $this->belongsTo('User', 'local_key'); } } %% Кроме того, вы передаёте третий параметр, который определяет имя связанного столбца в родительской таблице: %% class Phone extends Eloquent { public function user() { return $this->belongsTo('User', 'local_key', 'parent_key'); } } %% === ((#om)) Один ко многим == Примером отношения "один ко многим" является статья в блоге, которая имеет "много" комментариев. Вы можете смоделировать это отношение таким образом: %% class Post extends Eloquent { public function comments() { return $this->hasMany('Comment'); } } %% Теперь мы можем получить все комментарии с помощью ((#динамическ+ие))ого свойства: %% $comments = Post::find(1)->comments; %% Если вам нужно добавить ограничения на получаемые комментарии, можно вызвать метод %%comments()%% и продолжить добавлять условия: %% $comments = Post::find(1)->comments()->where('title', '=', 'foo')->first(); %% Как обычно, вы можете передать второй параметр в метод %%hasMany()%% для перекрытия стандартного имени ключа. И как для отношения "hasOne" также может быть указан локальный столбец: %% return $this->hasMany('Comment', 'foreign_key'); return $this->hasMany('Comment', 'foreign_key', 'local_key'); %% **Определение обратного отношения** Для определения обратного отношения используйте метод %%belongsTo()%%: %% class Comment extends Eloquent { public function post() { return $this->belongsTo('Post'); } } %% === ((#mm)) Многие ко многим == Отношения типа "многие ко многим" - более сложные, чем остальные виды ((#отношени+я))й. Примером может служить пользователь, имеющий много ролей, где роли также относятся ко многим пользователям. Например, один пользователь может иметь роль **admin**. Нужны три таблицы для этой связи: %%(t)users%%, %%(t)roles%% и %%(t)role_user%%. Название таблицы %%(t)role_user%% происходит от упорядоченных по алфавиту имён связанных моделей и она должна иметь поля %%(t)user_id%% и %%(t)role_id%%. Вы можете определить отношение "многие ко многим" через метод %%belongsToMany()%%: %% class User extends Eloquent { public function roles() { return $this->belongsToMany('Role'); } } %% Теперь мы можем получить роли через модель %%User%%: %% $roles = User::find(1)->roles; %% Вы можете передать второй параметр к методу %%belongsToMany()%% с указанием имени связующей (//pivot//) таблицы вместо стандартной: %% return $this->belongsToMany('Role', 'user_roles'); %% Вы также можете перекрыть имена ключей по умолчанию: %% return $this->belongsToMany('Role', 'user_roles', 'user_id', 'foo_id'); %% Конечно, вы можете определить и обратное отношение на модели %%Role%%: %% class Role extends Eloquent { public function users() { return $this->belongsToMany('User'); } } %% %%(DOCNEW 4.1=efd541a0b218b1c6aafb73f0051c18ed150e3c24 25.05.2014 05:21:00) === ((#hmt)) Ко многим через == Связь "ко многим через" обеспечивает удобный короткий путь для доступа к удалённым отношениям через промежуточные. Например, модель %%(t)Country%% может иметь много %%(t)Post%% через модель %%(t)User%%. Таблицы для этих отношений будут выглядеть так: ~%% countries id - integer name - string users id - integer country_id - integer name - string posts id - integer user_id - integer title - string ~%% Несмотря на то, что таблица %%(t)posts%% не содержит столбца %%(t)country_id%%, отношение "hasManyThrough" позволит нам получить доступ к %%(t)posts%% через %%(t)country%% с помощью %%$country->posts%%. Давайте определим отношения: ~%% class Country extends Eloquent { public function posts() { return $this->hasManyThrough('Post', 'User'); } } ~%% Если вы хотите указать ключи отношений вручную, вы можете передать их в качестве третьего и четвертого аргументов метода: ~%% class Country extends Eloquent { public function posts() { return $this->hasManyThrough('Post', 'User', 'country_id', 'user_id'); } } ~%% %% === ((#pl)) Полиморфические отношения == Полиморфические отношения позволяют модели быть связанной с более, чем одной моделью. Например, может быть модель %%Photo%%, содержащая записи, принадлежащие к моделям %%Staff%% и %%Order%%. Мы можем создать такое отношение таким образом: %% class Photo extends Eloquent { public function imageable() { return $this->morphTo(); } } class Staff extends Eloquent { public function photos() { return $this->morphMany('Photo', 'imageable'); } } class Order extends Eloquent { public function photos() { return $this->morphMany('Photo', 'imageable'); } } %% **Чтение полиморфической связи** Теперь мы можем получить фотографии и для персонала, и для заказа: %% $staff = Staff::find(1); foreach ($staff->photos as $photo) { // } %% **Чтение связи на владельце полиморфического отношения** Однако истинная "магия" полиморфизма происходит при чтении связи на модели %%Photo%%: %% $photo = Photo::find(1); $imageable = $photo->imageable; %% Отношение %%imageable%% модели %%Photo%% вернёт либо объект %%Staff%%, либо объект %%Order%% в зависимости от типа модели, которой принадлежит фотография. **Структура таблиц полиморфической связи** Чтобы понять, как это работает, давайте изучим структуру БД для полиморфического отношения: %%(t) staff id - integer name - string orders id - integer price - integer photos id - integer path - string imageable_id - integer imageable_type - string %% Главные поля, на которые нужно обратить внимание: **imageable_id** и **imageable_type** в таблице %%(t)photos%%. Первое содержит ID владельца, в нашем случае - заказа или персонала, а второе - имя класса-модели владельца. Это позволяет ORM определить, какой класс модели должен быть возвращён при использовании отношения %%imageable%%. %%(DOCNEW 4.1=efd541a0b218b1c6aafb73f0051c18ed150e3c24 25.05.2014 05:21:00) === ((#mmp)) Полиморфические связи многие ко многим == **Структура таблиц полиморфической связи многие ко многим** В дополнение к традиционным полиморфическим связям вы можете также задать полиморфические связи многие ко многим. Например, модели блогов %%(t)Post%% и %%(t)Video%% могут разделять полиморфическую связь с моделью %%(t)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 ~%% Далее, мы готовы к установке связи с моделью. Обе модели %%(t)Post%% и %%(t)Video%% будут иметь связь "morphToMany" через метод %%tags%%: ~%% class Post extends Eloquent { public function tags() { return $this->morphToMany('Tag', 'taggable'); } } ~%% Модель %%(t)Tag%% может определить метод для каждого из своих отношений: ~%% class Tag extends Eloquent { public function posts() { return $this->morphedByMany('Post', 'taggable'); } public function videos() { return $this->morphedByMany('Video', 'taggable'); } } ~%% %% === Запросы к отношениям == **Проверка связей при ((docs/v4/queries#выборк+а))е** При чтении отношений модели вам может быть нужно ограничить результаты в зависимости от существования связи. Например, вы хотите получить все статьи в блоге, имеющие хотя бы один комментарий. Для этого можно использовать метод %%has()%%: %% $posts = Post::has('comments')->get(); %% Вы также можете указать оператор и число: %% $posts = Post::has('comments', '>=', 3)->get(); %% %%(DOCNEW 4.1=efd541a0b218b1c6aafb73f0051c18ed150e3c24 25.05.2014 05:21:00) Если вам нужно ещё больше возможностей, вы можете использовать методы %%whereHas%% и %%orWhereHas%%, чтобы поместить условия %%(t)"where"%% в ваши запросы %%(t)has%%: ~%% $posts = Post::whereHas('comments', function($q) { $q->where('content', 'like', 'foo%'); })->get(); ~%% %% == Динамические свойства == Eloquent позволяет вам читать ((#отношения)) через динамические свойства. Eloquent автоматически определит используемую связь и даже вызовет %%get()%% для связей "один ко многим" и %%first()%% - для связей "один к одному". К примеру, для следующей модели %%$phone%%: %% class Phone extends Eloquent { public function user() { return $this->belongsTo('User'); } } $phone = Phone::find(1); %% Вместо того, чтобы получить e-mail пользователя так: %% echo $phone->user()->first()->email; %% ...вызов может быть сокращён до такого: %% echo $phone->user->email; %% .(alert) Отношения, которые возвращают много результатов, вернут экземпляр класса %%Illuminate\Database\Eloquent\Collection%%. == Активная загрузка == Активная загрузка (//eager loading//) призвана устранить проблему запросов //N + 1//. Например, представьте, что у нас есть модель %%Book%% со связью к модели %%Author%%. Отношение определено как: %% class Book extends Eloquent { public function author() { return $this->belongsTo('Author'); } } %% Теперь, у нас есть такой код: %% foreach (Book::all() as $book) { echo $book->author->name; } %% Цикл выполнит один запрос для получения всех книг в таблице, а затем будет выполнять по одному запросу на каждую книгу для получения автора. Таким образом, если у нас 25 книг, то потребуется 26 запросов. К счастью, мы можем использовать активную загрузку для кардинального уменьшения числа запросов. Отношение будет активно загружено, если оно было указано при вызове метода %%with()%%: %% foreach (Book::with('author')->get() as $book) { echo $book->author->name; } %% В цикле выше будут выполнены всего два запроса: %%(sql) select * from books select * from authors where id in (1, 2, 3, 4, 5, ...) %% Разумное использование активной загрузки поможет сильно повысить производительность вашего приложения. Конечно, вы можете загрузить несколько отношений одновременно: %% $books = Book::with('author', 'publisher')->get(); %% Вы даже можете загрузить вложенные отношения: %% $books = Book::with('author.contacts')->get(); %% В примере выше, связь %%author%% будет активно загружена вместе со связью %%contacts%% модели автора. === Ограничения активной загрузки == Иногда вам может быть нужно не только ((#активн+ая))о загрузить отношение, но также указать условие для его загрузки: %% $users = User::with(array('posts' => function($query) { $query->where('title', 'like', '%первое%'); }))->get(); %% В этом примере мы загружаем сообщения пользователя, но только те, заголовок которых содержит подстроку "первое". %%(DOCNEW 4.1=efd541a0b218b1c6aafb73f0051c18ed150e3c24 25.05.2014 05:21:00) Конечно, функции-замыкания активной загрузки не ограничиваются только условиями. Вы также можете применить упорядочивание: ~%% $users = User::with(array('posts' => function($query) { $query->orderBy('created_at', 'desc'); }))->get(); ~%% %% === Ленивая активная загрузка == Возможно активно загрузить связанные модели напрямую из уже созданного набора объектов моделей. Это может быть полезно при определении во время выполнения, требуется ли такая загрузка или нет, или в комбинации с кэшированием. %% $books = Book::all(); $books->load('author', 'publisher'); %% == Вставка связанных моделей == **Создание связанной модели** Часто вам нужно будет добавить связанную модель. Например, вы можете создать новый комментарий к сообщению. Вместо явного указания значения для поля %%(t)post_id%% вы можете вставить модель через её владельца - модели %%Post%%: %% $comment = new Comment(array('message' => 'Новый комментарий.')); $post = Post::find(1); $comment = $post->comments()->save($comment); %% В этом примере поле %%(t)post_id%% вставленного комментария автоматически получит значение ID своей статьи. %%(DOCNEW 4.2=d7b13440c003218ed79e9d508706eca01990122f 4.12.2014 5:01:15) Сохранить несколько связанных моделей можно так: ~%% $comments = array( new Comment(array('message' => 'A new comment.')), new Comment(array('message' => 'Another comment.')), new Comment(array('message' => 'The latest comment.')) ); $post = Post::find(1); $post->comments()->saveMany($comments); ~%% %% === Связывание моделей (//belongs to//) == При обновлении связей %%belongsTo%% ("принадлежит к") вы можете использовать метод %%associate()%%. Он установит внешний ключ на связанной модели: %% $account = Account::find(10); $user->account()->associate($account); $user->save(); %% === Связывание моделей (//многие ко многим//) == Вы также можете вставлять связанные модели при работе с ((#отношения+))ми ((#mm многие ко многим)). Продолжим использовать наши модели %%User%% и %%Role%% в качестве примеров. Вы можете легко привязать новые роли к пользователю методом %%attach()%%. **Связывание моделей "многие ко многим"** %% $user = User::find(1); $user->roles()->attach(1); %% Вы также можете передать массив атрибутов, которые должны быть сохранены в связующей (//pivot//) таблице для этого отношения: %% $user->roles()->attach(1, array('expires' => $expires)); %% Конечно, существует противоположность %%attach()%% - %%detach()%%: %% $user->roles()->detach(1); %% %%(DOCNEW 4.2=d7b13440c003218ed79e9d508706eca01990122f 4.12.2014 5:01:15) Оба метода %%attach()%% и %%detach()%% также принимают в качестве параметров массивы ID: ~%% $user = User::find(1); $user->roles()->detach([1, 2, 3]); $user->roles()->attach([1 => ['attribute1' => 'value1'], 2, 3]); ~%% %% **Использование %%sync()%% для привязки моделей "многие ко многим"** Вы также можете использовать метод %%sync()%% для привязки связанных моделей. Этот метод принимает массив ID, которые должны быть сохранены в связующей таблице. Когда операция завершится только переданные ID будут существовать в промежуточной таблице для данной модели: %% $user->roles()->sync(array(1, 2, 3)); %% **Добавление данных для связующей таблицы при синхронизации** Вы также можете связать другие связующие таблицы с нужными ID: %% $user->roles()->sync(array(1 => array('expires' => true))); %% Иногда вам может быть нужно создать новую связанную модель и добавить её одной командой. Для этого вы можете использовать метод %%save()%%: %% $role = new Role(array('name' => 'Editor')); User::find(1)->roles()->save($role); %% В этом примере новая модель %%Role%% будет сохранена и привязана к модели %%User%%. Вы можете также передать массив атрибутов для помещения в связующую таблицу: %% User::find(1)->roles()->save($role, array('expires' => $expires)); %% == Обновление времени владельца == Когда модель принадлежит к другой посредством %%belongsTo()%% - например, %%Comment%%, принадлежащий %%Post%% - иногда нужно обновить время изменения владельца при обновлении связанной модели. Например, при изменении модели %%Comment%% вы можете обновлять поле %%(t)updated_at%% её модели %%Post%%. Eloquent делает этот процесс простым - просто добавьте свойство %%$touches%%, содержащее имена всех отношений с моделями-потомками. %% class Comment extends Eloquent { protected $touches = array('post'); public function post() { return $this->belongsTo('Post'); } } %% Теперь при обновлении %%Comment%% владелец %%Post%% также обновит своё поле %%(t)updated_at%%: %% $comment = Comment::find(1); $comment->text = 'Изменение этого комментария.'; $comment->save(); %% == Работа со связующими таблицами == Как вы уже узнали, работа ((#отношения)) ((#mm многие ко многим)) требует наличия промежуточной таблицы. Например, предположим, что наш объект %%User%% имеет множество связанных объектов %%Role%%. После чтения отношения мы можем прочитать таблицу %%pivot%% на обеих моделях: %% $user = User::find(1); foreach ($user->roles as $role) { echo $role->pivot->created_at; } %% Заметьте, что каждая модель %%Role%% автоматически получила атрибут %%pivot%%. Этот атрибут содержит модель, представляющую промежуточную таблицу и она может быть использована как любая другая модель Eloquent. По умолчанию, только ключи будут представлены в объекте %%pivot%%. Если ваша связующая таблица содержит другие поля вы можете указать их при создании отношения: %% return $this->belongsToMany('Role')->withPivot('foo', 'bar'); %% Теперь атрибуты %%(t)foo%% и %%(t)bar%% будут также доступны на объекте %%pivot%% модели %%Role%%. Если вы хотите автоматически поддерживать поля %%(t)created_at%% и %%(t)updated_at%% актуальными, используйте метод %%withTimestamps()%% при создании отношения: %% return $this->belongsToMany('Role')->withTimestamps(); %% **Удаление всех связующих записей** Для удаления всех записей в связующей таблице можно использовать метод %%detach()%%: %% User::find(1)->roles()->detach(); %% Заметьте, что эта операция не удаляет записи из таблицы %%(t)roles%%, а только из связующей таблицы. %%(DOCNEW 4.2=d7b13440c003218ed79e9d508706eca01990122f 4.12.2014 5:01:15) **Обновление записи в связующей таблице** Иногда необходимо обновить связующую таблицу не отвязывая её. Для обновления вашей связующей таблицы на месте используйте метод %%updateExistingPivot()%%: ~%% User::find(1)->roles()->updateExistingPivot($roleId, $attributes); ~%% %% **Определение собственной связующей модели** Laravel также позволяет определять собственную связующую модель. Для этого сначала создайте свой класс "основной" модели, который наследует %%Eloquent%%. В остальных ваших моделях Eloquent наследуйте эту базовую модель вместо базового %%Eloquent%% по умолчанию. В вашу базовую модель добавьте следующую функцию, которая возвращает экземпляр вашей собственной связующей модели: %% public function newPivot(Model $parent, array $attributes, $table, $exists) { return new YourCustomPivot($parent, $attributes, $table, $exists); } %% == Коллекции == Все методы Eloquent, возвращающие набор моделей - либо через %%get()%%, либо через отношения - возвращают объект-коллекцию. Этот объект реализует стандартный интерфейс PHP %%IteratorAggregate%%, что позволяет ему быть использованным в циклах наподобие массива. Однако этот объект также имеет набор других полезных методов для работы с результатом запроса. **Проверка на существование ключа в коллекции** Например, мы можем выяснить, содержит ли результат запись с определённым первичным ключом методом %%contains()%%: %% $roles = User::find(1)->roles; if ($roles->contains(2)) { // } %% Коллекции также могут быть преобразованы в массив или строку ((ВП:JSON)): %% $roles = User::find(1)->roles->toArray(); $roles = User::find(1)->roles->toJson(); %% Если коллекция преобразуется в строку, результатом будет JSON-выражение: %% $roles = (string) User::find(1)->roles; %% **Проход по элементам коллекции** Коллекции Eloquent имеют несколько полезных методов для прохода и фильтрации содержащихся в них элементов: %% $roles = $user->roles->each(function ($role) { // }); %% **Фильтрация элементов коллекции** При фильтрации коллекций передаваемая функция будет использована как функция обратного вызова для ((php:array-filter array_filter)). %% $users = $users->filter(function($user) { return $user->isAdmin(); }); %% .(alert) При фильтрации коллекций и конвертации их в JSON попробуйте сначала вызвать функцию %%values%% для сброса ключей массива. **Применение функции обратного вызова** %% $roles = User::find(1)->roles; $roles->each(function ($role) { // }); %% **Сортировка коллекции по значению** %% $roles = $roles->sortBy(function ($role) { return $role->created_at; }); %% **Сортировка коллекции по значению** %% $roles = $roles->sortBy('created_at'); %% **Использование произвольного класса коллекции** Иногда вам может быть нужно получить собственный объект %%Collection%% со своими методами. Вы можете указать его при определении модели Eloquent, перекрыв метод %%newCollection()%%: %% class User extends Eloquent { public function newCollection(array $models = array()) { return new CustomCollection($models); } } %% == Читатели и преобразователи == **Объявление читателя** Eloquent содержит мощный механизм для преобразования атрибутов модели при их чтении и записи. Просто объявите в её классе метод %%getFooAttribute()%%. Помните, что имя метода должно следовать соглашению //camelCase//, даже если поля таблицы используют соглашение //snake-case// (!!(tl_note)он же - "стиль Си", с подчёркиваниями - //прим. пер.//!!): %% class User extends Eloquent { public function getFirstNameAttribute($value) { return ucfirst($value); } } %% В примере выше поле %%(t)first_name%% теперь имеет читателя (//accessor//). Заметьте, что оригинальное значение атрибута передаётся методу в виде параметра. **Объявление преобразователя** Преобразователи (//mutators//) объявляются подобным образом: %% class User extends Eloquent { public function setFirstNameAttribute($value) { $this->attributes['first_name'] = strtolower($value); } } %% === Преобразователи дат == По умолчанию Eloquent преобразует поля %%(t)created_at%% и %%(t)updated_at%% (а до версии 4.2 и %%(t)deleted_at%%) в объекты ((https://github.com/briannesbitt/Carbon Carbon)), которые предоставляют множество полезных методов, расширяя стандартный класс PHP ((phpdoc:book.datetime DateTime)). Вы можете указать, какие поля будут автоматически преобразованы и даже полностью отключить преобразование перекрыв метод %%getDates()%% класса модели. %% public function getDates() { return array('created_at'); } %% Когда поле является датой, вы можете установить его в число-оттиск времени формата Unix (//timestamp//), строку даты формата %%(t)Y-m-d%%, строку даты-времени и, конечно, экземпляр объекта %%DateTime%% или %%Carbon%%. Чтобы полностью отключить преобразование дат просто верните пустой массив из метода %%getDates()%%: %% public function getDates() { return array(); } %% == События моделей == Модели Eloquent инициируют несколько событий, что позволяет вам добавить к ним свои обработчики с помощью следующих методов: %%creating%%, %%created%%, %%updating%%, %%updated%%, %%saving%%, %%saved%%, %%deleting%%, %%deleted%%, %%restoring%%, %%restored%%. Когда первый раз сохраняется новая модель возникают события **creating** и **created**. Если модель уже существовала на момент вызова метода %%save()%%, вызываются события **updating** и **updated**. В обоих случаях также возникнут события **saving** и **saved**. **Отмена сохранения модели через события** Если обработчики %%creating%%, %%updating%%, %%saving%% или %%deleting%% вернут значение %%false%%, то действие будет отменено: %% User::creating(function ($user) { if ( ! $user->isValid()) return false; }); %% **Использование метода %%boot()%% класса модели** Модели Eloquent также содержат статический метод %%boot%%, который может быть хорошим местом для регистрации ваших обработчиков событий. %% class User extends Eloquent { public static function boot() { parent::boot(); // Регистрация ваших обработчиков... } } %% == Наблюдатели моделей == Для того, чтобы держать всех обработчиков событий моделей вместе вы можете зарегистрировать наблюдателя (//observer//). Объект-наблюдатель может содержать методы, соответствующие различным событиям моделей. Например, методы %%creating()%%, %%updating()%% и %%saving()%%, а также любые другие методы, соответствующие именам событий. К примеру, класс наблюдателя может выглядеть так: %% class UserObserver { public function saving($model) { // } public function saved($model) { // } } %% Вы можете зарегистрировать его используя метод %%observe()%%: %% User::observe(new UserObserver); %% == Преобразование в массивы и JSON == **Преобразование модели к массиву** При создании JSON API вам часто потребуется преобразовывать модели к массивам или выражениям JSON. Eloquent содержит методы для выполнения этих задач. Для преобразования модели или загруженного ((#отношения)) к массиву можно использовать метод %%toArray()%%: %% $user = User::with('roles')->first(); return $user->toArray(); %% Заметьте, что целая коллекция моделей также может быть преобразована к массиву: %% return User::all()->toArray(); %% **Преобразование модели к JSON** Для преобразования модели к JSON вы можете использовать метод %%toJson()%%: %% return User::find(1)->toJson(); %% **Возврат модели из маршрута** Обратите внимание, что если модель преобразуется к строке, результатом также будет JSON - это значит, что вы можете возвращать объекты Eloquent напрямую из ваших ((docs/v4/routing маршрутов))! %% Route::get('users', function () { return User::all(); }); %% **Скрытие атрибутов при преобразовании в массив или JSON** Иногда вам может быть нужно ограничить список атрибутов, включённых в преобразованный массив или JSON-строку - например, скрыть пароли. Для этого определите в классе модели свойство %%$hidden%%: %% class User extends Eloquent { protected $hidden = array('password'); } %% .(alert) При скрытии отношений используйте имя %%method%% отношения, а не имя для динамического доступа. Вы также можете использовать атрибут %%$visible%% для указания разрешённых полей: %% protected $visible = array('first_name', 'last_name'); %% Иногда вам может быть нужно добавить поле, которое не существует в таблице. Для этого просто определите для него ((#читател+и))я: %% public function getIsAdminAttribute() { return $this->attributes['admin'] == 'да'; } %% Как только вы создали читателя добавьте его имя к свойству-массиву %%$appends%% класса модели: %% protected $appends = array('is_admin'); %% Как только атрибут был добавлен к списку %%$appends%%, он будет включён в массивы и выражения JSON, образованные от этой модели. Атрибуты в массиве %%(t)appends%% соответствуют настройкам модели %%(t)visible%% и %%(t)hidden%%.