Topic 8 of 75·M15
Eloquent ORM
Must knowmodels · `$fillable`/`$guarded` · `$casts` · accessors · mutators · attribute objects · relationships · scopes · events · observers · collections · query builder · model events · serialization
The one-sentence answer
Eloquent is Laravel's ORM — each model class maps to a database table, and each instance maps to a row, with relationships, scopes, events, and serialization layered on top.
The mental model
Eloquent is active record: the model is the table, an instance is a row, and the model carries both data and behaviour.
// app/Models/Order.php
class Order extends Model
{
// the table is assumed (orders), the PK is assumed (id)
}
Order::where('status', 'paid')->get(); // SQL: select * from orders where status = 'paid'
$order = Order::find(1); // one row → one instance
$order->total; // a column value
$order->status = 'shipped'; // mutate a column
$order->save(); // update the row
The model is where mass-assignment safety, casting, accessors, scopes, and events all live — which is why "Eloquent" is the largest topic on the checklist.
How it works
Mass assignment — $fillable vs $guarded
class User extends Model
{
// whitelist: ONLY these fields can be mass-assigned
protected $fillable = ['name', 'email', 'password'];
// or blacklist everything except these (safer to leave guarded empty)
protected $guarded = ['id', 'is_admin'];
}
User::create($request->all()); // mass assignment — guarded by fillable
Mass assignment is the vector for the "user sets is_admin" attack (Lesson 37). $fillable is the whitelist, $guarded the blacklist — never leave both empty, and never trust raw input.
Casts — typed attributes
protected $casts = [
'is_active' => 'boolean', // 0/1 → true/false
'metadata' => 'array', // JSON column → array
'price' => 'decimal:2',
'paid_at' => 'datetime', // string → Carbon instance
'settings' => AsCollection::class, // or attribute objects (L10+)
];
Casts turn raw DB values into useful PHP objects at the boundary.
Accessors & mutators — the modern syntax
use Illuminate\Database\Eloquent\Casts\Attribute;
protected function fullName(): Attribute
{
return Attribute::make(
get: fn ($value) => trim($this->first_name.' '.$this->last_name),
set: fn ($value) => ['first_name' => ..., 'last_name' => ...],
);
}
$user->full_name; // reads through the accessor
$user->full_name = 'Ada Lovelace'; // writes through the mutator
Scopes — reusable query fragments
public function scopeActive($query) { return $query->where('is_active', true); }
public function scopeRecent($query) { return $query->orderBy('created_at', 'desc'); }
User::active()->recent()->get(); // global-scope-free, composable
Model events & observers
// in a provider's boot():
Order::observe(OrderObserver::class);
class OrderObserver
{
public function creating(Order $order) { $order->number = generateNumber(); }
public function created(Order $order) { event(new OrderCreated($order)); }
}
Model lifecycle hooks: creating/created, updating/updated, saving/saved, deleting/deleted, restoring/restored.
Serialization
$user->toArray(); // model → plain array
$user->toJson(); // model → JSON
$user->makeHidden('secret'); // omit attributes
$user->only('id', 'name'); // pick attributesInterview questions
Q1. What is Eloquent, and how is it different from the query builder?
Eloquent is the ORM — models map to tables, instances to rows, with relationships, casts, events, and serialization. The query builder (
DB::table()) is the lower-level, Eloquent-free SQL builder. Eloquent is built on the query builder; use Eloquent for domain models, the query builder for raw-ish joins and aggregates.
Q2. What is mass assignment, and how do you protect against it?
Mass assignment is creating/updating a model from an array (
User::create($request->all())). Without protection, a user could slipis_admin => 1into the request and get it persisted.$fillable(whitelist) or$guarded(blacklist) controls which fields mass assignment may touch.
Q3. What are accessors and mutators?
Accessors transform an attribute when read; mutators transform it when written. The modern syntax is an
Attributeobject withgetand/orsetclosures. They let you present data (full name, formatted price) without storing the presentation.
Q4. What are casts for?
Casts convert raw database values to PHP types at the boundary — booleans, arrays (JSON columns), decimals, datetimes as Carbon. Without them you'd manually convert
0/1, JSON strings, and date strings everywhere.
Q5. What are scopes?
Reusable query fragments —
scopeActive()becomesUser::active(). They keep repeated conditions (where('is_active', true)) in one place, composable with other scopes and constraints.
Q6. What are model events and observers?
Hooks into the model lifecycle —
creating,created,updating, etc. An observer groups them for one model in one class. Use them for cross-cutting model behaviour (auto-numbering, firing domain events); business logic still belongs in services.
Senior follow-up: When would you not use Eloquent?
For heavy reporting — complex joins, grouped aggregates, huge result sets — where the query builder or raw SQL with
EXPLAINis clearer and faster. And never hydrate thousands of models when you only need counts:count(),sum(),pluck()are Eloquent methods that avoid hydration entirely.
Common mistakes
❌ Leaving $guarded/$fillable unset — mass assignment is wide open.
❌ Accessors doing DB queries — accessors are for transformation, not queries (N+1 risk).
❌ Fat models — model events and scopes are convenient, but business logic belongs in services.
❌ Hydrating models to count them — Model::all()->count() loads everything; use Model::count().
Quick revision notes
- Eloquent = active record: model ↔ table, instance ↔ row
$fillable(whitelist) /$guarded(blacklist) — mass assignment defence$casts→ typed attributes ·Attribute::make(get, set)→ accessors/mutators- Scopes = reusable query fragments
- Model events/observers = lifecycle hooks (
creating/created/…) - Serialization:
toArray()/toJson()/makeHidden()
Check your understanding
Answer each one out loud. Then rate yourself honestly.
0/5What is active record, and how does Eloquent embody it?
Why is $fillable a security control, not just a nicety?
Accessor vs cast — when do you reach for each?
What's the difference between a scope and a model event?
When should you drop Eloquent for the query builder?