Laravel Eloquent ORM Basics

·

·

Eloquent is Laravel's Active Record ORM. Each model maps to a table, each instance to a row, and expressive methods replace verbose SQL for most application queries.

Models and Basic Queries

Generate models with php artisan make:model Post -m to also create a migration. Models live in app/Models and extend IlluminateDatabaseEloquentModel.

Eloquent assumes plural snake_case table names and primary key id unless you override $table or $primaryKey.

Step 1: Define fillable and hidden attributes

Set protected $fillable = ['title', 'body', 'user_id'] to whitelist mass assignment fields. Use $hidden = ['password'] on User models so secrets never appear in JSON arrays.

guarded = ['*'] blocks all mass assignment when you prefer explicit property assignment. Choose fillable for most CRUD APIs with validated input.

protected $fillable = ['title', 'body', 'published_at'];

Step 2: Create and retrieve records

Post::create($validated) inserts a row when fillable permits. Post::find($id) returns null if missing; Post::findOrFail($id) throws 404 for HTTP layers.

Post::where('published', true)->orderByDesc('created_at')->get() builds fluent queries. Prefer query scopes for reused constraints like published().

$post = Post::create($validated);
$post = Post::findOrFail($id);

Step 3: Attribute casting

protected $casts = ['published_at' => 'datetime', 'meta' => 'array'] converts database strings to Carbon instances and PHP arrays automatically.

Casts run on read and write, keeping controller code free of manual parsing. Enum casts in PHP 8.1+ map columns to backed enums for type safety.

protected $casts = ['status' => PostStatus::class];

Step 4: Pagination and chunking

Post::paginate(15) returns a LengthAwarePaginator for Blade links. Use chunk(100, function ($posts) { … }) when processing large exports without loading everything into memory.

cursor() offers lazy collections for memory-efficient iteration. Pick pagination for UI tables and chunking for batch jobs.

$posts = Post::latest()->paginate(20);

Query Builder and Scopes

Eloquent builds on the query builder. You can call Post::query()->where(…) or DB::table('posts') when you need lighter queries without model events.

Local scopes are reusable query constraints defined as methods on the model.

Step 1: Local query scopes

public function scopePublished($query) { return $query->whereNotNull('published_at'); } is called as Post::published()->get().

Scopes accept parameters: scopeForAuthor($query, User $user) enables Post::forAuthor($user)->latest()->paginate().

public function scopePublished($query)
{
    return $query->whereNotNull('published_at');
}

Step 2: Global scopes

Implement IlluminateDatabaseEloquentScope to apply constraints automatically. SoftDeletes uses a global scope to exclude deleted rows unless withTrashed() is used.

Register global scopes in the model booted() method when every query must respect tenant_id or archive flags.

static::addGlobalScope(new TenantScope);

Step 3: Aggregates and raw expressions

Post::where('user_id', $id)->count() and sum('views') answer analytics questions. selectRaw('DATE(created_at) as day, COUNT(*) as total')->groupBy('day') powers dashboards.

Watch SQL injection when using raw expressions—bind parameters instead of interpolating user input into selectRaw strings.

Post::published()->avg('rating');

Step 4: Subqueries and exists

Use whereExists or subquery closures for complex filters without loading models into PHP memory. exists() checks presence efficiently.

Eloquent hydrate() can map stdClass rows from joins into models when you need selected columns from related tables in one query.

$ids = Post::whereHas('comments')->pluck('id');

Mutators, Accessors, and Events

Accessors transform attributes when you read them; mutators transform values before save. Laravel 9+ uses Attribute::make() for unified definitions.

Model events—creating, updating, deleting—hook into the lifecycle for auditing, slug generation, or cache invalidation.

Step 1: Accessors with Attribute::make

protected function title(): Attribute { return Attribute::make(get: fn ($v) => ucwords($v)); } formats titles whenever $post->title is accessed.

Combine get and set in one Attribute definition. Accessors are useful for computed fields like full_name from first and last columns.

protected function slug(): Attribute
{
    return Attribute::make(
        set: fn ($v) => Str::slug($v)
    );
}

Step 2: Model events and observers

php artisan make:observer PostObserver –model=Post registers created, updated, and deleted hooks. Keep observers slim and dispatch jobs for heavy work.

Observers decouple side effects from controllers. Clearing cache or syncing search indexes belongs here rather than scattered across store() methods.

public function created(Post $post): void
{
    IndexPostJob::dispatch($post);
}

Step 3: Soft deletes

Add SoftDeletes trait and a deleted_at column. destroy() sets the timestamp; restore() and forceDelete() manage recovery and permanent removal.

Queries automatically exclude soft-deleted rows. Use withTrashed() in admin panels that let editors recover content.

use SoftDeletes;
protected $dates = ['deleted_at'];

Step 4: Factories for testing and seeding

Model factories generate fake data with Post::factory()->count(10)->create(). Define states like published() for common scenarios in tests.

Factories pair with PHPUnit or Pest to build realistic fixtures without manual SQL inserts in every test case.

Post::factory()->count(5)->published()->create();

Aadyanex models Laravel domains with explicit Eloquent boundaries, optimized queries, and migration strategies that keep databases fast as data grows.