İçeriğe atla
Üyelik kaydınızı yaparak son yazılan içeriklerden haberdar olun! ×

Laravel

  • makale
    24
  • yorum
    0
  • görüntüleme
    113.645

Laravel Eloquent İlişkiler


Doğuhan ELMA

86 görünüm

Genellikle her geliştiricinin hayatında bir veritabanı ile etkileşime girmeniz gereken bir nokta vardır. İşte bu noktada Laravel'in nesne-ilişkisel eşleyicisi (ORM) Eloquent, veritabanı tablolarınızla etkileşim sürecini sezgisel ve doğal hale getirir.

Bir profesyonel olarak, üzerinden geçeceğimiz ve gözden geçireceğimiz altı temel ilişki türünü tanımanız ve anlamanız hayati önem taşımaktadır.

 

Eloquent'te İlişkiler Nedir?
İlişkisel bir veritabanında tablolarla çalışırken, ilişkileri tablolar arasındaki bağlantılar olarak nitelendirebiliriz. Bu, verileri zahmetsizce düzenlemenize ve yapılandırmanıza yardımcı olarak verilerin üstün okunabilirliğini ve işlenmesini sağlar. Uygulamada üç tür veritabanı ilişkisi vardır:

bire-bir(one-to-one) - Bir tablodaki bir kayıt, başka bir tablodaki bir kayıtla ve yalnızca bir kayıtla ilişkilidir. Örneğin, bir kişi ve bir sosyal güvenlik numarası.
bire-çok(one-to-many) - Bir kayıt başka bir tablodaki birden çok kayıtla ilişkilidir. Örneğin, bir yazar ve blogları.
çoktan çoğa(many-to-many) - Bir tablodaki birden çok kayıt, başka bir tablodaki birden çok kayıtla ilişkilendirilir. Örneğin, öğrenciler ve kayıtlı oldukları dersler.

 

Laravel, Eloquent'te nesne yönelimli sözdizimi kullanarak veritabanı ilişkilerini etkileşime sokmayı ve yönetmeyi sorunsuz hale getirir.

Bu tanımların yanı sıra, Laravel daha fazla ilişki sunar, yani:

Has Many Through
Polymorphic Relations
Many-to-many Polymorphic


Örneğin, envanterinde her biri kendi kategorisinde çeşitli ürünler bulunan bir mağazayı ele alalım. Bu nedenle, veritabanını birden fazla tabloya bölmek iş açısından mantıklıdır. Her bir tabloyu tek tek sorgulamak istemediğiniz için bu durum kendi sorunlarını da beraberinde getirir.

pasted-image-0-6.png

 

Üç tablo ve polimorfik bir ilişkiyi temsil eden bir ortak tablo içeren veritabanı şeması

 

One-To-One Relationship:

Laravel'in sunduğu ilk temel ilişki olarak, iki tabloyu öyle bir şekilde ilişkilendirirler ki, ilk tablodaki bir satır diğer tablodaki sadece bir satırla ilişkilendirilir.

Bunu iş başında görmek için, kendi geçişlerine sahip iki model oluşturmalıyız: 

php artisan make:model Tenant 
Php artisan make:model Rent

Bu noktada biri Kiracı, diğeri Kiralık olmak üzere iki modelimiz var.

namespace App\Models;
use Illuminate\Database\Eloquent\Model;

class Tenant extends Model
{
    /**
    * Kiracının, kiralığını getir(bir varlık döner)
    */
    public function rent() 
    {
        return $this->hasOne(Rent::class);
    }
}

 

Migration:

...
Schema::create('rents', function (Blueprint $table) {
            $table->id();
            $table->foreignId('tenant_id')->constrained('tenants');
            $table->timestamps();
        });

...

 

eloquent yabancı anahtar ilişkisini üst model adına (bu durumda Tenant) göre belirlediğinden, Rent modeli bir tenant_id yabancı anahtarının var olduğunu varsayar.

hasOne yöntemine ek bir argüman ekleyerek kolayca üzerine yazabiliriz:

...
return $this- >hasOne(Rent::class, "custom_key");
...

Eloquent ayrıca tanımlanan yabancı anahtar ile üst öğenin (Tenant modeli) birincil anahtarı arasında bir eşleşme olduğunu varsayar. Varsayılan olarak, tenant_id ile Tenant kaydının id anahtarını eşleştirmeye çalışacaktır. Bunun üzerine hasOne yönteminde üçüncü bir bağımsız değişken ekleyerek başka bir anahtarla eşleşmesini sağlayabiliriz:

...
return $this->hasOne(Rent::class, "custom_key", "other_key");
...

 

Artık modeller arasındaki bire bir ilişkiyi tanımladığımıza göre, bunu aşağıdaki gibi kolayca kullanabiliriz:

...
$rent = Tenant::find(10)->rent;
...

Bu kod satırında 10 id sahip kiracının kiralık varlığını(laravel tarafında nesnesi) döndürüyoruz.

 

One-To-Many İlişkisi:
Önceki ilişkide olduğu gibi, bu da tek ebeveynli bir model ile birden fazla çocuk modeli arasındaki ilişkileri tanımlayacaktır. Kiracımızın tek bir Kira faturası olması pek olası değildir çünkü bu yinelenen bir ödemedir, bu nedenle birden fazla ödemesi olacaktır.

Bu durumda, önceki ilişkimizin kusurları vardır ve bunları düzeltebiliriz:

namespace App\Models;
use Illuminate\Database\Eloquent\Model;

class Tenant extends Model
{
    /**
    * Kiracının tüm kiralıklarını getir. Bir dizi nesne döner..
    */
    public function rent() 
    {
        return $this->hasMany(Rent::class);
    }
}

 

Kiraları almak için yöntemi çağırmadan önce, ilişkilerin sorgu oluşturucu olarak hizmet ettiğini bilmek iyi bir şeydir, bu nedenle daha fazla kısıtlama ekleyebilir (tarihler arasındaki kira, minimum ödeme vb. gibi) ve istediğimiz sonucu elde etmek için bunları zincirleyebiliriz:

...
$rents = Tenant::find(10)->rent()->where('payment', '>', 500)->first();
...

 

Ve önceki ilişkide olduğu gibi, ek argümanlar geçerek yabancı ve yerel anahtarların üzerine yazabiliriz:

return $this->hasMany(Rent::class, "foreign_key");
return $this->hasMany(Rent::class, "foreign_key", "local_key");

 

Artık bir kiracının tüm kirasına sahibiz, ancak kirayı bildiğimizde ve kime ait olduğunu bulmak istediğimizde ne yapacağız? belongsTo özelliğini kullanabiliriz:

namespace App\Models;
use Illuminate\Database\Eloquent\Model;

class Rent extends Model
{
    /**
    * Kira sahibi olan kiraci nesnesini döndürür. (Tek bir nesne)
    */
    public function tenant() 
    {
        return $this->belongsTo(Tenant::class);
    }
}

 

Ve şimdi kiracıyı kolayca alabiliyoruz:

$tenant = Rent::find(1)->tenant;

 

belongsTo yöntemi için, daha önce yaptığımız gibi yabancı ve yerel anahtarların üzerine de yazabiliriz.

 

Has-Of-Many İlişkisi:

Kiracı modelimiz birçok Kira modeliyle ilişkilendirilebildiğinden, ilişkilerin en son veya en eski ilgili modelini kolayca almak istiyoruz.

Bunu yapmanın uygun bir yolu hasOne ve ofMany yöntemlerini birleştirmektir:

public function latestRent() {
    return $this->hasOne(Rent::class)->latestOfMany();
}

public function oldestRent() {
    return $this->hasOne(Rent::class)->oldestOfMany();
}

 

Varsayılan olarak, verileri sıralanabilir olan birincil anahtara göre alıyoruz, ancak ofMany yöntemi için kendi filtrelerimizi oluşturabiliriz:

return $this->hasOne(Rent::class)->ofMany('price', 'min');

 

 

HasOneThrough ve HasManyThrough İlişkileri:

Geçiş yöntemleri, modellerimizin istenen modelle ilişki kurmak için başka bir modelden geçmesi gerektiğini gösterir. Örneğin, Kirayı Ev Sahibi ile ilişkilendirebiliriz, ancak Kiranın Ev Sahibine ulaşması için önce Kiracıdan geçmesi gerekir.

Bunun için gerekli tabloların anahtarları aşağıdaki gibi olacaktır:

rent
    id - integer
    name - string
    value - double

tenants
    id - integer
    name - string
    rent_id - integer

landlord
    id - integer
    name - string
    tenant_id - integer

Tablolarımızın nasıl göründüğünü görselleştirdikten sonra modelleri yapabiliriz:

namespace App\Models;
use Illuminate\Database\Eloquent\Model;

class Rent extends Model
{
    /**
    * kiracının ev sahibini döndürür.(Tek nesne)
    */
    public function rentLandlord() 
    {
        return $this->hasOneThrough(Landlord::class, Tenant::class);
    }
}

 

hasOneThrough yönteminin ilk bağımsız değişkeni erişmek istediğiniz model, ikinci bağımsız değişkeni ise içinden geçeceğiniz modeldir.

Ve tıpkı daha önce olduğu gibi, yabancı ve yerel anahtarların üzerine yazabilirsiniz. Artık iki modelimiz olduğuna göre, bu sırayla üzerine yazılacak ikişer modelimiz var:

public function rentLandlord() 
{
    return $this->hasOneThrough(
        Landlord::class,
        Tenant::class,
        "rent_id",    // Kiracı tablosundaki yabancı anahtar
        "tenant_id",  // Ev sahibi tablosundaki yabancı anahtar
        "id",         // Kiralık(rent) sınıfındaki yerel anahtar
        "id"          // Kiracı(tenant) tablosundaki yerel anahtar
    );
}

 

Benzer şekilde, Laravel Eloquent'teki "Has Many Through" ilişkisi, bir ara tablo aracılığıyla uzaktaki bir tablodaki kayıtlara erişmek istediğinizde kullanışlıdır. Üç tablo içeren bir örnek düşünelim:

  • country
  • users
  • games

Her Ülkenin birçok Kullanıcısı ve her Kullanıcının birçok Oyunu vardır. Bir Ülkeye ait tüm Oyunları Kullanıcı tablosu üzerinden almak istiyoruz.

Tabloları şu şekilde tanımlarsınız:

country
    id - integer
    name - string

user
    id - integer
    country_id - integer
    name - string

games
    id - integer
    user_id - integer
    title - string

 

Şimdi her bir tablo için Eloquent modelini tanımlamalısınız:

namespace App\Models;
use Illuminate\Database\Eloquent\Model;

class Country extends Model
{
    protected $fillable = ['name'];

    public function users()
    {
        return $this->hasMany(User::class);
    }

    public function games()
    {
        return $this->hasManyThrough(Games::class, User::class);
    }
}
namespace App\Models;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    protected $fillable = [article_id, 'name'];

    public function country()
    {
        return $this->belongsTo(Country::class);
    }

    public function posts()
    {
        return $this->hasMany(Post::class);
    }
}
namespace App\Models;
use Illuminate\Database\Eloquent\Model;

class Game extends Model
{
    protected $fillable = ['user_id', 'title'];

    public function user()
    {
        return $this->belongsTo(User::class);
    }
}

 

Artık tüm oyunları almak için country modelinin games() yöntemini çağırabiliriz çünkü Kullanıcı modeli aracılığıyla Country ve Game arasında "Has Many Through" ilişkisini kurduk.

$country = Country::find(159);
            
// Ülke için tüm oyunları al
$games = $country->games;

 

 

Many-To-Many İlşkisi:

Çoktan çoğa ilişki daha karmaşıktır. Birden fazla rolü olan bir çalışan buna iyi bir örnektir. Bir rol birden fazla çalışana da atanabilir. Bu, çoktan çoka ilişkinin temelidir.

Bunun için employees, roles ve role_employees tablolarına sahip olmalıyız.

Veritabanı tablo yapımız şu şekilde görünecektir:

employees
    id - integer
    name - string

roles 
    id - integer
    name - string

role_employees
    user_id - integer
    role_id - integer

 

İlişkinin tablo yapısını bilerek, Employee modelimizi belongToMany Role modeline kolayca tanımlayabiliriz.

namespace App\Models;
use Illuminate\Database\Eloquent\Model;

class Employee extends Model
{
    public function roles() 
    {
        return $this- >belongsToMany(Role::class);
    }
}

Bunu tanımladıktan sonra, bir çalışanın tüm rollerine erişebilir ve hatta bunları filtreleyebiliriz:

$employee = Employee::find(1);
$employee->roles->forEach(function($role) { // });

// yada 

$employee = Employee::find(1)->roles()->orderBy('name')->where('name', 'admin')->get();

 

Diğer tüm yöntemler gibi, belongsToMany yönteminin yabancı ve yerel anahtarlarının üzerine yazabiliriz.

belongsToMany'nin ters ilişkisini tanımlamak için sadece aynı yöntemi kullanırız, ancak şimdi çocuk yönteminde, bir argüman olarak ebeveyn ile.

namespace App\Models;
use Illuminate\Database\Eloquent\Model;

class Role extends Model
{
    public function employees() 
    {
        return $this->belongsToMany(Employee::class);
    }
}

 

Ara Tablonun Kullanım Alanları:

Fark etmiş olabileceğimiz gibi, çoktan çoğa ilişkiyi kullandığımızda, her zaman bir ara tabloya sahip olmamız gerekir. Bu durumda, role_employees tablosunu kullanıyoruz.

Varsayılan olarak, pivot tablomuz yalnızca id özniteliklerini içerecektir. Başka öznitelikler istiyorsak, bunları şu şekilde belirtmemiz gerekir:

return $this->belongsToMany(Employee::class)->withPivot("active", "created_at");

 

Zaman damgaları için pivotu kısaltmak istiyorsak, bunu yapabiliriz:

return $this->belongsToMany(Employee::class)->withTimestamps();

 

Bilmemiz gereken bir püf noktası, 'pivot' adını uygulamamıza daha uygun herhangi bir şeye dönüştürebileceğimizdir:

return $this->belongsToMany(Employee::class)->as('subscription')->withPivot("active", "created_by");

 

return $this->belongsToMany(Employee::class)->wherePivot('promoted', 1);
return $this->belongsToMany(Employee::class)->wherePivotIn('level', [1, 2]);
return $this->belongsToMany(Employee::class)->wherePivotNotIn('level', [2, 3]);
return $this->belongsToMany(Employee::class)->wherePivotBetween('posted_at', ['2023-01-01 00:00:00', '2023-01-02 00:00:00']);
return $this->belongsToMany(Employee::class)->wherePivotNull('expired_at');
return $this->belongsToMany(Employee::class)->wherePivotNotNull('posted_at');

 

Son bir şaşırtıcı özellik de pivotlara göre sıralama yapabilmemizdir:

return $this->belongsToMany(Employee::class)
        ->where('promoted', true)
        ->orderByPivot('hired_at', 'desc');

 

 

 

Polymorphic İlişkiler:

Polimorfik kelimesi Yunanca'dan gelir ve "birçok form" anlamına gelir. Bunun gibi, uygulamamızdaki bir model birçok form alabilir, yani birden fazla ilişkiye sahip olabilir. Bloglar, videolar, anketler vb. içeren bir uygulama oluşturduğumuzu düşünün. Bir kullanıcı bunlardan herhangi biri için bir yorum oluşturabilir. Bu nedenle, bir Yorum modeli Bloglar, Videolar ve Anketler modellerine ait olabilir.

 

Polymorphic One To One:

Bu ilişki türü standart bire bir ilişkiye benzer. Tek fark, alt modelin tek bir ilişkiyle birden fazla model türüne ait olabilmesidir.

Örneğin, bir Kiracı ve Ev Sahibi modelini ele alalım; bu model bir WaterBill modeliyle çok biçimli bir ilişkiyi paylaşabilir.

Tablo yapısı aşağıdaki gibi olabilir:

tenants
    id – integer
    name – string

landlords
    id – integer
    name – string

waterbills
    id – integer
    amount – double
    waterbillable_id
    waterbillable_type

 

Ev sahibi veya kiracının kimliği için waterbillable_id kullanıyoruz, waterbillable_type ise ana modelin sınıf adını içeriyor. Tip sütunu, eloquent tarafından hangi ana modelin döndürüleceğini bulmak için kullanılır.

Böyle bir ilişki için model tanımı aşağıdaki gibi görünecektir:

namespace App\Models;
use Illuminate\Database\Eloquent\Model;

class WaterBill extends Model
{
    public function billable()
    {
        return $this->morphTo();
    }
}

class Tenant extends Model
{
    public function waterBill()    
    {
        return $this->morphOne(WaterBill::class, 'billable');
    }
}

class Landlord extends Model
{
    public function waterBill()    
    {
        return $this->morphOne(WaterBill::class, 'billable');
    }
}

Tüm bunları yerine getirdikten sonra, hem Ev Sahibi hem de Kiracı modelinden verilere erişebiliriz:

$tenant = Tenant::find(1)->waterBill;
$landlord = Landlord::find(1)->waterBill;

 

 

Polymorphic One To Many:

Bu, normal bir-çok ilişkisine benzer; tek önemli fark, alt modelin tek bir ilişki kullanarak birden fazla model türüne ait olabilmesidir.

Facebook gibi bir uygulamada, kullanıcılar gönderilere, videolara, anketlere, canlı yayınlara vb. yorum yapabilir. Polimorfik bire çok ile, sahip olduğumuz tüm kategoriler için yorumları saklamak için tek bir yorumlar tablosu kullanabiliriz. Tablo yapımız aşağıdaki gibi görünecektir:

posts 
    id – integer
    title – string
    body – text

videos
    id – integer
    title – string
    url – string

polls
    id – integer
    title – string

comments 
    id – integer
    body – text
    commentable_id – integer
    commentable_type – string

commentable_id kaydın kimliği ve commentable_type sınıf türüdür, böylece eloquent neye bakacağını bilir. Model yapısına gelince, polimorfik bire bire çok benzer:

namespace App\Models;
use Illuminate\Database\Eloquent\Model;

class Comment extends Model 
{
    public function commentable()
    {
        return $this->morphTo();
    }
}

class Poll extends Model
{
    public function comments()
    {
        return $this->morphMany(Comment::class, 'commentable');
    }
}

class Live extends Model
{
    public function comments()
    {
        return $this->morphMany(Comments::class, 'commentable');
    }
}

 

Şimdi bir Live'ın yorumlarını almak için find metodunu id ile çağırabiliriz ve artık comments iterable sınıfına erişimimiz vardır:

use App\Models\Live;

$live = Live::find(1);

foreach ($live->comments as $comment) { }

// yada

Live::find(1)->comments()->each(function($comment) { // });
Live::find(1)->comments()->map(function($comment) { // });
Live::find(1)->comments()->filter(function($comment) { // });

 

Ve eğer bir yorumumuz varsa ve kime ait olduğunu öğrenmek istiyorsak, commentable metoduna erişiriz:

use App\Models\Comment;

$comment = Comment::find(10);
$commentable = $comment->commentable; //Post, Video, Poll, Live

 

Polymorphic One of Many:

Ölçeklendirilen birçok uygulamada, modellerle ve modeller arasında etkileşim kurmanın kolay bir yolunu isteriz. Bir kullanıcının ilk veya son gönderisini isteyebiliriz, bu da morphOne ve ofMany yöntemlerinin bir kombinasyonu ile yapılabilir:

public function latestPost()
{
    return $this->morphOne(Post::class, 'postable')->latestOfMany();
}

public function oldestPost()
{
    return $this->morphOne(Post::class, 'postable')->oldestOfMany();
}

 

latestOfMany ve oldestOfMany yöntemleri, sıralanabilir olma koşulu olan modelin birincil anahtarına dayalı olarak en son veya en eski modeli alır.

Bazı durumlarda, kimliğe göre sıralama yapmak istemeyiz, belki bazı gönderilerin yayınlanma tarihini değiştirdik ve onları kimliklerine göre değil, bu sırayla istiyoruz.

Bu konuda yardımcı olması için ofMany yöntemine 2 parametre aktarılabilir. İlk parametre filtrelemek istediğimiz anahtar, ikincisi ise sıralama yöntemidir:

 

public function latestPublishedPost()
{
    return $this->morphOne(Post::class, "postable")->ofMany("published_at", "max");
}

Bunu akılda tutarak, bunun için daha gelişmiş ilişkiler kurmak mümkün! Şu senaryoya sahip olduğumuzu düşünün. Bizden, yayınlandıkları sıraya göre tüm güncel gönderilerin bir listesini oluşturmamız isteniyor. Sorun, aynı published_at değerine sahip 2 gönderimiz olduğunda ve gönderilerin gelecekte yayınlanması planlandığında ortaya çıkar.

Bunu yapmak için, filtrelerin uygulanmasını istediğimiz sırayı ofMany yöntemine aktarabiliriz. Bu şekilde published_at'e göre sıralarız ve aynı iseler id'ye göre sıralarız. İkinci olarak, yayınlanması planlanan tüm gönderileri hariç tutmak için ofMany yöntemine bir sorgu işlevi uygulayabiliriz!

public function currentPosts()
{
    return $this->hasOne(Post::class)->ofMany([
        'published_at' => 'max',
        'id' => 'max',
    ], function ($query) {
        $query->where('published_at', '<', now());
    });
}

 

Polymorphic Many To Many:

Polimorfik çoktan çoğa normal olandan biraz daha karmaşıktır. Yaygın durumlardan biri, etiketlerin uygulamanızdaki daha fazla varlık için geçerli olmasıdır. Örneğin, TikTok'ta Videolara, Şortlara, Hikayelere vb. uygulanabilecek etiketlerimiz var.

Çok biçimli çoktan çoğa, Videolar, Kısalar ve Hikayeler ile ilişkili tek bir etiket tablosuna sahip olmamızı sağlar.

Tablo yapısı basittir:

videos
    id – integer
    description – string

stories 
    id – integer
    description – string

taggables 
    tag_id – integer
    taggable_id – integer
    taggable_type – string

 

Tablolar hazır olduğunda, modeli oluşturabilir ve morphToMany yöntemini kullanabiliriz. Bu metot model sınıfının adını ve 'ilişki adını' kabul eder:

namespace App\Models;
use Illuminate\Database\Eloquent\Model;

class Video extends Model
{
    public function tags()
    {
        return $this->morphToMany(Tag::class, 'taggable');
    }
}

Ve bununla, ters ilişkiyi kolayca tanımlayabiliriz. Her çocuk model için morphedByMany metodunu çağırmak istediğimizi biliyoruz:

namespace App\Models;
use Illuminate\Database\Eloquent\Model;

class Tag extends Model
{
    public function stories()
    {
        return $this->morphedByMany(Story::class, 'taggable');
    }

    public function videos()
    {
        return $this->morphedByMany(Video::class, 'taggable');
    } 
}

Ve şimdi, bir Etiket aldığımızda, bu etiketle ilgili tüm videoları ve hikayeleri alabiliyoruz!

use App\Model\Tag;

$tag = Tag::find(10);
$posts = $tag->stories;
$videos = $tag->videos;

 

Laravel'in Eloquent ORM'si ile çalışırken, veritabanı sorgularını nasıl optimize edeceğinizi ve veri getirmek için gereken zaman ve bellek miktarını nasıl en aza indireceğinizi anlamak çok önemlidir. Bunu yapmanın bir yolu uygulamanıza önbellekleme uygulamaktır.

Laravel, Redis, Memcached ve dosya tabanlı önbellekleme gibi çeşitli arka uçları destekleyen esnek bir önbellekleme sistemi sağlar. Eloquent sorgu sonuçlarını önbelleğe alarak, veritabanı sorgularının sayısını azaltabilir, uygulamanızı daha hızlı ve daha değerli hale getirebilirsiniz.

Ayrıca, Laravel'in sorgu oluşturucusunu kullanarak ek karmaşık sorgular oluşturabilir ve uygulamanızın performansını daha da optimize edebilirsiniz.

0 Yorum


Önerilen Yorumlar

Görüntülenecek yorum yok.

Misafir
Yorum ekle...

×   Zengin metin olarak yapıştırıldı.   Bunun yerine düz metin olarak yapıştır

  Yalnızca 75 emojiye izin verilir.

×   Bağlantınız otomatik olarak gömüldü.   Bunun yerine bağlantı olarak görüntüle

×   Önceki içeriğiniz geri yüklendi.   Düzenleyiciyi temizle

×   Görüntüleri doğrudan yapıştıramazsınız. URL'den resim yükleyin veya ekleyin.

×
×
  • Create New...