Laravel Eloquent ORM Guide

Eloquent ORM patterns: model definition, relationships, query scopes, accessors/mutators, eager loading, and mass assignment.

1. Model Definition

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Factories\HasFactory;

class Article extends Model
{
    use HasFactory, SoftDeletes;

    protected $table = 'articles';   // explicit table name (optional)
    protected $primaryKey = 'id';
    public $timestamps = true;

    // Fillable fields (whitelist for mass assignment)
    protected $fillable = ['title', 'slug', 'content', 'status', 'author_id'];

    // Hidden fields (excluded from JSON output)
    protected $hidden = ['deleted_at'];

    // Cast types
    protected $casts = [
        'published_at' => 'datetime',
        'metadata'     => 'array',
        'is_featured'  => 'boolean',
        'view_count'   => 'integer',
    ];
}

2. Relationships

class Article extends Model
{
    // BelongsTo (many articles โ†’ one author)
    public function author()
    {
        return $this->belongsTo(User::class, 'author_id');
    }

    // HasMany (one article โ†’ many comments)
    public function comments()
    {
        return $this->hasMany(Comment::class)->latest();
    }

    // BelongsToMany (article โ†” tags)
    public function tags()
    {
        return $this->belongsToMany(Tag::class, 'article_tags')
                    ->withTimestamps()
                    ->withPivot('order');
    }

    // HasOneThrough
    public function authorProfile()
    {
        return $this->hasOneThrough(Profile::class, User::class,
            'id',         // users.id
            'user_id',    // profiles.user_id
            'author_id',  // articles.author_id
        );
    }

    // Polymorphic
    public function likes()
    {
        return $this->morphMany(Like::class, 'likeable');
    }
}

3. Scopes

class Article extends Model
{
    // Local scope โ€” called as ->published()
    public function scopePublished($query)
    {
        return $query->where('status', 'published');
    }

    // Parameterized scope
    public function scopeByAuthor($query, $authorId)
    {
        return $query->where('author_id', $authorId);
    }

    // Global scope โ€” always applied
    protected static function booted()
    {
        static::addGlobalScope('active', function ($query) {
            $query->where('is_active', true);
        });
    }
}

// Usage
$articles = Article::published()->byAuthor(1)->latest()->paginate(15);
$articles = Article::withoutGlobalScope('active')->get();

4. Accessors & Mutators

use Illuminate\Database\Eloquent\Casts\Attribute;

class Article extends Model
{
    // Accessor โ€” computed property
    protected function readingTime(): Attribute
    {
        return Attribute::make(
            get: fn () => ceil(str_word_count($this->content) / 200) . ' min',
        );
    }

    // Mutator โ€” transform on set
    protected function title(): Attribute
    {
        return Attribute::make(
            get: fn ($v) => ucfirst($v),
            set: fn ($v) => trim($v),
        );
    }
}

// Access: $article->reading_time, $article->title

5. Eager Loading

// with() โ€” eager load
$articles = Article::with(['author', 'tags', 'comments.user'])->published()->get();

// Constrained eager loading
$articles = Article::with([
    'comments' => fn($q) => $q->where('approved', true)->latest()->take(5),
    'tags:id,name,color',
])->get();

// withCount โ€” adds {relation}_count attribute
$articles = Article::withCount(['comments', 'likes'])->get();
// Access: $article->comments_count

// Prevent N+1 globally
Model::preventLazyLoading(! app()->isProduction());

6. Query Builder Methods

MethodPurpose
where / orWhereAdd WHERE conditions
whereIn / whereNotInIN clause
whereBetweenBETWEEN a AND b
whereNull / whereNotNullIS NULL check
orderBy / latest / oldestSort results
select / addSelectColumn selection
groupBy / havingAggregation
join / leftJoinRaw JOIN
paginate / simplePaginatePagination
chunk / cursorMemory-efficient iteration