Skip to content

Laravel Eloquent ORM Deep — Advanced Active Record Patterns

DodaTech Updated 2026-06-28 4 min read

In this tutorial, you will learn about Laravel Eloquent ORM Deep. We cover key concepts, practical examples, and best practices to help you master this topic.

Laravel Eloquent ORM implements the Active Record pattern with a fluent query builder, model relationships, mutators, accessors, events, and API resource Serialization.

What You'll Learn

By the end of this tutorial, you'll master Eloquent's internals, use model events, implement global scopes, build API resources, and apply advanced patterns.

Why Eloquent Matters

Eloquent is Laravel's ORM with expressive syntax. Understanding its internals helps optimize queries, build clean APIs, and avoid common pitfalls.

Real-World Use

A Laravel API uses Eloquent models with API resources for JSON transformation, global scopes for multi-tenant filtering, and model events for cache invalidation.

Eloquent Path

flowchart LR
  A[Eloquent ORM] --> B[Model Pattern]
  B --> C[Relations]
  B --> D[Scopes]
  B --> E[Events]
  B --> F[API Resources]
  B --> G{You Are Here}
  style G fill:#f90,color:#fff

Model Boot Method

Tap into Eloquent's boot lifecycle for global behavior.

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Post extends Model {
    protected static function booted(): void {
        static::creating(function ($post) {
            $post->slug = str($post->title)->slug();
            $post->author_id = auth()->id();
        });
        static::created(function ($post) {
            Cache::tags(["posts"])->flush();
        });
        static::retrieved(function ($post) {
            $post->increment("views");
        });
    }
}

Global Scopes

Apply constraints to all queries on a model.

<?php
namespace App\Models\Scopes;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;
class TenantScope implements Scope {
    public function apply(Builder $builder, Model $model): void {
        $builder->where("tenant_id", tenant()->id);
    }
}
// In model
use App\Models\Scopes\TenantScope;
class Project extends Model {
    protected static function booted(): void {
        static::addGlobalScope(new TenantScope());
    }
}

API Resources

Transform Eloquent models for JSON APIs.

<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class UserResource extends JsonResource {
    public function toArray(Request $request): array {
        return [
            "id" => $this->id,
            "name" => $this->name,
            "email" => $this->email,
            "joined_at" => $this->created_at->diffForHumans(),
            "posts_count" => $this->whenCounted("posts"),
            "recent_posts" => PostResource::collection($this->whenLoaded("posts")),
        ];
    }
}

Model Events and Observers

Observers group event handlers for cleaner models.

<?php
namespace App\Observers;
use App\Models\Post;
use App\Services\SearchIndexer;
class PostObserver {
    public function __construct(private SearchIndexer $search) {}
    public function created(Post $post): void {
        $this->search->index("posts", $post->id, $post->toSearchableArray());
        Cache::tags(["posts"])->flush();
    }
    public function updated(Post $post): void {
        $this->search->update("posts", $post->id, $post->toSearchableArray());
    }
    public function deleted(Post $post): void {
        $this->search->delete("posts", $post->id);
    }
}

Common Mistakes

1. Overusing Lazy Loading

N+1 queries happen with lazy loaded relationships. Always eager load with with().

2. Not Using Query Builder for Complex Queries

Using Collection methods for filtering loads all rows into memory. Use the query builder.

3. Mass Assignment Without Fillable

Forgetting $fillable in models allows mass assignment. Always whitelist fields.

4. Ignoring Model Events for Side Effects

Putting side effects in controllers leads to duplication. Use model events.

5. Nested API Resources Without whenLoaded

Without whenLoaded, serializing relations without loading them causes N+1.

Practice Questions

1. What is the booted() method for?

Registering model events and global scopes that apply to every instance.

2. How do global scopes work?

They add query constraints automatically to every query on the model.

3. What is an API resource?

A transformation layer that converts models to JSON with selective attribute inclusion.

4. How do you create a model Observer?

Create an observer class and register it in the AppServiceProvider.

5. Challenge: Create an Eloquent model with a global scope and observer.

<?php
class Post extends Model {
    protected static function booted(): void {
        static::addGlobalScope("published", fn(Builder $q) => $q->where("status", "published"));
    }
}
class PostObserver {
    public function creating(Post $post): void {
        $post->uuid = (string) Str::uuid();
    }
}

FAQ

What is the difference between Eloquent and Doctrine?

Eloquent uses Active Record. Doctrine uses Data Mapper. Eloquent is simpler but less flexible for complex domains.

Can I use Eloquent outside Laravel?

Yes. Install illuminate/database and configure Capsule Manager.

What are accessors and mutators?

Accessors transform attributes on read. Mutators transform on write.

How do I prevent N+1 queries?

Use with() for eager loading. Watch the Laravel Debugbar.

What is the N+1 problem?

Loading one query for the parent and N queries for children. Eager loading solves it.

Mini Project: Eloquent Model Layer

Build a complete Eloquent model layer for a blog.

<?php
namespace App\Models;
use App\Observers\PostObserver;
use App\Http\Resources\PostResource;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
#[ObservedBy([PostObserver::class])]
class Post extends Model {
    protected $fillable = ["title", "content", "status"];
    protected $casts = ["published_at" => "datetime", "meta" => "array"];
    protected static function booted(): void {
        static::addGlobalScope("published", fn(Builder $q) => $q->where("status", "published"));
    }
    public function scopeRecent($query): void {
        $query->orderBy("created_at", "desc")->limit(10);
    }
}

What's Next

Eloquent Relations Deep Eloquent Query Scopes Eloquent Serialization

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro