Что такое Eloquent:
Eloquent реализует паттерн Active Record: каждой таблице соответствует класс-модель, а каждая строка таблицы превращается в объект этого класса. Создали объект, заполнили поля, вызвали save(), и Eloquent сам соберёт INSERT. Главное удобство в том, что вы работаете с привычными PHP-объектами, а не клеите строки запросов руками.
Создаём модель:
Код: Выделить всё
php artisan make:model Post
Руками нужно сделать две вещи: перечислить поля, доступные для массового заполнения, и описать касты типов. Без каста published_at приедет из базы обычной строкой, а с кастом datetime станет объектом Carbon, который можно сравнивать с now() и форматировать:
Код: Выделить всё
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
protected $fillable = ['title', 'body', 'published_at'];
protected function casts(): array
{
return [
'published_at' => 'datetime',
];
}
}
CRUD на практике:
Удобнее всего пробовать в tinker, интерактивной консоли Laravel. Запустите её командой php artisan tinker в корне проекта и выполняйте код построчно.
Код: Выделить всё
// создание
$post = Post::create([
'title' => 'Первый пост',
'body' => 'Привет, Eloquent',
]);
// чтение
$all = Post::all();
$post = Post::find(1);
$fresh = Post::where('published_at', '<=', now())->latest()->get();
// обновление
$post->title = 'Новый заголовок';
$post->save();
// то же самое одним вызовом, тоже подчиняется $fillable
$post->update(['title' => 'Новый заголовок']);
// удаление
$post->delete();
Post::destroy([2, 3]);
В контроллере типовое чтение выглядит так:
Код: Выделить всё
public function show(int $id)
{
$post = Post::findOrFail($id);
return view('posts.show', ['post' => $post]);
}
Писать findOrFail() в каждом методе при этом не обязательно. Если типизировать параметр моделью, Laravel сам найдёт запись по {post} из маршрута и сам отдаст 404, когда её нет. Это называется route model binding, и в свежем коде вы чаще увидите именно его:
Код: Выделить всё
public function show(Post $post)
{
return view('posts.show', ['post' => $post]);
}
Поле молча пропадает при create(). Когда $fillable заполнен, Eloquent по умолчанию не бросает исключений: лишние атрибуты просто отбрасываются, и вы получаете запись без нужного поля и без единой ошибки. MassAssignmentException вылетает только если у модели вообще нет $fillable или включён строгий режим. Лечится добавлением поля в $fillable, а не заменой на $guarded = [], которая отключает защиту целиком. И включите в локальном окружении Model::shouldBeStrict() (или точечно Model::preventSilentlyDiscardingAttributes()) в AppServiceProvider: тихое отбрасывание превратится в явное исключение, и такие баги перестанут прятаться.
Post::all() на боевой таблице. Метод тащит в память все строки разом. На учебной базе это незаметно, а на таблице в полмиллиона записей положит процесс. Для списков используйте paginate(15), а в Blade-шаблоне из третьей главы выводите ссылки пагинации через $posts->links(). Для фоновой обработки всей таблицы (экспорт, пересчёт полей) есть chunk(), lazy() и cursor(), они читают записи порциями. Подробно разберём их позже, пока просто запомните, что all() для таких задач не подходит.
Таблица названа не по конвенции. Обычная история с legacy-базами. Укажите имя явно, protected $table = 'tbl_articles'; в модели, и Eloquent перестанет искать несуществующую таблицу.
Массовый update идёт мимо событий модели. Запрос вида Post::where(...)->update([...]) проставит updated_at сам, об этом заботится Eloquent-билдер. А вот события модели (saving, updated и прочие) и наблюдатели при таком обновлении не сработают: модели даже не загружаются из базы. Метки времени теряются, только если спуститься ещё ниже, в DB::table()->update() или сырой SQL, там Laravel не трогает вообще ничего. Для одиночных записей надёжнее find() плюс save() или $post->update(): они и события вызовут, и метки обновят.
Что усвоили:
Модель отражает таблицу, конвенции избавляют от конфигурации, $fillable защищает от массового заполнения лишних полей, а весь CRUD укладывается в пять-шесть методов. В следующей главе свяжем модели между собой: посты получат автора и комментарии через hasMany и belongsTo.