在每個開發者的生活中,往往會有一個點,你必須與資料庫進行互動。在這裡,Eloquent,Laravel的物件關聯對映器(ORM),使你與資料庫表的互動過程變得直觀和自然。
作為一個專業人士,你應該認識和理解六種關鍵的關聯型別,這是至關重要的,我們將通過和審查。
- 什麼是Eloquent中的關聯?
- 一對一關聯
- 一對多關聯
- 一對多檢索關聯
- 遠端一對一和遠端一對多關聯
- 多對多關聯
- 多型關聯
- 多型一對一關聯
- 多型一對多關聯
- 多型一對多檢索關聯
- 多型多對多關聯
- 優化Eloquent的速度
什麼是Eloquent中的關聯?
在關聯型資料庫中處理表時,我們可以將關聯描述為表之間的連線。這可以幫助你毫不費力地組織和構建資料,使資料的可讀性和處理更加出色。在實踐中,有三種型別的資料庫關聯:
- 一對一 – 一個表中的一條記錄與另一個表中的一條,而且只有一條相關聯。例如,一個人和一個社會安全號碼。
- 一對多 – 一個記錄與另一個表中的多個記錄相關聯。例如,一個作家和他們的部落格。
- 多對多 – 一個表中的多個記錄與另一個表中的多個記錄相關聯。例如, 學生和他們所註冊的課程。
Laravel在Eloquent中使用物件導向的語法,使得互動和管理資料庫關聯變得天衣無縫。
伴隨著這些定義,Laravel引入了更多的關聯,即:
- 遠端一對
- 多型關聯
- 多型多對多
以一個商店為例,它的庫存包含了各種各樣的文章,每個都有自己的類別。因此,從商業角度來看,將資料庫分割成多個表是有意義的。這也帶來了自身的問題,因為你並不想查詢每一個表。
我們可以很容易地在Laravel中建立一個簡單的一對多的關聯來幫助我們,比如當我們需要查詢產品的時候,我們可以通過使用產品模型來完成。
有三個表和一個代表多型關聯的聯合表的資料庫模式
一對一關聯
作為Laravel提供的第一個基本關聯,他們將兩個表聯絡在一起,這樣第一張表的一條記錄就只和另一張表的一條記錄相關聯。
為了看到這一點, 我們必須建立兩個有自己遷移的模型:
php artisan make:model Tenant Php artisan make:model Rent
在這一點上,我們有兩個模型,一個是租戶,另一個是他們的租金。
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class Tenant extends Model { /** * Get the rent of a Tenant */ public function rent() { return $this->hasOne(Rent::class); } }
因為eloquent根據父模型的名稱(本例中是Tenant)來確定外來鍵關聯,Rent模型假定存在一個tenant_id外來鍵。
我們可以用hasOne方法的一個額外引數輕鬆地覆蓋它:
return $this- >hasOne(Rent::class, "custom_key");
Eloquent還假設定義的外來鍵和父記錄(租戶模型)的主鍵之間存在匹配。預設情況下,它將尋求將tenant_id與租戶記錄的id鍵相匹配。我們可以用hasOne方法中的第三個引數來覆蓋這一點,這樣它就可以匹配另一個鍵:
return $this->hasOne(Rent::class, "custom_key", "other_key");
現在我們已經定義了模型之間的一對一關聯,我們可以很容易地使用它,像這樣:
$rent = Tenant::find(10)->rent;
通過這行程式碼,我們得到了租戶的租金,如果它存在的話,ID為10。
一對多關聯
像前面的關聯一樣,這將定義一個單親模型和多個子模型之間的關聯。我們的租戶不太可能只有一張租金賬單,因為它是一個經常性的付款,因此,他將有多次付款。
在這種情況下,我們之前的關聯有缺陷,我們可以修復它們:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class Tenant extends Model { /** * Get the rents of a Tenant */ public function rent() { return $this->hasMany(Rent::class); } }
在我們呼叫方法獲得租金之前,需要知道的一件好事是,關聯可以作為查詢構建器,所以我們可以進一步新增約束條件(如日期之間的租金,最低付款額等),並將它們連線起來,以獲得我們想要的結果:
$rents = Tenant::find(10)->rent()->where('payment', '>', 500)->first();
和之前的關聯一樣,我們可以通過傳遞額外的引數來覆蓋外來鍵和本地鍵:
return $this->hasMany(Rent::class, "foreign_key");
return $this->hasMany(Rent::class, "foreign_key", "local_key");
現在我們有了一個租戶的所有租金,但當我們知道了租金並想弄清楚它屬於誰時,我們該怎麼做?我們可以利用 belongsTo 屬性:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class Rent extends Model { /** * Return the tenant for the rent */ public function tenant() { return $this->belongsTo(Tenant::class); } }
而現在我們可以很容易地得到租戶:
$tenant = Rent::find(1)->tenant;
對於 belongsTo 方法,我們也可以像之前那樣覆蓋外來鍵和本地鍵。
一對多檢索關聯
由於我們的租戶模型可以與許多租金模型相關聯,我們想輕鬆地檢索關聯中最新或最舊的相關模型。
一個方便的方法是結合hasOne和ofMany方法:
public function latestRent() { return $this->hasOne(Rent::class)->latestOfMany(); } public function oldestRent() { return $this->hasOne(Rent::class)->oldestOfMany(); }
預設情況下,我們是根據主鍵來獲取資料的,這是可排序的,但我們可以為ofMany方法建立我們自己的過濾器:
return $this->hasOne(Rent::class)->ofMany('price', 'min');
遠端一對一和遠端一對多關聯
-Through方法表明我們的模型將不得不通過另一個其他模型來建立與所需模型的關聯。例如,我們可以將租金與房東聯絡起來,但租金必須首先通過租戶才能到達房東那裡。
這方面所需的表的鍵看起來是這樣的:
rent id - integer name - string value - double tenants id - integer name - string rent_id - integer landlord id - integer name - string tenant_id - integer
在想象出我們的表格的樣子後,我們可以製作模型:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class Rent extends Model { /** * Return the rents' landlord */ public function rentLandlord() { return $this->hasOneThrough(Landlord::class, Tenant::class); } }
hasOneThrough方法的第一個引數是你要訪問的模型,第二個引數是你要經過的模型。
就像以前一樣,你可以覆蓋外來鍵和本地鍵。現在我們有兩個模型,我們有兩個各自的模型要按這個順序覆蓋:
public function rentLandlord() { return $this->hasOneThrough( Landlord::class, Tenant::class, "rent_id", // Foreign key on the tenant table "tenant_id", // Foreign key on the landlord table "id", // Local key on the tenant class "id" // Local key on the tenant table ); }
同樣, Laravel Eloquent中的 “Has Many Through” 關聯在你想通過中間表訪問遠方表的記錄時很有用。讓我們考慮一個有三個表的例子:
- country
- users
- games
每個國家有很多使用者, 每個使用者有很多遊戲。我們想通過使用者表來檢索屬於某個國家的所有遊戲。
你可以這樣定義這些表:
country id - integer name - string user id - integer country_id - integer name - string games id - integer user_id - integer title - string
現在你應該為每一個表定義Eloquent模型:
<?php 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); } }
<?php 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); } }
<?php 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); } }
現在我們可以呼叫國家模型的games()方法來獲得所有的遊戲,因為我們通過使用者模型在國家和遊戲之間建立了 “Has Many Through” 關聯。
<?php $country = Country::find(159); // Retrieve all games for the country $games = $country->games;
多對多關聯
多對多的關聯更為複雜。一個很好的例子是一個擁有多個角色的僱員。一個角色也可以分配給多個僱員。這就是多對多關聯的基礎。
為此,我們必須有employees, roles, 和 role_employees表。
我們的資料庫表結構將看起來像這樣:
employees id - integer name - string roles id - integer name - string role_employees user_id - integer role_id - integer
知道了關聯表的結構,我們可以很容易地將我們的Employee model定義為屬於belongToMany Role模型。
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class Employee extends Model { public function roles() { return $this- >belongsToMany(Role::class); } }
一旦我們定義了這個,我們就可以訪問一個僱員的所有角色,甚至可以過濾它們:
$employee = Employee::find(1); $employee->roles->forEach(function($role) { // }); // OR $employee = Employee::find(1)->roles()->orderBy('name')->where('name', 'admin')->get();
像所有其他方法一樣,我們可以覆蓋 belongsToMany 方法的外來鍵和本地鍵。
要定義 belongsToMany 的逆向關聯,我們只需使用同樣的方法,但現在是在子方法上,以父方法作為引數。
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class Role extends Model { public function employees() { return $this->belongsToMany(Employee::class); } }
中間表的用途
我們可能已經注意到,當我們使用多對多的關聯時,我們總是應該有一箇中間表。在這種情況下,我們使用的是role_employees表。
預設情況下,我們的透視表將只包含id屬性。如果我們想要其他屬性,我們必須像這樣指定它們:
return $this->belongsToMany(Employee::class)->withPivot("active", "created_at");
如果我們想縮短時間戳的pivots,我們可以這樣做:
return $this->belongsToMany(Employee::class)->withTimestamps();
要知道的一個訣竅是,我們可以將 “pivot” 的名稱自定義為任何更適合我們應用的東西:
return $this->belongsToMany(Employee::class)->as('subscription')->withPivot("active", "created_by");
過濾一個雄辯的查詢結果是任何想要加強他們的遊戲和優化他們的Laravel應用程式的開發人員必須知道的。
因此,Laravel提供了一個奇妙的功能,即pivots,可以用來過濾我們想要收集的資料。所以,我們可以用一些有用的方法來過濾資料,比如wherePivot, wherePivotIn, wherePivotNotIn, wherePivotBetween, wherePivotNotBetween, wherePivotNull, wherePivotNull,我們可以在定義表之間的關聯時使用它們,而不是使用其他的功能,比如資料庫事務來獲取我們的資料塊!
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');
最後一個令人驚奇的功能是,我們可以按pivots排序:
return $this->belongsToMany(Employee::class) ->where('promoted', true) ->orderByPivot('hired_at', 'desc');
多型關聯
Polymorphic(多型)這個詞來自希臘語,它的意思是 “多種形式”。就像這樣,我們應用程式中的一個模型可以採取多種形式,也就是說它可以有多個關聯。想象一下,我們正在建立一個有部落格、視訊、投票等的應用程式。一個使用者可以為其中任何一個建立評論。因此,一個Comment model可能屬於Blogs, Videos, 和 Polls模型。
多型一對一關聯
這種型別的關聯類似於標準的一對一關聯。唯一的區別是,子模型可以通過一個關聯屬於一個以上的模型型別。
以一個Tenant和Landlord模型為例,它可能與WaterBill模型共享一個多型關聯。
表的結構可以是這樣的:
tenants id – integer name – string landlords id – integer name – string waterbills id – integer amount – double waterbillable_id waterbillable_type
我們使用waterbillable_id來表示landlord或tenant的ID,而waterbillable_type包含了父模型的類名。eloquent使用這個型別列來計算要返回的父模型。
這種關聯的模型定義看起來如下:
<?php 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'); } }
一旦我們把這些都準備好了,我們就可以從房東和租戶模型中獲取資料:
<?php $tenant = Tenant::find(1)->waterBill; $landlord = Landlord::find(1)->waterBill;
多型一對多關聯
這類似於普通的一對多關聯,唯一的關鍵區別是,子模型可以屬於一個以上的模型型別,使用一個關聯。
在像Facebook這樣的應用中,使用者可以對帖子、視訊、投票、直播等進行評論。通過多型的一對多,我們可以使用一個單一的comments表來儲存我們所有類別的評論。我們的表的結構會是這樣的:
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是記錄的id,而commentable_type是類的型別,所以eloquent知道要找什麼。至於模型結構,它與多型的一對多非常相似:
<?php 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'); } }
現在要檢索一個Live的評論,我們可以簡單地用id呼叫find方法,現在我們可以訪問評論可迭代類:
<?php use App\Models\Live; $live = Live::find(1); foreach ($live->comments as $comment) { } // OR Live::find(1)->comments()->each(function($comment) { // }); Live::find(1)->comments()->map(function($comment) { // }); Live::find(1)->comments()->filter(function($comment) { // }); // etc.
如果我們有了評論,並想知道它屬於誰,我們就訪問可評論方法:
<?php use App\Models\Comment; $comment = Comment::find(10); $commentable = $comment->commentable; // commentable – type of Post, Video, Poll, Live
多型一對多檢索關聯
在很多具有規模的應用中,我們希望有一種簡單的方式來與模型和模型之間進行互動。我們可能想要一個使用者的第一個或最後一個帖子,這可以通過morphOne和ofMany方法的組合來完成:
<?php public function latestPost() { return $this->morphOne(Post::class, 'postable')->latestOfMany(); } public function oldestPost() { return $this->morphOne(Post::class, 'postable')->oldestOfMany(); }
latestOfMany和oldestOfMany方法是基於模型的主鍵來檢索最新或最舊的模型,這是它可排序的條件。
在某些情況下,我們不希望按ID排序,也許我們改變了一些帖子的釋出日期,我們希望它們按這個順序排列,而不是按它們的ID。
這可以通過向ofMany方法傳遞2個引數來幫助實現。第一個引數是我們想要過濾的key,第二個引數是sorting method(排序方法):
<?php public function latestPublishedPost() { return $this->morphOne(Post::class, "postable")->ofMany("published_at", "max"); }
考慮到這一點,我們有可能為此構建更高階的關聯! 想象一下,我們有這樣的場景。我們被要求生成一個當前所有帖子的列表,按照它們被髮布的順序。當我們有兩個具有相同published_at值的帖子時,以及當帖子被安排在未來發布時,問題就出現了。
要做到這一點,我們可以將我們希望應用過濾器的順序傳遞給ofMany方法。這樣,我們按published_at排序,如果它們是相同的,我們就按id排序。其次,我們可以在ofMany方法中應用一個查詢函式,以排除所有預定要釋出的帖子!
<?php public function currentPosts() { return $this->hasOne(Post::class)->ofMany([ 'published_at' => 'max', 'id' => 'max', ], function ($query) { $query->where('published_at', '<', now()); }); }
多型多對多關聯
多型的多對多比普通的多對多要稍微複雜一些。一個常見的情況是,在你的應用程式中,標籤適用於更多的資產。例如,在TikTok,我們的標籤可以應用於視訊、短劇、故事等。
多型的多對多允許我們有一個與視訊、短片和故事相關的標籤表。
表的結構很簡單:
videos id – integer description – string stories id – integer description – string taggables tag_id – integer taggable_id – integer taggable_type – string
表準備好後,我們可以製作模型並使用morphToMany方法。這個方法接受模型類的名稱和 “relationship name”:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class Video extends Model { public function tags() { return $this->morphToMany(Tag::class, 'taggable'); } }
有了這個,我們可以很容易地定義逆向關聯。我們知道,對於每個子模型,我們要呼叫morphedByMany方法:
<?php 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'); } }
而現在,當我們得到一個標籤時,我們可以檢索到與該標籤相關的所有視訊和故事!
<?php use App\Model\Tag; $tag = Tag::find(10); $posts = $tag->stories; $videos = $tag->stories;
優化Eloquent的速度
當使用Laravel的Eloquent ORM時,瞭解如何優化資料庫查詢並儘量減少獲取資料所需的時間和記憶體是至關重要的。其中一個方法就是在你的應用程式中實現快取。
Laravel提供了一個靈活的快取系統,支援各種後端,如Redis, Memcached, 和基於檔案的快取。通過快取Eloquent查詢結果, 你可以減少資料庫查詢的次數, 使你的應用程式更快,更有價值。
此外, 你可以使用Laravel的查詢生成器來建立額外的複雜查詢, 進一步優化你的應用程式的效能.
小結
總之,Eloquent關聯是Laravel的一個強大的功能,允許開發人員輕鬆處理相關資料。從一對一到多對多的關聯,Eloquent提供了一個簡單而直觀的語法來定義和查詢這些關聯.
作為一個Laravel開發者,掌握Eloquent關聯可以極大地提高你的開發工作流程,使你的程式碼更有效率和可讀性。如果你有興趣瞭解更多關於Laravel的資訊,有各種資源可用,包括一個關於Laravel入門的教程和一篇關於Laravel開發者工資的文章。
評論留言