Laravel模型關聯高階教程

Laravel模型關聯高階教程

在每個開發者的生活中,往往會有一個點,你必須與資料庫進行互動。在這裡,Eloquent,Laravel的物件關聯對映器(ORM),使你與資料庫表的互動過程變得直觀和自然。

作為一個專業人士,你應該認識和理解六種關鍵的關聯型別,這是至關重要的,我們將通過和審查。

  1. 什麼是Eloquent中的關聯?
  2. 一對一關聯
  3. 一對多關聯
  4. 一對多檢索關聯
  5. 遠端一對一和遠端一對多關聯
  6. 多對多關聯
  7. 多型關聯
  8. 多型一對一關聯
  9. 多型一對多關聯
  10. 多型一對多檢索關聯
  11. 多型多對多關聯
  12. 優化Eloquent的速度

什麼是Eloquent中的關聯?

在關聯型資料庫中處理表時,我們可以將關聯描述為表之間的連線。這可以幫助你毫不費力地組織和構建資料,使資料的可讀性和處理更加出色。在實踐中,有三種型別的資料庫關聯:

  • 一對一 – 一個表中的一條記錄與另一個表中的一條,而且只有一條相關聯。例如,一個人和一個社會安全號碼。
  • 一對多 – 一個記錄與另一個表中的多個記錄相關聯。例如,一個作家和他們的部落格。
  • 多對多 – 一個表中的多個記錄與另一個表中的多個記錄相關聯。例如, 學生和他們所註冊的課程。

Laravel在Eloquent中使用物件導向的語法,使得互動和管理資料庫關聯變得天衣無縫。

伴隨著這些定義,Laravel引入了更多的關聯,即:

  • 遠端一對
  • 多型關聯
  • 多型多對多

以一個商店為例,它的庫存包含了各種各樣的文章,每個都有自己的類別。因此,從商業角度來看,將資料庫分割成多個表是有意義的。這也帶來了自身的問題,因為你並不想查詢每一個表。

我們可以很容易地在Laravel中建立一個簡單的一對多的關聯來幫助我們,比如當我們需要查詢產品的時候,我們可以通過使用產品模型來完成。

有三個表和一個代表多型關聯的聯合表的資料庫模式

有三個表和一個代表多型關聯的聯合表的資料庫模式

一對一關聯

作為Laravel提供的第一個基本關聯,他們將兩個表聯絡在一起,這樣第一張表的一條記錄就只和另一張表的一條記錄相關聯。

為了看到這一點, 我們必須建立兩個有自己遷移的模型:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
php artisan make:model Tenant
Php artisan make:model Rent
php artisan make:model Tenant Php artisan make:model Rent
php artisan make:model Tenant 
Php artisan make:model Rent

在這一點上,我們有兩個模型,一個是租戶,另一個是他們的租金。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<?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);
}
}
<?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); } }
<?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方法的一個額外引數輕鬆地覆蓋它:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
return $this- >hasOne(Rent::class, "custom_key");
return $this- >hasOne(Rent::class, "custom_key");
return $this- >hasOne(Rent::class, "custom_key");

Eloquent還假設定義的外來鍵和父記錄(租戶模型)的主鍵之間存在匹配。預設情況下,它將尋求將tenant_id與租戶記錄的id鍵相匹配。我們可以用hasOne方法中的第三個引數來覆蓋這一點,這樣它就可以匹配另一個鍵:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
return $this->hasOne(Rent::class, "custom_key", "other_key");
return $this->hasOne(Rent::class, "custom_key", "other_key");
return $this->hasOne(Rent::class, "custom_key", "other_key");

現在我們已經定義了模型之間的一對一關聯,我們可以很容易地使用它,像這樣:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
$rent = Tenant::find(10)->rent;
$rent = Tenant::find(10)->rent;
$rent = Tenant::find(10)->rent;

通過這行程式碼,我們得到了租戶的租金,如果它存在的話,ID為10。

一對多關聯

像前面的關聯一樣,這將定義一個單親模型和多個子模型之間的關聯。我們的租戶不太可能只有一張租金賬單,因為它是一個經常性的付款,因此,他將有多次付款。

在這種情況下,我們之前的關聯有缺陷,我們可以修復它們:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<?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);
}
}
<?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); } }
<?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);
}
}

在我們呼叫方法獲得租金之前,需要知道的一件好事是,關聯可以作為查詢構建器,所以我們可以進一步新增約束條件(如日期之間的租金,最低付款額等),並將它們連線起來,以獲得我們想要的結果:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
$rents = Tenant::find(10)->rent()->where('payment', '>', 500)->first();
$rents = Tenant::find(10)->rent()->where('payment', '>', 500)->first();
$rents = Tenant::find(10)->rent()->where('payment', '>', 500)->first();

和之前的關聯一樣,我們可以通過傳遞額外的引數來覆蓋外來鍵和本地鍵:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
return $this->hasMany(Rent::class, "foreign_key");
return $this->hasMany(Rent::class, "foreign_key");
return $this->hasMany(Rent::class, "foreign_key");
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
return $this->hasMany(Rent::class, "foreign_key", "local_key");
return $this->hasMany(Rent::class, "foreign_key", "local_key");
return $this->hasMany(Rent::class, "foreign_key", "local_key");

現在我們有了一個租戶的所有租金,但當我們知道了租金並想弄清楚它屬於誰時,我們該怎麼做?我們可以利用 belongsTo 屬性:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<?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);
}
}
<?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); } }
<?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);
}
}

而現在我們可以很容易地得到租戶:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
$tenant = Rent::find(1)->tenant;
$tenant = Rent::find(1)->tenant;
$tenant = Rent::find(1)->tenant;

對於 belongsTo 方法,我們也可以像之前那樣覆蓋外來鍵和本地鍵。

一對多檢索關聯

由於我們的租戶模型可以與許多租金模型相關聯,我們想輕鬆地檢索關聯中最新或最舊的相關模型。

一個方便的方法是結合hasOneofMany方法:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
public function latestRent() {
return $this->hasOne(Rent::class)->latestOfMany();
}
public function oldestRent() {
return $this->hasOne(Rent::class)->oldestOfMany();
}
public function latestRent() { return $this->hasOne(Rent::class)->latestOfMany(); } public function oldestRent() { return $this->hasOne(Rent::class)->oldestOfMany(); }
public function latestRent() {
return $this->hasOne(Rent::class)->latestOfMany();
}
public function oldestRent() {
return $this->hasOne(Rent::class)->oldestOfMany();
}

預設情況下,我們是根據主鍵來獲取資料的,這是可排序的,但我們可以為ofMany方法建立我們自己的過濾器:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
return $this->hasOne(Rent::class)->ofMany('price', 'min');
return $this->hasOne(Rent::class)->ofMany('price', 'min');
return $this->hasOne(Rent::class)->ofMany('price', 'min');

遠端一對一和遠端一對多關聯

-Through方法表明我們的模型將不得不通過另一個其他模型來建立與所需模型的關聯。例如,我們可以將租金與房東聯絡起來,但租金必須首先通過租戶才能到達房東那裡。

這方面所需的表的鍵看起來是這樣的:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
rent
id - integer
name - string
value - double
tenants
id - integer
name - string
rent_id - integer
landlord
id - integer
name - string
tenant_id - integer
rent id - integer name - string value - double tenants id - integer name - string rent_id - integer landlord id - integer name - string tenant_id - integer
rent
id - integer
name - string
value - double
tenants
id - integer
name - string
rent_id - integer
landlord
id - integer
name - string
tenant_id - integer

在想象出我們的表格的樣子後,我們可以製作模型:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<?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);
}
}
<?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); } }
<?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方法的第一個引數是你要訪問的模型,第二個引數是你要經過的模型。

就像以前一樣,你可以覆蓋外來鍵和本地鍵。現在我們有兩個模型,我們有兩個各自的模型要按這個順序覆蓋:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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
);
}
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 ); }
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

每個國家有很多使用者, 每個使用者有很多遊戲。我們想通過使用者表來檢索屬於某個國家的所有遊戲。

你可以這樣定義這些表:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
country
id - integer
name - string
user
id - integer
country_id - integer
name - string
games
id - integer
user_id - integer
title - string
country id - integer name - string user id - integer country_id - integer name - string games id - integer user_id - integer title - string
country
id - integer
name - string
user
id - integer
country_id - integer
name - string
games
id - integer
user_id - integer
title - string

現在你應該為每一個表定義Eloquent模型:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<?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 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 Country extends Model
{
protected $fillable = ['name'];
public function users()
{
return $this->hasMany(User::class);
}
public function games()
{
return $this->hasManyThrough(Games::class, User::class);
}
}
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<?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 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 User extends Model
{
protected $fillable = [article_id, 'name'];
public function country()
{
return $this->belongsTo(Country::class);
}
public function posts()
{
return $this->hasMany(Post::class);
}
}
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<?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);
}
}
<?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); } }
<?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” 關聯。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<?php
$country = Country::find(159);
// Retrieve all games for the country
$games = $country->games;
<?php $country = Country::find(159); // Retrieve all games for the country $games = $country->games;
<?php
$country = Country::find(159);
// Retrieve all games for the country
$games = $country->games;

多對多關聯

多對多的關聯更為複雜。一個很好的例子是一個擁有多個角色的僱員。一個角色也可以分配給多個僱員。這就是多對多關聯的基礎。

為此,我們必須有employeesroles, 和 role_employees表。

我們的資料庫表結構將看起來像這樣:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
employees
id - integer
name - string
roles
id - integer
name - string
role_employees
user_id - integer
role_id - integer
employees id - integer name - string roles id - integer name - string role_employees user_id - integer role_id - integer
employees
id - integer
name - string
roles 
id - integer
name - string
role_employees
user_id - integer
role_id - integer

知道了關聯表的結構,我們可以很容易地將我們的Employee model定義為屬於belongToMany Role模型。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Employee extends Model
{
public function roles()
{
return $this- >belongsToMany(Role::class);
}
}
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class Employee extends Model { public function roles() { return $this- >belongsToMany(Role::class); } }
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Employee extends Model
{
public function roles() 
{
return $this- >belongsToMany(Role::class);
}
}

一旦我們定義了這個,我們就可以訪問一個僱員的所有角色,甚至可以過濾它們:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
$employee = Employee::find(1);
$employee->roles->forEach(function($role) { // });
// OR
$employee = Employee::find(1)->roles()->orderBy('name')->where('name', 'admin')->get();
$employee = Employee::find(1); $employee->roles->forEach(function($role) { // }); // OR $employee = Employee::find(1)->roles()->orderBy('name')->where('name', 'admin')->get();
$employee = Employee::find(1);
$employee->roles->forEach(function($role) { // });
// OR 
$employee = Employee::find(1)->roles()->orderBy('name')->where('name', 'admin')->get();

像所有其他方法一樣,我們可以覆蓋 belongsToMany 方法的外來鍵和本地鍵。

要定義 belongsToMany 的逆向關聯,我們只需使用同樣的方法,但現在是在子方法上,以父方法作為引數。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Role extends Model
{
public function employees()
{
return $this->belongsToMany(Employee::class);
}
}
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class Role extends Model { public function employees() { return $this->belongsToMany(Employee::class); } }
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Role extends Model
{
public function employees() 
{
return $this->belongsToMany(Employee::class);
}
}

中間表的用途

我們可能已經注意到,當我們使用多對多的關聯時,我們總是應該有一箇中間表。在這種情況下,我們使用的是role_employees表。

預設情況下,我們的透視表將只包含id屬性。如果我們想要其他屬性,我們必須像這樣指定它們:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
return $this->belongsToMany(Employee::class)->withPivot("active", "created_at");
return $this->belongsToMany(Employee::class)->withPivot("active", "created_at");
return $this->belongsToMany(Employee::class)->withPivot("active", "created_at");

如果我們想縮短時間戳的pivots,我們可以這樣做:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
return $this->belongsToMany(Employee::class)->withTimestamps();
return $this->belongsToMany(Employee::class)->withTimestamps();
return $this->belongsToMany(Employee::class)->withTimestamps();

要知道的一個訣竅是,我們可以將 “pivot” 的名稱自定義為任何更適合我們應用的東西:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
return $this->belongsToMany(Employee::class)->as('subscription')->withPivot("active", "created_by");
return $this->belongsToMany(Employee::class)->as('subscription')->withPivot("active", "created_by");
return $this->belongsToMany(Employee::class)->as('subscription')->withPivot("active", "created_by");

過濾一個雄辯的查詢結果是任何想要加強他們的遊戲和優化他們的Laravel應用程式的開發人員必須知道的。

因此,Laravel提供了一個奇妙的功能,即pivots,可以用來過濾我們想要收集的資料。所以,我們可以用一些有用的方法來過濾資料,比如wherePivot, wherePivotIn, wherePivotNotIn, wherePivotBetween, wherePivotNotBetween, wherePivotNull, wherePivotNull,我們可以在定義表之間的關聯時使用它們,而不是使用其他的功能,比如資料庫事務來獲取我們的資料塊!

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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');
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');
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排序:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
return $this->belongsToMany(Employee::class)
->where('promoted', true)
->orderByPivot('hired_at', 'desc');
return $this->belongsToMany(Employee::class) ->where('promoted', true) ->orderByPivot('hired_at', 'desc');
return $this->belongsToMany(Employee::class)
->where('promoted', true)
->orderByPivot('hired_at', 'desc');

多型關聯

Polymorphic(多型)這個詞來自希臘語,它的意思是 “多種形式”。就像這樣,我們應用程式中的一個模型可以採取多種形式,也就是說它可以有多個關聯。想象一下,我們正在建立一個有部落格、視訊、投票等的應用程式。一個使用者可以為其中任何一個建立評論。因此,一個Comment model可能屬於BlogsVideos, 和 Polls模型。

多型一對一關聯

這種型別的關聯類似於標準的一對一關聯。唯一的區別是,子模型可以通過一個關聯屬於一個以上的模型型別。

以一個TenantLandlord模型為例,它可能與WaterBill模型共享一個多型關聯。

表的結構可以是這樣的:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
tenants
id – integer
name – string
landlords
id – integer
name – string
waterbills
id – integer
amount – double
waterbillable_id
waterbillable_type
tenants id – integer name – string landlords id – integer name – string waterbills id – integer amount – double waterbillable_id waterbillable_type
tenants
id – integer
name – string
landlords
id – integer
name – string
waterbills
id – integer
amount – double
waterbillable_id
waterbillable_type

我們使用waterbillable_id來表示landlordtenant的ID,而waterbillable_type包含了父模型的類名。eloquent使用這個型別列來計算要返回的父模型。

這種關聯的模型定義看起來如下:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<?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 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
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');
}
}

一旦我們把這些都準備好了,我們就可以從房東和租戶模型中獲取資料:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<?php
$tenant = Tenant::find(1)->waterBill;
$landlord = Landlord::find(1)->waterBill;
<?php $tenant = Tenant::find(1)->waterBill; $landlord = Landlord::find(1)->waterBill;
<?php
$tenant = Tenant::find(1)->waterBill;
$landlord = Landlord::find(1)->waterBill;

多型一對多關聯

這類似於普通的一對多關聯,唯一的關鍵區別是,子模型可以屬於一個以上的模型型別,使用一個關聯。

在像Facebook這樣的應用中,使用者可以對帖子、視訊、投票、直播等進行評論。通過多型的一對多,我們可以使用一個單一的comments表來儲存我們所有類別的評論。我們的表的結構會是這樣的:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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
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
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知道要找什麼。至於模型結構,它與多型的一對多非常相似:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<?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');
}
}
<?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'); } }
<?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方法,現在我們可以訪問評論可迭代類:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<?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\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\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.

如果我們有了評論,並想知道它屬於誰,我們就訪問可評論方法:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<?php
use App\Models\Comment;
$comment = Comment::find(10);
$commentable = $comment->commentable;
// commentable – type of Post, Video, Poll, Live
<?php use App\Models\Comment; $comment = Comment::find(10); $commentable = $comment->commentable; // commentable – type of Post, Video, Poll, Live
<?php
use App\Models\Comment;
$comment = Comment::find(10);
$commentable = $comment->commentable;
// commentable – type of Post, Video, Poll, Live

多型一對多檢索關聯

在很多具有規模的應用中,我們希望有一種簡單的方式來與模型和模型之間進行互動。我們可能想要一個使用者的第一個或最後一個帖子,這可以通過morphOneofMany方法的組合來完成:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<?php
public function latestPost()
{
return $this->morphOne(Post::class, 'postable')->latestOfMany();
}
public function oldestPost()
{
return $this->morphOne(Post::class, 'postable')->oldestOfMany();
}
<?php public function latestPost() { return $this->morphOne(Post::class, 'postable')->latestOfMany(); } public function oldestPost() { return $this->morphOne(Post::class, 'postable')->oldestOfMany(); }
<?php
public function latestPost()
{
return $this->morphOne(Post::class, 'postable')->latestOfMany();
}
public function oldestPost()
{
return $this->morphOne(Post::class, 'postable')->oldestOfMany();
}

latestOfManyoldestOfMany方法是基於模型的主鍵來檢索最新或最舊的模型,這是它可排序的條件。

在某些情況下,我們不希望按ID排序,也許我們改變了一些帖子的釋出日期,我們希望它們按這個順序排列,而不是按它們的ID。

這可以通過向ofMany方法傳遞2個引數來幫助實現。第一個引數是我們想要過濾的key,第二個引數是sorting method(排序方法)

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<?php
public function latestPublishedPost()
{
return $this->morphOne(Post::class, "postable")->ofMany("published_at", "max");
}
<?php public function latestPublishedPost() { return $this->morphOne(Post::class, "postable")->ofMany("published_at", "max"); }
<?php
public function latestPublishedPost()
{
return $this->morphOne(Post::class, "postable")->ofMany("published_at", "max");
}

考慮到這一點,我們有可能為此構建更高階的關聯! 想象一下,我們有這樣的場景。我們被要求生成一個當前所有帖子的列表,按照它們被髮布的順序。當我們有兩個具有相同published_at值的帖子時,以及當帖子被安排在未來發布時,問題就出現了。

要做到這一點,我們可以將我們希望應用過濾器的順序傳遞給ofMany方法。這樣,我們按published_at排序,如果它們是相同的,我們就按id排序。其次,我們可以在ofMany方法中應用一個查詢函式,以排除所有預定要釋出的帖子!

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<?php
public function currentPosts()
{
return $this->hasOne(Post::class)->ofMany([
'published_at' => 'max',
'id' => 'max',
], function ($query) {
$query->where('published_at', '<', now());
});
}
<?php public function currentPosts() { return $this->hasOne(Post::class)->ofMany([ 'published_at' => 'max', 'id' => 'max', ], function ($query) { $query->where('published_at', '<', now()); }); }
<?php
public function currentPosts()
{
return $this->hasOne(Post::class)->ofMany([
'published_at' => 'max',
'id' => 'max',
], function ($query) {
$query->where('published_at', '<', now());
});
}

多型多對多關聯

多型的多對多比普通的多對多要稍微複雜一些。一個常見的情況是,在你的應用程式中,標籤適用於更多的資產。例如,在TikTok,我們的標籤可以應用於視訊、短劇、故事等。

多型的多對多允許我們有一個與視訊、短片和故事相關的標籤表。

表的結構很簡單:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
videos
id – integer
description – string
stories
id – integer
description – string
taggables
tag_id – integer
taggable_id – integer
taggable_type – string
videos id – integer description – string stories id – integer description – string taggables tag_id – integer taggable_id – integer taggable_type – string
videos
id – integer
description – string
stories 
id – integer
description – string
taggables 
tag_id – integer
taggable_id – integer
taggable_type – string

表準備好後,我們可以製作模型並使用morphToMany方法。這個方法接受模型類的名稱和 “relationship name”:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Video extends Model
{
public function tags()
{
return $this->morphToMany(Tag::class, 'taggable');
}
}
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class Video extends Model { public function tags() { return $this->morphToMany(Tag::class, 'taggable'); } }
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Video extends Model
{
public function tags()
{
return $this->morphToMany(Tag::class, 'taggable');
}
}

有了這個,我們可以很容易地定義逆向關聯。我們知道,對於每個子模型,我們要呼叫morphedByMany方法:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<?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 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
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');
} 
}

而現在,當我們得到一個標籤時,我們可以檢索到與該標籤相關的所有視訊和故事!

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<?php
use App\Model\Tag;
$tag = Tag::find(10);
$posts = $tag->stories;
$videos = $tag->stories;
<?php use App\Model\Tag; $tag = Tag::find(10); $posts = $tag->stories; $videos = $tag->stories;
<?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開發者工資的文章

評論留言