LaravelJuniorTechnical
Что такое Eloquent ORM и как он взаимодействует с базой данных?
Eloquent ORM реализует паттерн Active Record: каждая модель соответствует таблице, экземпляр — строке. CRUD-операции выполняются через fluent-методы (find, create, save, delete), связи — через belongsTo/hasMany/belongsToMany.
Eloquent ORM
Eloquent — реализация паттерна Active Record в Laravel. Каждый класс модели соответствует таблице в базе данных; экземпляр модели представляет одну строку. Eloquent использует PDO через Illuminate\Database\Connection и строит SQL через fluent Query Builder.
Базовая модель и соглашения
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
// По умолчанию: таблица 'posts', PK 'id', timestamps created_at/updated_at
protected $table = 'posts'; // явное имя таблицы
protected $primaryKey = 'id'; // PK (default)
public $timestamps = true; // автоматические created_at/updated_at
// Белый список для mass assignment
protected $fillable = ['title', 'body', 'user_id'];
// Или чёрный список:
protected $guarded = ['is_admin'];
// Автоматическое приведение типов
protected $casts = [
'published_at' => 'datetime',
'settings' => 'array',
'is_featured' => 'boolean',
];
}
CRUD-операции
// Create
$post = Post::create(['title' => 'Hello', 'body' => 'World', 'user_id' => 1]);
// или
$post = new Post(['title' => 'Hello']);
$post->user_id = auth()->id();
$post->save();
// Read
$post = Post::find(1); // null если не найдено
$post = Post::findOrFail(1); // выбрасывает ModelNotFoundException
$posts = Post::where('is_published', true)->orderBy('created_at', 'desc')->get();
$count = Post::where('user_id', 1)->count();
// Update
$post->update(['title' => 'Updated']);
// или
$post->title = 'Updated';
$post->save();
// Delete
$post->delete();
Post::destroy([1, 2, 3]); // удалить несколько по ID
Связи (Relationships)
class Post extends Model
{
// Пост принадлежит пользователю (FK: user_id в таблице posts)
public function author(): BelongsTo
{
return $this->belongsTo(User::class, 'user_id');
}
// Пост имеет много комментариев (FK: post_id в таблице comments)
public function comments(): HasMany
{
return $this->hasMany(Comment::class);
}
// Теги через pivot-таблицу post_tag
public function tags(): BelongsToMany
{
return $this->belongsToMany(Tag::class)->withTimestamps();
}
}
// Использование
$post->author->name; // ленивая загрузка
$post->comments()->count(); // запрос через query builder
$post->tags()->attach([1, 3]); // добавить теги через pivot
Scopes для переиспользуемых фильтров
class Post extends Model
{
// Local scope
public function scopePublished(Builder $query): Builder
{
return $query->where('published_at', '<=', now());
}
public function scopeByAuthor(Builder $query, int $userId): Builder
{
return $query->where('user_id', $userId);
}
}
// Использование
$posts = Post::published()->byAuthor(1)->latest()->paginate(20);
Soft Deletes
use Illuminate\Database\Eloquent\SoftDeletes;
class Post extends Model
{
use SoftDeletes; // добавляет поле deleted_at
}
$post->delete(); // устанавливает deleted_at, не удаляет строку
$post->restore(); // сбрасывает deleted_at
Post::withTrashed()->get(); // включает soft-deleted записи
Post::onlyTrashed()->get(); // только удалённые
Подводные камни
- Mass assignment без
$fillableили$guardedоткрывает уязвимость: злоумышленник может передатьis_admin=1в POST-запросе и Eloquent сохранит его. - Метод
all()загружает все строки таблицы в память — на больших таблицах это приводит к OOM; используйтеchunk(),lazy()илиcursor(). - Динамические свойства (
$post->author) триггерят lazy loading — первое обращение в цикле на коллекции порождает N+1 запросов. $model->update([])с пустым массивом возвращаетtrue, но ничего не обновляет; метод не выбрасывает исключение при пустом fillable.- Soft Deletes не удаляют связанные записи автоматически — каскадный soft delete нужно реализовывать вручную через observer или события модели.
- Приведение
'settings' => 'array'сериализует в JSON при сохранении, но при чтении возвращает PHP-массив; изменение вложенного элемента без переприсвоения всего поля не триггеритisDirty(). - Глобальные scopes (например, SoftDeletes) применяются ко всем запросам — при join с той же таблицей через alias это ломает запрос, нужно явно вызывать
withoutGlobalScope().
Common mistakes
- Сводить eloquent orm к названию метода без lifecycle и failure path.
- Игнорировать модель runtime: Laravel 13 поверх PHP: request проходит через HTTP kernel, middleware, routing, controller/action и response pipeline.
- Не отделять validation, authorization, transaction boundary и business logic.
What the interviewer is testing
- Объясняет eloquent orm через конкретную точку lifecycle в Laravel.
- Приводит корректный минимальный пример без вымышленных методов или callbacks.
- Называет edge cases: пустые значения, ошибки, транзакции, безопасность или concurrency.