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.
Üç 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.