Laravel 4.2. Eloquent ORM (часть 6)

Категория: Laravel

В Laravel 4 используется собственная ORM Eloquent. ORM довольно простая но функциональна. Eloquent ORM следует принципам ActiveRecord. В отличие от тех же Propel/Doctrine, все связи с другими сущностями описываются вручную. По началу, некоторые вещи мне показались не интуитивными. В таких случаях рекомендую обращаться к официальной документации - там вы найдете ответы в 80% случаев.

Обратите внимание на расширенную реализацию моделей в Ardenthttps://github.com/laravelbook/ardent#relations

Типы объектов

/** @var Illuminate\Database\Eloquent\Builder $query */
$query = User::where(...);

/** @var Illuminate\Database\Eloquent\Collection $users */
$users = User::where(...)->get();

/** @var Illuminate\Pagination\Paginator $slice */
$slice = User::where(...)->paginate(1);

Отладка

Включить логирование запросов (app/storage/logs/):

DB::enableQueryLog();
// или так
Log::info(print_r(DB::getQueryLog(), true));

Получить лог запросов вручную:

$queries = DB::getQueryLog(); // array
// или так
$sql = Model::where('column', 'value')->getQuery()->toSql();
Важно!

Логирование запросов работает только на MySQL! Не пытайтесь заставить работать QueryLog для SQLite или PostgreSQL! Это стороннее расширение которое приняли в ядро, однако в официальной документации о нем ничего не сказано.

Создание записей

Найти или добавить запись в таблицу (сразу же сохраняет параметры фильтрации):

$user = User::firstOrCreate(['email' => $email]);

Метод firstOrCreate() в случае не успешного поиска, сразу же попытается сохранить запись в БД. Поэтому убедитесь, что параметры фильтрации, которые могут оказаться свойствами новой модели - корректны (валидны). Иначе будут ошибки...

Найти запись или создать новую модель (без сохранения в БД):

$user = User::firstOrNew(['id' => (int)$_GET['id']]);
$user->fill($data);
$user->save();

Приводите к integer поле id (PK, первичный ключ), иначе получите ошибку: SQLSTATE[22P02]: Invalid text representation: 7 ERROR: invalid input syntax for integer: "" (SQL: select * from "table" where "id" = ? limit 1) (Bindings: array ( 0 => '', ))

Поиск и фильтрация

Примечание

Метод get() возвращает массив моделей или пустой массив, метод first() - модель или null. Метод count() вернет количество записей соответствующих условию.

$user  = User::find($id || $ids);// Найти по ID или массиву IDs (вернет массив или коллекцию)
$user = User::findOrFail($id); // Найти или бросить 404 ошибку
$users = User::all(); // Получить все записи таблицы (коллекция)
$users = User::where('id', '>', '5')->first();
$users = User::whereEmail('some@mail.com')->first();
$users = User::where('created_at', '>', '2013-09-01')
->orderBy('id', 'desc') ->sortBy(function($user) { return rand(); }) ->take(10) // срез 10 записей
->get();
$users = User::->whereRaw("fname = 'Ledorf' AND lname = ?", array('Rasmus'))->get(); $users = User::withTrashed()->get(); // искать в удаленных (Soft Deletes)
$users = User::onlyTrashed()->get(); // только удаленные записи
$user->trashed(); // проверить отметку Soft Delete

Динамические условия фильтрации:

User::where(function ($query) use ($some_field) {
if ($some_field) {
// (!) Not use `$this->some_field` instead `$some_field` on PHP < 5.4
$query->where('some_field', '!=', $some_field);
}
})
Не пытайтесь удалить коллекцию записей

Вы получите сообщение об ошибке: Call to undefined method Illuminate\Database\Eloquent\Collection::delete()

Это потому, что методы поиска where* возвращают билдер - Illuminate\Database\Eloquent\Builder,  а методы find([array]) и all() - возвращают объект класса Illuminate\Database\Eloquent\Collection, у которого нет метода delete().

Подзапросы

Получить категории с количеством постов:

$subQuery = '(SELECT count(id) FROM note WHERE note.category_id = category.id) as post_count';
$categories = Category::query()->select(['id', 'name'])->selectRaw($subQuery)->get();

Связанные записи

Получение

Получить количество связанных записей (юзеров) для каждой группы (жадная загружка):

$groupsWithUsers = Group::with('User')->get();
foreach($groupsWithUsers as $groupWithUsers) {
echo $groupWithUsers->name . " (" . count($groupWithUsers->users) . ")<br>";
}
Примечание

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

Group::with('User.Post', 'Modifier')->get();

Фильтрация

Фильтрация по полям связанных таблиц:

// Выбрать всех пользователей из группы 'admin'
$users = User::whereHas('Group', function(\Illuminate\Database\Eloquent\Builder $q) {
  $q->where('type', 'admin');
})->get();
$posts = Post::whereHas('users', function ($query) use ($created_at) {
  $query->where('users.created_at', '>', $created_at);
})->where('category_id', 3)->get();

Использование условий для ограничения подключаемых объектов в with():

$users = User::with([
  'Group' => function(\Illuminate\Database\Eloquent\Relations\BelongsTo $query) use ($type) {
    $query->where('type', $type);
  }
])->get();
Внимание!

Условие внутри with() фильтрует не итоговый результат, а лишь объекты подключаемые к основным данным.

Обновить updated_at для всех связанных моделей:

$user->roles()->touch(); // обновить отметку времени для ролей (не связующей таблицы)

Получить все посты, у которых есть комментарии:

$posts = Post::has('comments')->get();
$posts = Post::has('comments', '>=', 4)->get(); // больше равно 4х комментариев

Проверить наличие записи по PK в коллекции:

$user->roles->contains(4); // bool

Получить массив полей idname для всех ролей пользователя:

$roles_array = $user->roles()->get(['id', 'name'])->toArray();
$roles = $user->roles()->getResults(); // все роли юзера

Обновление и создание

Связать роль с пользователем, связь M:M:

$role = Role::create([/*role data*/]);
$user->roles()->save($role);

Связать пользователя с ролью (M:M):

$user->roles()->attach([1,2]); // void
$affectedRows = $user->roles()->detach([1,2,3], ['field' => 'val']);
Внимание!

Если вы укажете методу attach() id уже установленной роли - эта связь будет добавлена еще раз! Если вам не нужны множественные связи между 2мя моделями - используйте метод sync():

$user->roles()->sync([1,3], false);

Обновить pivot таблицу:

User::find(1)->roles()->updateExistingPivot($roleId, $attributes);

BelongsToMany::save()

Пример Many-to-Many связывания новой сущности (роли с пользователем):

$role = $user->roles()->save(
  Role::create(['name' => 'Developer']), // создаем роль на лету
  [ 'some_field_in_pivot_table' => 'value_in_relation_table' ]
);

Второй аргумент не обязателен, в нем задаются расширенные поля промежуточной таблицы user_roles.

BelongsToMany::sync()

Синхронизирует связь между 2мя сущностями через промежуточную таблицу, недостающие будут добавлены, а лишние удалены. Метод sync() принимает ID'шники записей сущности, с которой необходимо связать модель юзера. Всегда возвращает null.

Синхронизировать связи юзера с ролями:

$user->roles()->sync([1,2,163]); // добавить только перечисленные роли
$user->roles()->sync([1,2,163], false); // не удалять существующие связи

Если установить аргумент $detaching = false - то Eloquent не будет удалять существующие связи, которые не перечислены в новой синхронизации, а только добавит недостающие. Но, в отличие от attach() - исключит дублирование линков.

Если промежуточная таблица содержит дополнительные поля, их можно указать во вложенном массиве (ИМХО плохо, что нет возможности задать общие поля для всех ID):

$user->roles()->sync([4]); // updated_at НЕ будет обновлен, если роль 4 уже есть
// updated_at будет обновлен для этой связи
$user->roles()->sync([4 => ['updated_at' => date('c')]]);
Примечание

По умолчанию метод sync() не будет устанавливать и обновлять timestamp поля в промежуточной таблице. И метод withTimestamps() никак на это не влияет!

Обновить updated_at поле в связующей таблице (pivot table):

$affectedRows = $user->roles()->first()->pivot->touch();
Примечание

Через свойство pivot мы получаем доступ к модели (записи) промежуточной таблицы (pivot table), которая ведет себя как обычная Eloquent модель. По умолчанию pivot table будет содержать только поля с идентификаторами связанных записей.

Если вам нужен доступ к дополнительным полям (те же created_atupdated_at), тогда вы должны добавить эти поля в модели с помощью метода withPivot().

Для того, чтобы Eloquent автоматически обновляла отметки времени в промежуточных таблицах - нужно вызвать метод  withTimestamps() при описании связи.

Чтобы эти изменения вступили в силу - необходимо очистить кеш:

php artisan cache:clear

Кастомные коллекции

Создать пустую коллекцию моделей и добавить в нее модели:

/** @var User[] $users */
$users = new Illuminate\Database\Eloquent\Collection();

foreach (Post::where(/* ... */)->get() as $post) {
  $users->add($post->user);
}

$users->all();     // Массив классов моделей
$users->toArray(); // Массив массивов моделей

Удаление

User::find(1)->delete(); // удаление ТОЛЬКО одного юзера
User::destroy(1); // можно передать массив или список ID

// Массовое удаление
$affectedRows = User::where('votes', '>', 100)->delete();
$affectedRows = User::whereIn([1,2,3])->delete();
// Удаление связанных hasMany записей $group->users()->delete(); // Полное удаление записи, при включенном Soft Delete
$user->forceDelete();

// Очистить таблицу
User::truncate();

Обновление и восстановление

$user->touch(); // обновить юзеру поле updated_at

// Будут обновлены только разрешенные поля, перечисленные в свойстве $fillable.
// Поля, указаны в свойстве $guarded НЕ будут игнорироваться!
$user->update(['email' => 'user@gmail.com']);

// Массовое обновление
$affectedRows = User::where('votes', '>', 100)->update(['status' => 2]);
// Массовое сохранение связанных записей $user->posts()->saveMany($posts); // Восстановить удаленную запись
$user->restore();

Фильтрация и условия

Проверить сохранение или существование модели (модель создана через create() или загружена из БД):

$model->exists; // хотя, isNew() или isSaved() смотрелись бы более гармонично)

Проверить была ли изменена модель с момента ее загрузки из БД:

$model->isDirty();

Прочее

Получить массив PK:
$ids = $this->user->roles()->get()->modelKeys(); // так
$ids = $this->user->roles()->getRelatedIds();    // или так
Получить массив значений любого столбца:
$ids = User::all()->lists('id');
$options = User::all()->lists('title', 'id'); // массив опций
Обход коллекции записей:
$rolesLevel = [];
$user->roles->each(function($role) use ( &$rolesArray) {
  $rolesLevel[] = $role->name . $role->level;
});
Формирование массива объектов:
$array = User::get(['username as Author', 'cretaed_at as Registered']);

#Eloquent ORM, #ORM

категория: Laravel