Eloquent İlişkileri (Relationships)
İlişkiler, veritabanı tablolarınız arasındaki bağlantıları tanımlar. Laravel, tüm yaygın ilişki türlerini destekler.
🎯 Önemli: İlişkileri doğru anlamak, N+1 query probleminden kaçınmak ve verimli sorgular yazmak için kritiktir.
One-to-One (Bire-Bir)
Bir kullanıcının bir profili olması gibi.
// Migration: profiles tablosu
Schema::create('profiles', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->unique()->constrained()->cascadeOnDelete();
$table->string('bio')->nullable();
$table->string('avatar')->nullable();
$table->date('birth_date')->nullable();
$table->timestamps();
});
// User Model
class User extends Model
{
public function profile()
{
return $this->hasOne(Profile::class);
// users.id = profiles.user_id
}
}
// Profile Model
class Profile extends Model
{
public function user()
{
return $this->belongsTo(User::class);
}
}
// Kullanım
$user = User::find(1);
$profile = $user->profile; // Profile instance
$bio = $user->profile->bio;
$profile = Profile::find(1);
$user = $profile->user; // User instance
// Oluşturma
$user->profile()->create([
'bio' => 'Merhaba dünya!',
'avatar' => 'avatar.jpg',
]);
One-to-Many (Bire-Çok)
Bir yazarın birden fazla blog yazısı olması gibi.
// Migration: posts tablosu
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->string('title');
$table->text('content');
$table->timestamps();
});
// User Model
class User extends Model
{
public function posts()
{
return $this->hasMany(Post::class);
// users.id = posts.user_id
}
}
// Post Model
class Post extends Model
{
public function user()
{
return $this->belongsTo(User::class);
}
// Alternatif isim (author)
public function author()
{
return $this->belongsTo(User::class, 'user_id');
}
}
// Kullanım
$user = User::find(1);
$posts = $user->posts; // Collection of Posts
$postCount = $user->posts()->count();
$post = Post::find(1);
$author = $post->user; // User instance
$authorName = $post->user->name;
// Oluşturma
$user->posts()->create([
'title' => 'Yeni Yazı',
'content' => 'İçerik...',
]);
// Birden fazla
$user->posts()->createMany([
['title' => 'Yazı 1', 'content' => '...'],
['title' => 'Yazı 2', 'content' => '...'],
]);
Many-to-Many (Çoka-Çok)
Bir yazının birden fazla etiketi, bir etiketin birden fazla yazısı olması gibi.
// Migration: tags tablosu
Schema::create('tags', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('slug')->unique();
$table->timestamps();
});
// Migration: pivot tablosu (post_tag)
// İsimlendirme: alfabetik sıra, tekil (post_tag, not posts_tags)
Schema::create('post_tag', function (Blueprint $table) {
$table->id();
$table->foreignId('post_id')->constrained()->cascadeOnDelete();
$table->foreignId('tag_id')->constrained()->cascadeOnDelete();
$table->timestamps();
$table->unique(['post_id', 'tag_id']);
});
// Post Model
class Post extends Model
{
public function tags()
{
return $this->belongsToMany(Tag::class);
// Otomatik pivot: post_tag tablosu
}
// Pivot tablosu özelleştirme
public function tagsWithTimestamps()
{
return $this->belongsToMany(Tag::class)
->withTimestamps()
->withPivot('order', 'is_primary');
}
}
// Tag Model
class Tag extends Model
{
public function posts()
{
return $this->belongsToMany(Post::class);
}
}
// Kullanım
$post = Post::find(1);
$tags = $post->tags; // Collection of Tags
$tag = Tag::find(1);
$posts = $tag->posts; // Collection of Posts
// İlişki yönetimi
$post->tags()->attach(1); // Tek tag ekle
$post->tags()->attach([1, 2, 3]); // Çoklu ekle
$post->tags()->detach(1); // Tek tag çıkar
$post->tags()->detach([1, 2]); // Çoklu çıkar
$post->tags()->detach(); // Tümünü çıkar
$post->tags()->sync([1, 2, 3]); // Sadece bunlar kalsın
$post->tags()->syncWithoutDetaching([4, 5]); // Ekle, silme
$post->tags()->toggle([1, 2]); // Varsa çıkar, yoksa ekle
// Pivot verileri ile
$post->tags()->attach(1, ['is_primary' => true]);
$post->tags()->sync([
1 => ['is_primary' => true],
2 => ['is_primary' => false],
]);
// Pivot verilerine erişim
foreach ($post->tags as $tag) {
echo $tag->pivot->created_at;
echo $tag->pivot->is_primary;
}
Has Many Through
Ara tablo üzerinden ilişki. Örneğin: Ülke -> Kullanıcılar -> Yazılar
// Country Model
class Country extends Model
{
public function posts()
{
return $this->hasManyThrough(
Post::class, // Final model
User::class, // Ara model
'country_id', // users tablosundaki foreign key
'user_id', // posts tablosundaki foreign key
'id', // countries tablosundaki local key
'id' // users tablosundaki local key
);
}
}
// Kullanım
$country = Country::find(1);
$posts = $country->posts; // Türkiye'deki tüm kullanıcıların yazıları
Polymorphic İlişkiler
Bir model, birden fazla farklı modele ait olabilir. Örneğin: Yorumlar hem yazılara hem de videolara yapılabilir.
// Migration: comments tablosu
Schema::create('comments', function (Blueprint $table) {
$table->id();
$table->text('body');
$table->morphs('commentable'); // commentable_type ve commentable_id
$table->timestamps();
});
// Comment Model
class Comment extends Model
{
public function commentable()
{
return $this->morphTo();
}
}
// Post Model
class Post extends Model
{
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
}
// Video Model
class Video extends Model
{
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
}
// Kullanım
$post = Post::find(1);
$comments = $post->comments;
$video = Video::find(1);
$comments = $video->comments;
$comment = Comment::find(1);
$parent = $comment->commentable; // Post veya Video instance
// Oluşturma
$post->comments()->create(['body' => 'Harika yazı!']);
$video->comments()->create(['body' => 'Güzel video!']);
Eager Loading (N+1 Problemi Çözümü)
⚠️ N+1 Query Problemi: En yaygın performans sorunlarından biri. Her ilişki için ayrı sorgu çalıştırılması.
// ❌ N+1 Problemi
$posts = Post::all();
foreach ($posts as $post) {
echo $post->user->name; // Her post için ayrı sorgu!
}
// 1 (posts) + N (users) = N+1 sorgu
// ✅ Eager Loading ile çözüm
$posts = Post::with('user')->get();
foreach ($posts as $post) {
echo $post->user->name; // Sorgu yok, zaten yüklü
}
// Sadece 2 sorgu: posts ve users
// Birden fazla ilişki
$posts = Post::with(['user', 'tags', 'category'])->get();
// İç içe ilişkiler
$posts = Post::with('user.profile')->get();
$posts = Post::with(['user.profile', 'comments.user'])->get();
// Koşullu eager loading
$posts = Post::with(['comments' => function ($query) {
$query->where('is_approved', true)
->orderBy('created_at', 'desc');
}])->get();
// Lazy eager loading (sonradan yükleme)
$posts = Post::all();
$posts->load('user');
$posts->load(['tags', 'category']);
// Sayım ile
$posts = Post::withCount('comments')->get();
foreach ($posts as $post) {
echo $post->comments_count;
}
Önemli Noktalar
- hasOne/belongsTo: One-to-One ilişki
- hasMany/belongsTo: One-to-Many ilişki
- belongsToMany: Many-to-Many (pivot tablo gerekir)
- morphTo/morphMany: Polymorphic ilişkiler
- with() ile eager loading N+1 problemini çözer