مقاله

آموزش روابط یک‌به‌چند، چند‌به‌چند و polymorphic در Laravel 12 با مثال Post، Category، Tag و User — از migration تا eager load.

روابط Eloquent در Laravel — راهنمای کامل hasMany، belongsTo و بقیه

روابط Eloquent در Laravel — راهنمای کامل hasMany، belongsTo و بقیه

بدون روابط درست Eloquent، هر query دستی می‌نویسید و N+1 شما را می‌خورد. در پروژه‌های وبلاگ، فروشگاه و CRM تقریباً همه چیز به relation ختم می‌شود: User دارد Post، Post دارد Category و Tag. این راهنما همان الگوهایی است که در production استفاده می‌کنم — نه تعریف خشک، بلکه تصمیم‌گیری «کدام relation؟»

چرا relation را درست تعریف کنیم؟

relation درست = کد خوانا، eager load آسان، policy و authorization ساده‌تر. relation غلط = join دستی، باگ ساکت، refactor دردناک. مستندات: Eloquent Relationships.

belongsTo و hasMany — جفت inseparable

Post متعلق به Category است؛ Category چند Post دارد.

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

// Category.php
public function posts(): HasMany
{
    return $this->hasMany(Post::class);
}

foreign key پیش‌فرض: category_id روی جدول posts. اگر نام متفاوت است: belongsTo(Category::class, 'cat_id').

قانون انگشت: belongsTo جایی که foreign key است

ستون user_id روی orders → Order belongsTo User.

hasOne — یک‌به‌یک

User یک Profile دارد:

public function profile(): HasOne
{
    return $this->hasOne(Profile::class);
}

foreign key روی profiles.user_id است.

belongsToMany — چند‌به‌چند

Post و Tag با pivot post_tag:

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

// با فیلد اضافه pivot
return $this->belongsToMany(Tag::class)->withPivot('sort_order')->withTimestamps();

migration pivot:

Schema::create('post_tag', function (Blueprint $table) {
    $table->foreignId('post_id')->constrained()->cascadeOnDelete();
    $table->foreignId('tag_id')->constrained()->cascadeOnDelete();
    $table->primary(['post_id', 'tag_id']);
});

Polymorphic — یک relation به چند model

Comment روی Post یا Product:

// Comment
public function commentable(): MorphTo
{
    return $this->morphTo();
}

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

ستون‌ها: commentable_id، commentable_type.

Has Many Through

Country از طریق User به Post می‌رسد — کمتر روزمره؛ وقتی زنجیره واضح است.

تعریف inverse همیشه

هر hasMany باید belongsTo طرف مقابل داشته باشد — برای associate() و dissociate() و خوانایی.

Query روی relation

$category->posts()->where('status', 'published')->latest()->get();

Post::whereHas('category', fn ($q) => $q->where('slug', 'laravel'))->get();

اشتباهات رایج

  • foreign key بدون index
  • نام relation مفرد/جمع اشتباه (tag() به‌جای tags())
  • حلقه بی‌نهایت toArray با relation تودرتو
  • فراموش کردن cascade در migration

بیشتر: اشتباهات مبتدی.

قدم بعد

Eager Loading، Scope، بهینه‌سازی.

معماری دیتابیس پروژه