0

One-to-many relationships — hasMany, belongsTo

Beginner5 min read·lv-12-006
sqlinterview

Concept

Eloquent relationships define how models relate to each other. They are defined as methods on the model and return relationship objects that extend Illuminate\Database\Eloquent\Relations\Relation. Accessing a relationship as a property triggers a query; calling it as a method returns the relationship builder for further chaining.

Types of relationships:

RelationshipLaravel MethodSQL Pattern
One-to-onehasOneparent.id ← child.parent_id
One-to-one (inverse)belongsTomodel.owner_id → owner.id
One-to-manyhasManyparent.id ← children.parent_id
Many-to-manybelongsToManypivot table
Has-one-throughhasOneThroughindirect via intermediate
Has-many-throughhasManyThroughindirect via intermediate
Polymorphic one-to-onemorphOne / morphTo*_type + *_id
Polymorphic one-to-manymorphMany / morphTo*_type + *_id
Polymorphic many-to-manymorphToMany / morphedByManypolymorphic pivot

Property vs method access:

  • $user->posts — property access. Returns a Collection. Triggers a DB query if not loaded.
  • $user->posts() — method access. Returns HasMany builder. No query yet. Use for chaining: $user->posts()->where('published', true)->get().

Conventions:

  • hasOne/hasMany: foreign key is parent_model_id on the related table.
  • belongsTo: foreign key is related_model_id on THIS model's table.
  • Keys are inferred from method names but can be overridden.

Code Example

php
<?php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;

class User extends Model
{
    // hasOne — user has one profile (profiles.user_id → users.id)
    public function profile(): HasOne
    {
        return $this->hasOne(Profile::class);
    }

    // hasMany — user has many posts
    public function posts(): HasMany
    {
        return $this->hasMany(Post::class);
    }

    // hasMany with custom foreign key
    public function orders(): HasMany
    {
        return $this->hasMany(Order::class, 'customer_id', 'id');
        // hasMany($related, $foreignKey, $localKey)
    }

    // belongsToMany — user belongs to many roles (via role_user pivot)
    public function roles(): BelongsToMany
    {
        return $this->belongsToMany(Role::class);
    }
}

class Post extends Model
{
    // belongsTo — post belongs to a user
    public function author(): BelongsTo
    {
        return $this->belongsTo(User::class, 'user_id');
    }

    // belongsTo — optional/nullable
    public function category(): BelongsTo
    {
        return $this->belongsTo(Category::class)->withDefault(['name' => 'Uncategorized']);
    }
}

// Usage
$user = User::find(1);
$profile = $user->profile;         // property: executes SELECT + LIMIT 1
$posts = $user->posts;             // property: returns Collection

// Method access — returns builder for chaining
$publishedPosts = $user->posts()
    ->where('published', true)
    ->orderBy('published_at', 'desc')
    ->get();

$post = Post::find(1);
$author = $post->author;           // belongsTo property
echo $author->name;