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:
| Relationship | Laravel Method | SQL Pattern |
|---|---|---|
| One-to-one | hasOne | parent.id ← child.parent_id |
| One-to-one (inverse) | belongsTo | model.owner_id → owner.id |
| One-to-many | hasMany | parent.id ← children.parent_id |
| Many-to-many | belongsToMany | pivot table |
| Has-one-through | hasOneThrough | indirect via intermediate |
| Has-many-through | hasManyThrough | indirect via intermediate |
| Polymorphic one-to-one | morphOne / morphTo | *_type + *_id |
| Polymorphic one-to-many | morphMany / morphTo | *_type + *_id |
| Polymorphic many-to-many | morphToMany / morphedByMany | polymorphic pivot |
Property vs method access:
$user->posts— property access. Returns aCollection. Triggers a DB query if not loaded.$user->posts()— method access. ReturnsHasManybuilder. No query yet. Use for chaining:$user->posts()->where('published', true)->get().
Conventions:
hasOne/hasMany: foreign key isparent_model_idon the related table.belongsTo: foreign key isrelated_model_idon 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;