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.

Sources

Related topics