Laravel Eloquent ORM: What is it and Why is it a Game-Changer?

Published on November 11, 2025
Laravel Eloquent ORM Database Models PHP

What is Eloquent ORM?

Eloquent is Laravel's built-in Object-Relational Mapper (ORM) that provides a beautiful, simple ActiveRecord implementation for working with your database. It allows you to interact with your database tables using intuitive, object-oriented syntax.

In simple terms: Eloquent lets you work with database records as if they were regular PHP objects.

Traditional vs Eloquent Approach

// ❌ Traditional PHP with raw SQL
$users = [];
$result = mysqli_query($connection, "SELECT * FROM users WHERE active = 1");
while ($row = mysqli_fetch_assoc($result)) {
    $users[] = $row;
}

// ✅ Laravel Eloquent
$users = User::where('active', true)->get();

Why Eloquent is a Game-Changer

1. Elegant, Readable Syntax

// Instead of complex SQL...
"SELECT * FROM posts 
 WHERE published_at <= NOW() 
 AND (category_id = 1 OR author_id = 5) 
 ORDER BY created_at DESC 
 LIMIT 10"

// You write clean PHP...
$posts = Post::where('published_at', '<=', now())
            ->where(function($query) {
                $query->where('category_id', 1)
                      ->orWhere('author_id', 5);
            })
            ->latest()
            ->limit(10)
            ->get();

2. Database Agnostic

The same Eloquent code works across MySQL, PostgreSQL, SQLite, and SQL Server.

3. Built-in Security

Eloquent automatically uses parameter binding to prevent SQL injection attacks.

4. Rich Feature Set

Relationships, eager loading, mutators, accessors, scopes, and much more!

Creating Your First Eloquent Model

Basic Model Structure

<?php
// app/Models/User.php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    // That's it! Eloquent handles the rest automatically
}

Model Conventions

  • Table name: Plural snake_case of model name (users for User)
  • Primary key: id (auto-incrementing)
  • Timestamps: created_at and updated_at (automatically managed)
  • Database connection: Default database connection

Customizing Model Conventions

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    // Custom table name
    protected $table = 'blog_posts';
    
    // Custom primary key
    protected $primaryKey = 'post_id';
    
    // Non-incrementing primary key
    public $incrementing = false;
    
    // Custom key type
    protected $keyType = 'string';
    
    // Disable timestamps
    public $timestamps = false;
    
    // Custom timestamp formats
    const CREATED_AT = 'creation_date';
    const UPDATED_AT = 'last_updated';
    
    // Custom database connection
    protected $connection = 'mysql_blog';
    
    // Default attributes
    protected $attributes = [
        'views' => 0,
        'is_published' => false,
    ];
}

Basic CRUD Operations with Eloquent

Create (Insert Records)

// Method 1: Create and save
$user = new User;
$user->name = 'John Doe';
$user->email = 'john@example.com';
$user->save();

// Method 2: Create with array
$user = new User([
    'name' => 'Jane Doe',
    'email' => 'jane@example.com'
]);
$user->save();

// Method 3: Create in one line
$user = User::create([
    'name' => 'Mike Smith',
    'email' => 'mike@example.com'
]);

// Method 4: First or create (prevents duplicates)
$user = User::firstOrCreate(
    ['email' => 'john@example.com'], // Check if exists
    ['name' => 'John Doe']           // Create with these attributes
);

// Method 5: First or new
$user = User::firstOrNew(
    ['email' => 'sarah@example.com'],
    ['name' => 'Sarah Wilson']
);
$user->save(); // Only saves if it's new

Read (Retrieve Records)

// Get all records
$users = User::all();

// Find by primary key
$user = User::find(1);

// Find or fail (throws ModelNotFoundException)
$user = User::findOrFail(999); // Throws exception if not found

// Get first record
$user = User::first();

// Get specific columns
$users = User::select('name', 'email')->get();

// Chunk results for large datasets
User::chunk(200, function ($users) {
    foreach ($users as $user) {
        // Process 200 users at a time
    }
});

// Lazy loading for memory efficiency
foreach (User::lazy() as $user) {
    // Processes one user at a time in memory
}

// Cursor (even more memory efficient)
foreach (User::cursor() as $user) {
    // Uses PHP generators
}

Update Records

// Method 1: Retrieve, update, save
$user = User::find(1);
$user->name = 'Updated Name';
$user->save();

// Method 2: Mass update
User::where('active', false)
    ->update(['active' => true]);

// Method 3: Update or create
User::updateOrCreate(
    ['email' => 'john@example.com'], // Find by these attributes
    ['name' => 'John Updated']       // Update with these attributes
);

// Method 4: Increment/Decrement
$post = Post::find(1);
$post->increment('views');
$post->increment('votes', 5);
$post->decrement('credits', 10);

Delete Records

// Method 1: Retrieve and delete
$user = User::find(1);
$user->delete();

// Method 2: Direct delete
User::where('active', false)->delete();

// Method 3: Destroy by primary key
User::destroy(1);
User::destroy([1, 2, 3]);
User::destroy(1, 2, 3);

// Method 4: Soft delete (if enabled)
$user->delete(); // Sets deleted_at timestamp

// Method 5: Force delete (ignores soft delete)
$user->forceDelete();

// Method 6: Delete all models (truncate)
User::truncate();

Eloquent Query Building

Basic Where Clauses

// Simple where
$users = User::where('votes', '>', 100)->get();

// Multiple conditions
$users = User::where('votes', '>', 100)
            ->where('name', 'John')
            ->get();

// OR conditions
$users = User::where('votes', '>', 100)
            ->orWhere('name', 'John')
            ->get();

// Array of conditions
$users = User::where([
    ['status', '=', 'active'],
    ['votes', '>=', 100]
])->get();

// Additional where methods
$users = User::whereIn('category_id', [1, 2, 3])->get();
$users = User::whereNotIn('category_id', [4, 5, 6])->get();
$users = User::whereNull('updated_at')->get();
$users = User::whereNotNull('updated_at')->get();
$users = User::whereBetween('votes', [1, 100])->get();
$users = User::whereNotBetween('votes', [101, 200])->get();
$users = User::whereDate('created_at', '2023-12-25')->get();
$users = User::whereMonth('created_at', '12')->get();
$users = User::whereDay('created_at', '25')->get();
$users = User::whereYear('created_at', '2023')->get();
$users = User::whereTime('created_at', '=', '14:30:00')->get();
$users = User::whereColumn('updated_at', '>', 'created_at')->get();

Ordering, Grouping, and Limits

// Ordering
$users = User::orderBy('name', 'desc')->get();
$users = User::latest()->get(); // Order by created_at desc
$users = User::oldest()->get(); // Order by created_at asc
$users = User::inRandomOrder()->get();

// Grouping and Having
$users = User::groupBy('account_id')
            ->having('account_id', '>', 100)
            ->get();

// Limits and Offsets
$users = User::limit(10)->get();
$users = User::offset(10)->limit(5)->get(); // Skip 10, take 5
$users = User::take(5)->get(); // Alias for limit

// Distinct results
$users = User::distinct()->get();

Aggregates

// Count
$count = User::count();
$count = User::where('votes', '>', 100)->count();

// Other aggregates
$max = User::max('votes');
$min = User::min('votes');
$avg = User::avg('votes');
$sum = User::sum('votes');

// Check if record exists
if (User::where('email', $email)->exists()) {
    // Email exists
}

// Check if no records exist
if (User::where('email', $email)->doesntExist()) {
    // Email doesn't exist
}

Eloquent Relationships

One-to-One Relationship

// User has one Profile
class User extends Model
{
    public function profile()
    {
        return $this->hasOne(Profile::class);
    }
}

class Profile extends Model
{
    public function user()
    {
        return $this->belongsTo(User::class);
    }
}

// Usage
$user = User::find(1);
$profile = $user->profile; // Get user's profile
$user = $profile->user;    // Get profile's user

One-to-Many Relationship

// User has many Posts
class User extends Model
{
    public function posts()
    {
        return $this->hasMany(Post::class);
    }
}

class Post extends Model
{
    public function user()
    {
        return $this->belongsTo(User::class);
    }
}

// Usage
$user = User::find(1);
foreach ($user->posts as $post) {
    echo $post->title;
}

// Create post for user
$user->posts()->create([
    'title' => 'New Post',
    'content' => 'Post content...'
]);

Many-to-Many Relationship

// Post belongs to many Tags
class Post extends Model
{
    public function tags()
    {
        return $this->belongsToMany(Tag::class);
    }
}

class Tag extends Model
{
    public function posts()
    {
        return $this->belongsToMany(Post::class);
    }
}

// Usage
$post = Post::find(1);
foreach ($post->tags as $tag) {
    echo $tag->name;
}

// Attach/detach tags
$post->tags()->attach($tagId);
$post->tags()->attach([1, 2, 3]);
$post->tags()->detach(1);
$post->tags()->sync([1, 2, 3]); // Only these tags will remain

Advanced Relationship Types

// Has Many Through
class Country extends Model
{
    public function posts()
    {
        return $this->hasManyThrough(Post::class, User::class);
    }
}

// Polymorphic Relations
class Post extends Model
{
    public function comments()
    {
        return $this->morphMany(Comment::class, 'commentable');
    }
}

class Video extends Model
{
    public function comments()
    {
        return $this->morphMany(Comment::class, 'commentable');
    }
}

class Comment extends Model
{
    public function commentable()
    {
        return $this->morphTo();
    }
}

Eager Loading for Performance

The N+1 Query Problem

// ❌ BAD: N+1 queries
$posts = Post::all();
foreach ($posts as $post) {
    echo $post->user->name; // Makes a new query for each post
}

// ✅ GOOD: Eager loading (2 queries total)
$posts = Post::with('user')->get();
foreach ($posts as $post) {
    echo $post->user->name; // No additional queries
}

Advanced Eager Loading

// Load multiple relationships
$posts = Post::with(['user', 'tags', 'comments'])->get();

// Nested eager loading
$posts = Post::with('user.profile')->get();

// Eager load with constraints
$posts = Post::with(['comments' => function ($query) {
    $query->where('approved', true);
}])->get();

// Lazy eager loading
$posts = Post::all();
$posts->load('user', 'tags');

// Conditional relationships
$posts = Post::with(['user', 'tags' => function ($query) {
    $query->where('active', true);
}])->get();

Model Features and Advanced Techniques

Accessors and Mutators

class User extends Model
{
    // Accessor: Get formatted value
    public function getNameAttribute($value)
    {
        return ucwords($value);
    }
    
    // Mutator: Set formatted value before save
    public function setNameAttribute($value)
    {
        $this->attributes['name'] = strtolower($value);
    }
    
    // Virtual attribute (doesn't exist in database)
    public function getFullNameAttribute()
    {
        return "{$this->first_name} {$this->last_name}";
    }
}

// Usage
$user = User::find(1);
echo $user->name; // Automatically formatted
echo $user->full_name; // Virtual attribute

$user->name = 'JOHN DOE'; // Automatically converted to lowercase
$user->save();

Attribute Casting

class Post extends Model
{
    protected $casts = [
        'is_published' => 'boolean',
        'metadata' => 'array',
        'published_at' => 'datetime',
        'views' => 'integer',
        'rating' => 'decimal:2',
        'options' => 'object',
    ];
}

// Usage
$post = Post::find(1);
if ($post->is_published) { // Automatically cast to boolean
    // ...
}

$post->metadata = ['key' => 'value']; // Automatically serialized to JSON
$post->save();

Query Scopes

class Post extends Model
{
    // Local scope
    public function scopePublished($query)
    {
        return $query->where('is_published', true);
    }
    
    public function scopePopular($query, $minViews = 1000)
    {
        return $query->where('views', '>=', $minViews);
    }
    
    // Dynamic scope
    public function scopeOfType($query, $type)
    {
        return $query->where('type', $type);
    }
    
    // Global scope (applied to all queries)
    protected static function booted()
    {
        static::addGlobalScope('active', function ($query) {
            $query->where('is_active', true);
        });
    }
}

// Usage
$posts = Post::published()->get();
$posts = Post::popular(5000)->get();
$posts = Post::ofType('blog')->get();

Model Events

class Post extends Model
{
    protected static function booted()
    {
        static::creating(function ($post) {
            $post->slug = Str::slug($post->title);
        });
        
        static::created(function ($post) {
            // Send notification, update cache, etc.
        });
        
        static::updating(function ($post) {
            $post->updated_by = auth()->id();
        });
    }
}

Practical Examples

Complete Blog System

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;

class Post extends Model
{
    use HasFactory, SoftDeletes;

    protected $fillable = [
        'title', 'slug', 'content', 'excerpt', 'featured_image',
        'is_published', 'published_at', 'user_id', 'category_id'
    ];

    protected $casts = [
        'is_published' => 'boolean',
        'published_at' => 'datetime',
        'metadata' => 'array',
    ];

    protected $attributes = [
        'views' => 0,
        'is_published' => false,
    ];

    // Relationships
    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }

    public function category(): BelongsTo
    {
        return $this->belongsTo(Category::class);
    }

    public function tags(): BelongsToMany
    {
        return $this->belongsToMany(Tag::class);
    }

    public function comments(): HasMany
    {
        return $this->hasMany(Comment::class);
    }

    // Scopes
    public function scopePublished($query)
    {
        return $query->where('is_published', true)
                    ->where('published_at', '<=', now());
    }

    public function scopePopular($query, $minViews = 1000)
    {
        return $query->where('views', '>=', $minViews);
    }

    public function scopeByCategory($query, $categoryId)
    {
        return $query->where('category_id', $categoryId);
    }

    // Accessors
    public function getReadingTimeAttribute()
    {
        $wordCount = str_word_count(strip_tags($this->content));
        return ceil($wordCount / 200); // 200 words per minute
    }

    public function getExcerptAttribute($value)
    {
        return $value ?: Str::limit(strip_tags($this->content), 150);
    }

    // Mutators
    public function setTitleAttribute($value)
    {
        $this->attributes['title'] = $value;
        $this->attributes['slug'] = Str::slug($value);
    }

    // Business logic
    public function publish()
    {
        $this->update([
            'is_published' => true,
            'published_at' => now(),
        ]);
    }

    public function unpublish()
    {
        $this->update([
            'is_published' => false,
            'published_at' => null,
        ]);
    }

    public function incrementViews()
    {
        $this->increment('views');
    }
}

// Usage examples
$publishedPosts = Post::published()->with(['user', 'category', 'tags'])->get();

$popularPosts = Post::published()
                   ->popular(5000)
                   ->orderBy('views', 'desc')
                   ->take(10)
                   ->get();

$post = Post::create([
    'title' => 'My New Post',
    'content' => 'Post content here...',
    'category_id' => 1,
    'user_id' => auth()->id(),
]);

$post->tags()->attach([1, 2, 3]);
$post->publish();

Common Interview Questions & Answers

1. What is Eloquent ORM?

Eloquent is Laravel's ActiveRecord ORM implementation that provides an intuitive, object-oriented interface for database interactions, allowing you to work with database records as PHP objects.

2. What's the difference between find() and findOrFail()?

find() returns null if the record doesn't exist, while findOrFail() throws a ModelNotFoundException which can be caught and handled appropriately.

3. What is eager loading and why is it important?

Eager loading solves the N+1 query problem by loading related models in a single query instead of making separate queries for each parent model, significantly improving performance.

4. What are accessors and mutators?

Accessors format attribute values when retrieving them from models, while mutators format attribute values before saving them to the database.

5. What is the difference between save() and create()?

save() is used on existing model instances, while create() is a static method that both instantiates the model and saves it to the database in one step.

6. How do you define relationships in Eloquent?

Relationships are defined as methods in model classes that return relationship type objects like hasOne, hasMany, belongsTo, belongsToMany, etc.

Best Practices

  • Use eager loading to avoid N+1 query problems
  • Always type hint relationships for better IDE support
  • Use model events for business logic that should run automatically
  • Define $fillable or $guarded for mass assignment protection
  • Use query scopes for reusable query logic
  • Leverage attribute casting for automatic data type conversion
  • Use model factories for testing
  • Implement soft deletes when you might need to restore data

Performance Tips

// ❌ Avoid N+1 problems
$posts = Post::all();
foreach ($posts as $post) {
    echo $post->user->name; // Makes new query for each post
}

// ✅ Use eager loading
$posts = Post::with('user')->get();

// ❌ Don't select all columns
$users = User::get();

// ✅ Select only needed columns
$users = User::select('id', 'name', 'email')->get();

// ❌ Avoid loading large datasets into memory
$users = User::all(); // Could be millions of records!

// ✅ Use chunking for large datasets
User::chunk(1000, function ($users) {
    foreach ($users as $user) {
        // Process in batches
    }
});

Now you understand why Eloquent ORM is truly a game-changer for Laravel development! In our next post, we'll explore Laravel Eloquent Models: A Crash Course on Interacting with Your Database to dive deeper into model-specific features and techniques.