Eloquent:入門
簡介
Laravel 內建了 Eloquent,這是一個物件關聯映射 (ORM),讓您能愉快地與資料庫互動。使用 Eloquent 時,每個資料庫資料表都有一個對應的「Model」,用於與該資料表互動。除了從資料庫資料表中擷取記錄外,Eloquent 模型也允許您插入、更新和刪除資料表中的記錄。
📌 備註
在開始之前,請務必在應用程式的 config/database.php
設定檔中配置資料庫連線。有關配置資料庫的更多資訊,請參閱資料庫配置文件。
生成模型類別
首先,讓我們建立一個 Eloquent 模型。模型通常位於 app\Models
目錄中,並繼承自 Illuminate\Database\Eloquent\Model
類別。您可以使用 make:model
Artisan 指令來生成一個新的模型:
php artisan make:model Flight
如果您想在生成模型的同時生成資料庫遷移,您可以使用 --migration
或 -m
選項:
php artisan make:model Flight --migration
在生成模型時,您還可以生成各種其他類別,例如 factories、seeders、policies、controllers 和 form requests。此外,這些選項可以組合使用,一次建立多個類別:
# Generate a model and a FlightFactory class...
php artisan make:model Flight --factory
php artisan make:model Flight -f
# Generate a model and a FlightSeeder class...
php artisan make:model Flight --seed
php artisan make:model Flight -s
# Generate a model and a FlightController class...
php artisan make:model Flight --controller
php artisan make:model Flight -c
# Generate a model, FlightController resource class, and form request classes...
php artisan make:model Flight --controller --resource --requests
php artisan make:model Flight -crR
# Generate a model and a FlightPolicy class...
php artisan make:model Flight --policy
# Generate a model and a migration, factory, seeder, and controller...
php artisan make:model Flight -mfsc
# Shortcut to generate a model, migration, factory, seeder, policy, controller, and form requests...
php artisan make:model Flight --all
php artisan make:model Flight -a
# Generate a pivot model...
php artisan make:model Member --pivot
php artisan make:model Member -p
檢查模型
有時,僅透過快速瀏覽模型的程式碼,很難確定其所有可用的屬性和關聯。您可以嘗試使用 model:show
Artisan 指令,它會提供模型所有屬性與關聯的方便概覽:
php artisan model:show Flight
Eloquent 模型慣例
由 make:model
命令生成的模型會被放置在 app/Models
目錄中。讓我們來檢視一個基本的模型類別,並討論一些 Eloquent 的關鍵慣例:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
// ...
}
資料表名稱
看過上方的範例後,您可能已經注意到我們並未告知 Eloquent 哪個資料庫資料表與我們的 Flight
模型相對應。按照慣例,類別的「snake case」複數形式名稱將被用作資料表名稱,除非明確指定其他名稱。因此,在本例中,Eloquent 將會假設 Flight
模型將記錄儲存在 flights
資料表中,而 AirTrafficController
模型將記錄儲存在 air_traffic_controllers
資料表中。
如果您的模型對應的資料庫資料表不符合此慣例,您可以透過在模型上定義 table
屬性來手動指定模型的資料表名稱:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'my_flights';
}
主鍵
Eloquent 也會假設每個模型對應的資料庫資料表都有一個名為 id
的主鍵欄位。如有必要,您可以在模型上定義一個保護的 $primaryKey
屬性來指定作為模型主鍵的不同欄位:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
/**
* The primary key associated with the table.
*
* @var string
*/
protected $primaryKey = 'flight_id';
}
此外,Eloquent 假設主鍵是一個遞增的整數值,這表示 Eloquent 會自動將主鍵轉換為整數。如果您希望使用非遞增或非數字的主鍵,則必須在模型上定義一個公開的 $incrementing
屬性並將其設定為 false
:
<?php
class Flight extends Model
{
/**
* Indicates if the model's ID is auto-incrementing.
*
* @var bool
*/
public $incrementing = false;
}
如果您的模型主鍵不是整數,您應該在模型上定義一個保護的 $keyType
屬性。此屬性值應為 string
:
<?php
class Flight extends Model
{
/**
* The data type of the primary key ID.
*
* @var string
*/
protected $keyType = 'string';
}
「複合」主鍵
Eloquent 要求每個模型至少有一個唯一識別的「ID」作為其主鍵。Eloquent 模型不支援「複合」主鍵。但是,除了資料表的唯一識別主鍵外,您可以自由地為資料庫資料表添加額外的多欄位唯一索引。
UUID 與 ULID 鍵
您可以選擇使用 UUIDs 而不是自動遞增整數作為您的 Eloquent 模型主鍵。UUIDs 是通用唯一的英數字元識別碼,長度為 36 個字元。
如果您希望模型使用 UUID 鍵而不是自動遞增整數鍵,可以在模型上使用 Illuminate\Database\Eloquent\Concerns\HasUuids
Trait。當然,您應該確保模型具有一個等效的 UUID 主鍵欄位:
use Illuminate\Database\Eloquent\Concerns\HasUuids;
use Illuminate\Database\Eloquent\Model;
class Article extends Model
{
use HasUuids;
// ...
}
$article = Article::create(['title' => 'Traveling to Europe']);
$article->id; // "8f8e8478-9035-4d23-b9a7-62f4d2612ce5"
預設情況下,HasUuids
Trait 將為您的模型生成「有序」UUID。這些 UUIDs 對於索引化的資料庫儲存更有效率,因為它們可以按字典順序排序。
您可以透過在模型上定義 newUniqueId
方法來覆寫特定模型的 UUID 生成過程。此外,您還可以透過在模型上定義 uniqueIds
方法來指定哪些欄位應該接收 UUIDs:
use Ramsey\Uuid\Uuid;
/**
* Generate a new UUID for the model.
*/
public function newUniqueId(): string
{
return (string) Uuid::uuid4();
}
/**
* Get the columns that should receive a unique identifier.
*
* @return array<int, string>
*/
public function uniqueIds(): array
{
return ['id', 'discount_code'];
}
如果您願意,您可以選擇使用「ULIDs」而不是 UUIDs。ULIDs 與 UUIDs 相似;但是,它們只有 26 個字元的長度。與有序 UUIDs 相似,ULIDs 也是按字典順序可排序的,以實現高效的資料庫索引。要使用 ULIDs,您應該在模型上使用 Illuminate\Database\Eloquent\Concerns\HasUlids
Trait。您還應確保模型具有等效的 ULID 主鍵欄位:
use Illuminate\Database\Eloquent\Concerns\HasUlids;
use Illuminate\Database\Eloquent\Model;
class Article extends Model
{
use HasUlids;
// ...
}
$article = Article::create(['title' => 'Traveling to Asia']);
$article->id; // "01gd4d3tgrrfqeda94gdbtdk5c"
時間戳記
預設情況下,Eloquent 期望模型的對應資料庫資料表上存在 created_at
和 updated_at
欄位。當模型建立或更新時,Eloquent 會自動設定這些欄位的值。如果您不希望這些欄位由 Eloquent 自動管理,您應該在模型上定義一個 $timestamps
屬性並將其值設為 false
:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
/**
* Indicates if the model should be timestamped.
*
* @var bool
*/
public $timestamps = false;
}
如果您需要自訂模型時間戳記的格式,請在模型上設定 $dateFormat
屬性。此屬性決定日期屬性在資料庫中的儲存方式,以及模型序列化為陣列或 JSON 時的格式:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
/**
* The storage format of the model's date columns.
*
* @var string
*/
protected $dateFormat = 'U';
}
如果您需要自訂用於儲存時間戳記的欄位名稱,您可以在模型上定義 CREATED_AT
和 UPDATED_AT
常數:
<?php
class Flight extends Model
{
const CREATED_AT = 'creation_date';
const UPDATED_AT = 'updated_date';
}
如果您希望執行模型操作而不修改模型的 updated_at
時間戳記,您可以將模型操作封裝在傳遞給 withoutTimestamps
方法的閉包中:
Model::withoutTimestamps(fn () => $post->increment('reads'));
資料庫連線
預設情況下,所有 Eloquent 模型都將使用為您的應用程式配置的預設資料庫連線。如果您希望在與特定模型互動時指定不同的連線,則應在模型上定義 $connection
屬性:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
/**
* The database connection that should be used by the model.
*
* @var string
*/
protected $connection = 'mysql';
}
預設屬性值
預設情況下,新實例化的模型實例將不會包含任何屬性值。如果您想為模型的一些屬性定義預設值,可以在模型上定義 $attributes
屬性。放置在 $attributes
陣列中的屬性值應以其原始的、「可儲存」的格式呈現,就像它們是從資料庫中讀取出來的一樣:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
/**
* The model's default values for attributes.
*
* @var array
*/
protected $attributes = [
'options' => '[]',
'delayed' => false,
];
}
配置 Eloquent 的嚴謹度
Laravel 提供了幾種方法,讓您可以在多種情況下配置 Eloquent 的行為和「嚴謹度」。
首先,preventLazyLoading
方法接受一個可選的布林值引數,用於指示是否應阻止惰性載入。例如,您可能希望只在非生產環境中禁用惰性載入,這樣即使生產程式碼中不小心出現惰性載入的關聯,您的生產環境仍能正常運作。通常,此方法應在應用程式的 AppServiceProvider
的 boot
方法中調用:
use Illuminate\Database\Eloquent\Model;
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Model::preventLazyLoading(! $this->app->isProduction());
}
此外,您可以透過調用 preventSilentlyDiscardingAttributes
方法,指示 Laravel 在嘗試填充一個不可填充屬性時拋出例外。這有助於在本地開發期間,當嘗試設定尚未添加到模型 fillable
陣列中的屬性時,避免意外錯誤。
Model::preventSilentlyDiscardingAttributes(! $this->app->isProduction());
擷取模型
建立模型和其關聯的資料庫資料表後,您就可以開始從資料庫擷取資料了。您可以將每個 Eloquent 模型視為功能強大的查詢產生器,讓您能流暢地查詢與模型關聯的資料庫資料表。模型的 all
方法會從模型關聯的資料庫資料表中擷取所有記錄:
use App\Models\Flight;
foreach (Flight::all() as $flight) {
echo $flight->name;
}
建立查詢
Eloquent 的 all
方法會傳回模型資料表中的所有結果。然而,由於每個 Eloquent 模型都作為查詢產生器,您可以為查詢添加額外約束,然後調用 get
方法來擷取結果:
$flights = Flight::where('active', 1)
->orderBy('name')
->limit(10)
->get();
📌 備註
由於 Eloquent 模型是查詢產生器,您應該審閱 Laravel 查詢產生器提供的所有方法。在撰寫 Eloquent 查詢時,您可以使用其中任何方法。
重新整理模型
如果您已經有一個從資料庫擷取的 Eloquent 模型實例,您可以使用 fresh
和 refresh
方法來「重新整理」模型。fresh
方法會重新從資料庫擷取模型。現有的模型實例不會受到影響:
$flight = Flight::where('number', 'FR 900')->first();
$freshFlight = $flight->fresh();
refresh
方法會使用資料庫中的新資料重新填充現有的模型。此外,其所有已載入的關聯也會重新整理:
$flight = Flight::where('number', 'FR 900')->first();
$flight->number = 'FR 456';
$flight->refresh();
$flight->number; // "FR 900"
集合
如我們所見,Eloquent 方法如 all
和 get
會從資料庫擷取多筆記錄。然而,這些方法不會傳回純 PHP 陣列。取而代之的是,會傳回 Illuminate\Database\Eloquent\Collection
的實例。
Eloquent Collection
類別擴展了 Laravel 的基礎 Illuminate\Support\Collection
類別,該類別提供了各種實用方法來處理資料集合。例如,reject
方法可用於根據調用閉包的結果從集合中移除模型:
$flights = Flight::where('destination', 'Paris')->get();
$flights = $flights->reject(function (Flight $flight) {
return $flight->cancelled;
});
除了 Laravel 基礎集合類別提供的方法之外,Eloquent 集合類別還提供了一些額外方法,這些方法專門用於處理 Eloquent 模型集合。
由於 Laravel 的所有集合都實作了 PHP 的 iterable 介面,您可以像遍歷陣列一樣遍歷集合:
foreach ($flights as $flight) {
echo $flight->name;
}
分批處理結果
如果嘗試透過 all
或 get
方法載入數萬筆 Eloquent 記錄,您的應用程式可能會耗盡記憶體。除了使用這些方法,還可以使用 chunk
方法更有效率地處理大量模型。
chunk
方法會擷取 Eloquent 模型的一個子集,將其傳遞給閉包進行處理。由於一次只擷取 Eloquent 模型的當前批次,因此在處理大量模型時,chunk
方法將顯著降低記憶體使用量:
use App\Models\Flight;
use Illuminate\Database\Eloquent\Collection;
Flight::chunk(200, function (Collection $flights) {
foreach ($flights as $flight) {
// ...
}
});
傳遞給 chunk
方法的第一個引數是您希望每個「批次」接收的記錄數。作為第二個引數傳遞的閉包將在每次從資料庫擷取一個批次時被調用。資料庫查詢將會被執行,以擷取傳遞給閉包的每個批次記錄。
如果您根據某個欄位過濾 chunk
方法的結果,並且在遍歷結果時也會更新該欄位,則應該使用 chunkById
方法。在這些情況下使用 chunk
方法可能會導致意外和不一致的結果。在內部,chunkById
方法總是會擷取 id
欄位大於前一個批次中最後一個模型的模型:
Flight::where('departed', true)
->chunkById(200, function (Collection $flights) {
$flights->each->update(['departed' => false]);
}, column: 'id');
由於 chunkById
和 lazyById
方法會將其自己的「where」條件添加到正在執行的查詢中,因此您通常應該在閉包內邏輯分組您自己的條件:
Flight::where(function ($query) {
$query->where('delayed', true)->orWhere('cancelled', true);
})->chunkById(200, function (Collection $flights) {
$flights->each->update([
'departed' => false,
'cancelled' => true
]);
}, column: 'id');
使用惰性集合分批處理
lazy
方法的運作方式類似於chunk
方法,在幕後它會分批執行查詢。然而,lazy
方法並不會將每個批次直接傳遞給回呼函數,而是傳回一個扁平化的 Eloquent 模型 LazyCollection,讓您能夠以單一串流的形式與結果互動:
use App\Models\Flight;
foreach (Flight::lazy() as $flight) {
// ...
}
如果您根據某個欄位過濾 lazy
方法的結果,並且在遍歷結果時也會更新該欄位,則應該使用 lazyById
方法。在內部,lazyById
方法總是會擷取 id
欄位大於前一個批次中最後一個模型的模型:
Flight::where('departed', true)
->lazyById(200, column: 'id')
->each->update(['departed' => false]);
您可以使用 lazyByIdDesc
方法根據 id
的降序來過濾結果。
游標
與 lazy
方法類似,cursor
方法可用於在迭代數萬筆 Eloquent 模型記錄時,顯著降低應用程式的記憶體消耗。
cursor
方法只會執行單一資料庫查詢;然而,個別的 Eloquent 模型在實際被迭代之前並不會被實例化。因此,在迭代游標時,任何時刻都只會有一筆 Eloquent 模型保留在記憶體中。
⚠️ 警告
由於 cursor
方法在任何時刻都只會保留單一 Eloquent 模型在記憶體中,因此它無法預載入關聯 (relationships)。如果您需要預載入關聯,請考慮改用 the lazy
method。
在內部,cursor
方法使用 PHP generators 來實現此功能:
use App\Models\Flight;
foreach (Flight::where('destination', 'Zurich')->cursor() as $flight) {
// ...
}
cursor
會回傳一個 Illuminate\Support\LazyCollection
實例。Lazy collections 允許您使用一般 Laravel 集合上的許多集合方法,同時每次只載入一個模型到記憶體中:
use App\Models\User;
$users = User::cursor()->filter(function (User $user) {
return $user->id > 500;
});
foreach ($users as $user) {
echo $user->id;
}
儘管 cursor
方法比一般查詢使用的記憶體少得多(因為它每次只在記憶體中保留一個 Eloquent 模型),但它最終仍會耗盡記憶體。這是由於 PHP 的 PDO 驅動程式內部會在其緩衝區中快取所有原始查詢結果。如果您正在處理大量 Eloquent 記錄,請考慮改用 the lazy
method。
進階子查詢
子查詢選取
Eloquent 也提供進階子查詢支援,允許您在單一查詢中從相關資料表提取資訊。例如,假設我們有一個航班 destinations
資料表和一個飛往目的地的 flights
資料表。flights
資料表包含一個 arrived_at
欄位,表示航班抵達目的地的時間。
透過查詢建構器 select
和 addSelect
方法提供的子查詢功能,我們可以使用單一查詢選取所有 destinations
以及最近抵達該目的地的航班名稱:
use App\Models\Destination;
use App\Models\Flight;
return Destination::addSelect(['last_flight' => Flight::select('name')
->whereColumn('destination_id', 'destinations.id')
->orderByDesc('arrived_at')
->limit(1)
])->get();
子查詢排序
此外,查詢建構器的 orderBy
函數支援子查詢。繼續使用我們的航班範例,我們可以使用此功能根據上次航班抵達目的地的時間來排序所有目的地。同樣地,這可以在執行單一資料庫查詢時完成:
return Destination::orderByDesc(
Flight::select('arrived_at')
->whereColumn('destination_id', 'destinations.id')
->orderByDesc('arrived_at')
->limit(1)
)->get();
擷取單一模型 / 聚合
除了擷取符合指定查詢的所有記錄之外,您還可以使用 find
、first
或 firstWhere
方法來擷取單一記錄。這些方法不會回傳 Eloquent 模型的集合 (collection),而是回傳單一模型實例:
use App\Models\Flight;
// Retrieve a model by its primary key...
$flight = Flight::find(1);
// Retrieve the first model matching the query constraints...
$flight = Flight::where('active', 1)->first();
// Alternative to retrieving the first model matching the query constraints...
$flight = Flight::firstWhere('active', 1);
有時您可能希望在找不到任何結果時執行其他操作。findOr
和 firstOr
方法將回傳單一模型實例,如果找不到結果,則會執行指定的閉包 (closure)。閉包回傳的值將被視為方法的結果:
$flight = Flight::findOr(1, function () {
// ...
});
$flight = Flight::where('legs', '>', 3)->firstOr(function () {
// ...
});
找不到模型時的例外
有時您可能希望在找不到模型時拋出例外 (exception)。這在路由 (routes) 或控制器 (controllers) 中特別有用。findOrFail
和 firstOrFail
方法將擷取查詢的第一個結果;然而,如果找不到結果,則會拋出 Illuminate\Database\Eloquent\ModelNotFoundException
:
$flight = Flight::findOrFail(1);
$flight = Flight::where('legs', '>', 3)->firstOrFail();
如果 ModelNotFoundException
沒有被捕獲,則會自動向客戶端發送 404 HTTP 回應。
use App\Models\Flight;
Route::get('/api/flights/{id}', function (string $id) {
return Flight::findOrFail($id);
});
擷取或建立模型
firstOrCreate
方法會嘗試使用指定的欄位/值對來定位資料庫記錄。如果資料庫中找不到該模型,則會插入一條記錄,其屬性 (attributes) 為合併第一個陣列參數與可選的第二個陣列參數的結果。
firstOrNew
方法與 firstOrCreate
類似,會嘗試在資料庫中定位符合指定屬性的記錄。但是,如果找不到模型,則會回傳一個新的模型實例。請注意,firstOrNew
回傳的模型尚未儲存 (persisted) 到資料庫中。您需要手動呼叫 save
方法來將其儲存:
use App\Models\Flight;
// Retrieve flight by name or create it if it doesn't exist...
$flight = Flight::firstOrCreate([
'name' => 'London to Paris'
]);
// Retrieve flight by name or create it with the name, delayed, and arrival_time attributes...
$flight = Flight::firstOrCreate(
['name' => 'London to Paris'],
['delayed' => 1, 'arrival_time' => '11:30']
);
// Retrieve flight by name or instantiate a new Flight instance...
$flight = Flight::firstOrNew([
'name' => 'London to Paris'
]);
// Retrieve flight by name or instantiate with the name, delayed, and arrival_time attributes...
$flight = Flight::firstOrNew(
['name' => 'Tokyo to Sydney'],
['delayed' => 1, 'arrival_time' => '11:30']
);
擷取聚合
當與 Eloquent 模型互動時,您也可以使用 Laravel 查詢建構器 (query builder) 提供的 count
、sum
、max
和其他 聚合方法 (aggregate methods)。正如您可能預期的,這些方法回傳的是純量值 (scalar value),而非 Eloquent 模型實例:
$count = Flight::where('active', 1)->count();
$max = Flight::where('active', 1)->max('price');
插入與更新模型
插入
當然,當使用 Eloquent 時,我們不僅需要從資料庫中擷取模型,也需要插入新的記錄。幸好,Eloquent 讓這變得簡單。要將新記錄插入資料庫,您應該實例化一個新的模型實例,並為模型設定屬性。然後,呼叫模型實例上的 save
方法:
<?php
namespace App\Http\Controllers;
use App\Models\Flight;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
class FlightController extends Controller
{
/**
* Store a new flight in the database.
*/
public function store(Request $request): RedirectResponse
{
// Validate the request...
$flight = new Flight;
$flight->name = $request->name;
$flight->save();
return redirect('/flights');
}
}
在此範例中,我們將傳入的 HTTP 請求中的 name
欄位賦值給 App\Models\Flight
模型實例的 name
屬性。當我們呼叫 save
方法時,記錄將被插入資料庫。模型的 created_at
和 updated_at
時間戳記在呼叫 save
方法時會自動設定,因此無需手動設定。
或者,您可以使用 create
方法,透過單一 PHP 語句來「儲存」新的模型。create
方法會將插入的模型實例返回給您:
use App\Models\Flight;
$flight = Flight::create([
'name' => 'London to Paris',
]);
然而,在使用 create
方法之前,您需要在模型類別上指定 fillable
或 guarded
屬性。這些屬性是必要的,因為所有 Eloquent 模型預設都受到批量賦值漏洞的保護。要了解更多關於批量賦值的資訊,請參考 批量賦值文件。
更新
save
方法也可用於更新資料庫中已存在的模型。要更新模型,您應該擷取它並設定任何您希望更新的屬性。然後,您應該呼叫模型的 save
方法。同樣地,updated_at
時間戳記將會自動更新,因此無需手動設定其值:
use App\Models\Flight;
$flight = Flight::find(1);
$flight->name = 'Paris to London';
$flight->save();
有時,如果沒有匹配的模型存在,您可能需要更新現有模型或建立新模型。類似於 firstOrCreate
方法,updateOrCreate
方法會持久化模型,因此無需手動呼叫 save
方法。
在下面的範例中,如果存在 departure
位置為 Oakland
且 destination
位置為 San Diego
的航班,其 price
和 discounted
欄位將會被更新。如果沒有此類航班存在,則將會建立一個新航班,其屬性是將第一個參數陣列與第二個參數陣列合併的結果:
$flight = Flight::updateOrCreate(
['departure' => 'Oakland', 'destination' => 'San Diego'],
['price' => 99, 'discounted' => 1]
);
批量更新
更新也可以針對符合給定查詢條件的模型執行。在此範例中,所有 active
狀態且 destination
為 San Diego
的航班將被標記為延遲:
Flight::where('active', 1)
->where('destination', 'San Diego')
->update(['delayed' => 1]);
update
方法預期一個由欄位與值組成的陣列,代表應更新的欄位。update
方法會返回受影響的行數。
⚠️ 警告
當透過 Eloquent 發出批量更新時,saving
、saved
、updating
和 updated
模型事件不會為被更新的模型觸發。這是因為在執行批量更新時,模型實際上並未被擷取。
檢查屬性變更
Eloquent 提供了 isDirty
、isClean
和 wasChanged
方法,用於檢查模型的內部狀態,並判斷其屬性自模型最初被擷取以來發生了哪些變化。
isDirty
方法判斷模型的任何屬性自模型被擷取以來是否已更改。您可以將特定的屬性名稱或屬性陣列傳遞給 isDirty
方法,以判斷是否有任何屬性為「髒」。isClean
方法會判斷某個屬性自模型被擷取以來是否保持不變。此方法也接受一個可選的屬性參數:
use App\Models\User;
$user = User::create([
'first_name' => 'Taylor',
'last_name' => 'Otwell',
'title' => 'Developer',
]);
$user->title = 'Painter';
$user->isDirty(); // true
$user->isDirty('title'); // true
$user->isDirty('first_name'); // false
$user->isDirty(['first_name', 'title']); // true
$user->isClean(); // false
$user->isClean('title'); // false
$user->isClean('first_name'); // true
$user->isClean(['first_name', 'title']); // false
$user->save();
$user->isDirty(); // false
$user->isClean(); // true
wasChanged
方法判斷在當前請求週期中模型上次儲存時是否有任何屬性發生變化。如有需要,您可以傳遞一個屬性名稱來查看特定屬性是否已更改:
$user = User::create([
'first_name' => 'Taylor',
'last_name' => 'Otwell',
'title' => 'Developer',
]);
$user->title = 'Painter';
$user->save();
$user->wasChanged(); // true
$user->wasChanged('title'); // true
$user->wasChanged(['title', 'slug']); // true
$user->wasChanged('first_name'); // false
$user->wasChanged(['first_name', 'title']); // true
getOriginal
方法返回一個陣列,其中包含模型的原始屬性,無論自模型被擷取以來發生了什麼變化。如有需要,您可以傳遞特定的屬性名稱以獲取特定屬性的原始值:
$user = User::find(1);
$user->name; // John
$user->email; // [email protected]
$user->name = 'Jack';
$user->name; // Jack
$user->getOriginal('name'); // John
$user->getOriginal(); // Array of original attributes...
getChanges
方法返回一個陣列,其中包含模型上次儲存時已更改的屬性,而 getPrevious
方法返回一個陣列,其中包含模型上次儲存前的原始屬性值:
$user = User::find(1);
$user->name; // John
$user->email; // [email protected]
$user->update([
'name' => 'Jack',
'email' => '[email protected]',
]);
$user->getChanges();
/*
[
'name' => 'Jack',
'email' => '[email protected]',
]
*/
$user->getPrevious();
/*
[
'name' => 'John',
'email' => '[email protected]',
]
*/
批量賦值
您可以使用 create
方法,以單一 PHP 語句「儲存」一個新模型。插入的模型實例將由該方法回傳給您:
use App\Models\Flight;
$flight = Flight::create([
'name' => 'London to Paris',
]);
然而,在使用 create
方法之前,您需要指定模型類別上的 fillable
或 guarded
屬性。這些屬性是必需的,因為預設情況下,所有 Eloquent 模型都受到批量賦值漏洞的保護。
當使用者傳遞一個意料之外的 HTTP 請求欄位,並且該欄位更改了資料庫中您不期望更改的欄位時,就會發生批量賦值漏洞。例如,惡意使用者可能會透過 HTTP 請求傳送 is_admin
參數,然後該參數會傳遞給模型的 create
方法,允許使用者將自己提升為管理員。
因此,首先您應該定義您希望可以批量賦值的模型屬性。您可以使用模型上的 $fillable
屬性來執行此操作。例如,讓我們將 Flight
模型的 name
屬性設為可批量賦值:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = ['name'];
}
一旦您指定了哪些屬性是可批量賦值的,您就可以使用 create
方法將新記錄插入資料庫。create
方法會回傳新建立的模型實例:
$flight = Flight::create(['name' => 'London to Paris']);
如果您已經有模型實例,您可以使用 fill
方法來使用屬性陣列填充它:
$flight->fill(['name' => 'Amsterdam to Frankfurt']);
批量賦值與 JSON 欄位
賦值 JSON 欄位時,每個欄位可批量賦值的鍵必須在模型的 $fillable
陣列中指定。為了安全起見,當使用 guarded
屬性時,Laravel 不支援更新巢狀 JSON 屬性:
/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = [
'options->enabled',
];
允許批量賦值
如果您想將所有屬性設為可批量賦值,您可以將模型的 $guarded
屬性定義為空陣列。如果您選擇解除模型的保護,您應特別注意始終手動建立傳遞給 Eloquent 的 fill
、create
和 update
方法的陣列:
/**
* The attributes that aren't mass assignable.
*
* @var array<string>|bool
*/
protected $guarded = [];
批量賦值例外
預設情況下,在執行批量賦值操作時,未包含在 $fillable
陣列中的屬性會被默默地丟棄。在生產環境中,這是預期行為;然而,在本地開發期間,這可能會導致困惑,因為模型變更未生效。
如果您願意,可以透過呼叫 preventSilentlyDiscardingAttributes
方法,指示 Laravel 在嘗試填充不可填充的屬性時拋出例外。通常,此方法應在應用程式的 AppServiceProvider
類別的 boot
方法中呼叫:
use Illuminate\Database\Eloquent\Model;
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Model::preventSilentlyDiscardingAttributes($this->app->isLocal());
}
新增或更新
Eloquent 的 upsert
方法可用於在單一原子操作中更新或建立記錄。該方法的第一個參數包含要插入或更新的值,而第二個參數列出在相關聯的資料表中唯一識別記錄的欄位。該方法的第三個也是最後一個參數是一個陣列,包含如果資料庫中已經存在匹配記錄時應該更新的欄位。如果模型上已啟用時間戳記,upsert
方法將自動設定 created_at
和 updated_at
時間戳記:
Flight::upsert([
['departure' => 'Oakland', 'destination' => 'San Diego', 'price' => 99],
['departure' => 'Chicago', 'destination' => 'New York', 'price' => 150]
], uniqueBy: ['departure', 'destination'], update: ['price']);
⚠️ 警告
除了 SQL Server 以外的所有資料庫都要求 upsert
方法的第二個參數中的欄位必須具有「主鍵」或「唯一」索引。此外,MariaDB 和 MySQL 資料庫驅動會忽略 upsert
方法的第二個參數,並始終使用資料表的「主鍵」和「唯一」索引來偵測現有記錄。
刪除模型
要刪除模型,您可以對模型實例呼叫 delete
方法:
use App\Models\Flight;
$flight = Flight::find(1);
$flight->delete();
透過主鍵刪除現有模型
在上面的範例中,我們在呼叫 delete
方法之前先從資料庫中擷取模型。但是,如果您知道模型的主鍵,您可以透過呼叫 destroy
方法來刪除模型,而無需明確地擷取它。除了接受單一主鍵外,destroy
方法還會接受多個主鍵、主鍵陣列或主鍵的 collection:
Flight::destroy(1);
Flight::destroy(1, 2, 3);
Flight::destroy([1, 2, 3]);
Flight::destroy(collect([1, 2, 3]));
如果您使用 軟刪除模型,您可以透過 forceDestroy
方法永久刪除模型:
Flight::forceDestroy(1);
⚠️ 警告
destroy
方法會單獨載入每個模型並呼叫 delete
方法,以便正確地為每個模型派發 deleting
和 deleted
事件。
透過查詢刪除模型
當然,您可以建立一個 Eloquent 查詢來刪除所有符合查詢條件的模型。在此範例中,我們將刪除所有標記為非活動狀態的航班。與批量更新一樣,批量刪除不會為已刪除的模型派發模型事件:
$deleted = Flight::where('active', 0)->delete();
要刪除資料表中的所有模型,您應該執行一個不帶任何條件的查詢:
$deleted = Flight::query()->delete();
⚠️ 警告
透過 Eloquent 執行批量刪除語句時,將不會為已刪除的模型派發 deleting
和 deleted
模型事件。這是因為在執行刪除語句時,模型實際上從未被擷取。
軟刪除
除了實際從資料庫中刪除記錄之外,Eloquent 還可以「軟刪除」模型。當模型被軟刪除時,它們實際上不會從資料庫中移除。相反,會在模型上設定一個 deleted_at
屬性,指示模型被「刪除」的日期和時間。要為模型啟用軟刪除,請將 Illuminate\Database\Eloquent\SoftDeletes
trait 新增到模型中:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Flight extends Model
{
use SoftDeletes;
}
📌 備註
SoftDeletes
trait 會自動將 deleted_at
屬性轉換為 DateTime
/ Carbon
實例。
您還應該將 deleted_at
欄位新增到您的資料庫資料表中。Laravel 的 schema builder 包含一個幫助方法來建立此欄位:
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
Schema::table('flights', function (Blueprint $table) {
$table->softDeletes();
});
Schema::table('flights', function (Blueprint $table) {
$table->dropSoftDeletes();
});
現在,當您在模型上呼叫 delete
方法時,deleted_at
欄位將設定為目前的日期和時間。但是,模型的資料庫記錄將保留在資料表中。當查詢使用軟刪除的模型時,軟刪除的模型將自動從所有查詢結果中排除。
要判斷給定的模型實例是否已軟刪除,您可以使用 trashed
方法:
if ($flight->trashed()) {
// ...
}
還原軟刪除的模型
有時您可能希望「取消刪除」一個軟刪除的模型。要還原軟刪除的模型,您可以在模型實例上呼叫 restore
方法。restore
方法會將模型的 deleted_at
欄位設定為 null
:
$flight->restore();
您也可以在查詢中使用 restore
方法來還原多個模型。同樣地,與其他「批量」操作一樣,這不會為已還原的模型派發任何模型事件:
Flight::withTrashed()
->where('airline_id', 1)
->restore();
restore
方法也可以在建立 relationship 查詢時使用:
$flight->history()->restore();
永久刪除模型
有時您可能需要真正從資料庫中刪除模型。您可以使用 forceDelete
方法從資料庫資料表中永久刪除軟刪除的模型:
$flight->forceDelete();
您也可以在建立 Eloquent relationship 查詢時使用 forceDelete
方法:
$flight->history()->forceDelete();
查詢軟刪除的模型
包含軟刪除的模型
如上所述,軟刪除的模型將自動從查詢結果中排除。但是,您可以透過在查詢上呼叫 withTrashed
方法,強制將軟刪除的模型包含在查詢結果中:
use App\Models\Flight;
$flights = Flight::withTrashed()
->where('account_id', 1)
->get();
withTrashed
方法也可以在建立 relationship 查詢時呼叫:
$flight->history()->withTrashed()->get();
只擷取軟刪除的模型
onlyTrashed
方法將 只 擷取軟刪除的模型:
$flights = Flight::onlyTrashed()
->where('airline_id', 1)
->get();
修剪模型
有時您可能希望定期刪除不再需要的模型。為此,您可以將 Illuminate\Database\Eloquent\Prunable
或 Illuminate\Database\Eloquent\MassPrunable
trait 添加到您希望定期修剪的模型中。將其中一個 trait 添加到模型後,實作一個 prunable
方法,該方法會回傳一個 Eloquent 查詢建構器,用於解析不再需要的模型:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Prunable;
class Flight extends Model
{
use Prunable;
/**
* Get the prunable model query.
*/
public function prunable(): Builder
{
return static::where('created_at', '<=', now()->subMonth());
}
}
標記模型為 Prunable
時,您也可以在模型上定義一個 pruning
方法。此方法將在模型被刪除之前被呼叫。此方法在模型從資料庫中永久移除之前,對於刪除與模型相關的任何額外資源(例如儲存的檔案)非常有用:
/**
* Prepare the model for pruning.
*/
protected function pruning(): void
{
// ...
}
配置好您的可修剪模型後,您應該在應用程式的 routes/console.php
檔案中排程 model:prune
Artisan 命令。您可以自由選擇此命令應執行的適當間隔:
use Illuminate\Support\Facades\Schedule;
Schedule::command('model:prune')->daily();
在幕後,model:prune
命令將自動偵測應用程式 app/Models
目錄中的「Prunable」模型。如果您的模型位於不同位置,您可以使用 --model
選項來指定模型類別名稱:
Schedule::command('model:prune', [
'--model' => [Address::class, Flight::class],
])->daily();
如果您希望在修剪所有其他偵測到的模型時排除某些模型,您可以使用 --except
選項:
Schedule::command('model:prune', [
'--except' => [Address::class, Flight::class],
])->daily();
您可以透過執行帶有 --pretend
選項的 model:prune
命令來測試您的 prunable
查詢。在模擬執行時,model:prune
命令將只會報告如果實際執行命令會修剪多少筆記錄:
php artisan model:prune --pretend
⚠️ 警告
軟刪除的模型如果符合可修剪查詢條件,將會被永久刪除(forceDelete
)。
批量修剪
當模型被標記為 Illuminate\Database\Eloquent\MassPrunable
trait 時,模型將使用批量刪除查詢從資料庫中刪除。因此,pruning
方法不會被呼叫,deleting
和 deleted
模型事件也不會被分發。這是因為在刪除之前,模型從未實際被擷取,從而使修剪過程更加高效:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\MassPrunable;
class Flight extends Model
{
use MassPrunable;
/**
* Get the prunable model query.
*/
public function prunable(): Builder
{
return static::where('created_at', '<=', now()->subMonth());
}
}
複製模型
您可以使用 replicate
方法來建立現有模型實例的一個未儲存副本。當您擁有多個共用許多相同屬性的模型實例時,此方法特別有用:
use App\Models\Address;
$shipping = Address::create([
'type' => 'shipping',
'line_1' => '123 Example Street',
'city' => 'Victorville',
'state' => 'CA',
'postcode' => '90001',
]);
$billing = $shipping->replicate()->fill([
'type' => 'billing'
]);
$billing->save();
若要從複製到新模型的屬性中排除一個或多個屬性,您可以將一個陣列傳遞給 replicate
方法:
$flight = Flight::create([
'destination' => 'LAX',
'origin' => 'LHR',
'last_flown' => '2020-03-04 11:00:00',
'last_pilot_id' => 747,
]);
$flight = $flight->replicate([
'last_flown',
'last_pilot_id'
]);
查詢範圍
全域範圍
全域範圍允許您為特定模型的所有查詢加入限制。Laravel 自帶的 軟刪除 功能就是利用全域範圍,僅從資料庫中擷取「未刪除」的模型。編寫自己的全域範圍可以提供一種方便、簡單的方式,確保每個針對特定模型的查詢都收到某些限制。
生成範圍
若要生成一個新的全域範圍,您可以調用 make:scope
Artisan 指令,這會將生成的範圍放置在您應用程式的 app/Models/Scopes
目錄中:
php artisan make:scope AncientScope
編寫全域範圍
編寫全域範圍很簡單。首先,使用 make:scope
指令生成一個實作 Illuminate\Database\Eloquent\Scope
介面的類別。Scope
介面要求您實作一個方法:apply
。apply
方法可以根據需要向查詢中添加 where
條件或其他類型的子句:
<?php
namespace App\Models\Scopes;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;
class AncientScope implements Scope
{
/**
* Apply the scope to a given Eloquent query builder.
*/
public function apply(Builder $builder, Model $model): void
{
$builder->where('created_at', '<', now()->subYears(2000));
}
}
📌 備註
如果您的全域範圍正在向查詢的 select 子句添加欄位,您應該使用 addSelect
方法而不是 select
。這將防止無意中替換查詢現有的 select 子句。
套用全域範圍
若要將全域範圍指派給模型,您可以直接將 ScopedBy
屬性放置在模型上:
<?php
namespace App\Models;
use App\Models\Scopes\AncientScope;
use Illuminate\Database\Eloquent\Attributes\ScopedBy;
#[ScopedBy([AncientScope::class])]
class User extends Model
{
//
}
或者,您可以透過覆寫模型的 booted
方法並調用模型的 addGlobalScope
方法來手動註冊全域範圍。addGlobalScope
方法接受您的範圍實例作為其唯一引數:
<?php
namespace App\Models;
use App\Models\Scopes\AncientScope;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* The "booted" method of the model.
*/
protected static function booted(): void
{
static::addGlobalScope(new AncientScope);
}
}
將上述範例中的範圍新增到 App\Models\User
模型後,呼叫 User::all()
方法將執行以下 SQL 查詢:
select * from `users` where `created_at` < 0021-02-18 00:00:00
匿名全域範圍
Eloquent 也允許您使用閉包定義全域範圍,這對於不需要獨立類別的簡單範圍特別有用。當使用閉包定義全域範圍時,您應該將自己選擇的範圍名稱作為 addGlobalScope
方法的第一個引數:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* The "booted" method of the model.
*/
protected static function booted(): void
{
static::addGlobalScope('ancient', function (Builder $builder) {
$builder->where('created_at', '<', now()->subYears(2000));
});
}
}
移除全域範圍
如果您想為給定查詢移除全域範圍,您可以使用 withoutGlobalScope
方法。此方法接受全域範圍的類別名稱作為其唯一引數:
User::withoutGlobalScope(AncientScope::class)->get();
或者,如果您使用閉包定義了全域範圍,您應該傳遞您為該全域範圍指派的字串名稱:
User::withoutGlobalScope('ancient')->get();
如果您想移除查詢中的幾個甚至所有全域範圍,您可以使用 withoutGlobalScopes
和 withoutGlobalScopesExcept
方法:
// Remove all of the global scopes...
User::withoutGlobalScopes()->get();
// Remove some of the global scopes...
User::withoutGlobalScopes([
FirstScope::class, SecondScope::class
])->get();
// Remove all global scopes except the given ones...
User::withoutGlobalScopesExcept([
SecondScope::class,
])->get();
局部範圍
局部範圍允許您定義一組常見的查詢條件,以便您可以在應用程式中輕鬆重複使用。例如,您可能需要頻繁地擷取所有被認為是「熱門」的用戶。若要定義範圍,請將 Scope
屬性新增至 Eloquent 方法。
範圍應始終返回相同的查詢建構器實例或 void
:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Attributes\Scope;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* Scope a query to only include popular users.
*/
#[Scope]
protected function popular(Builder $query): void
{
$query->where('votes', '>', 100);
}
/**
* Scope a query to only include active users.
*/
#[Scope]
protected function active(Builder $query): void
{
$query->where('active', 1);
}
}
利用局部範圍
一旦範圍被定義,您可以在查詢模型時呼叫範圍方法。您甚至可以將多個範圍調用串聯起來:
use App\Models\User;
$users = User::popular()->active()->orderBy('created_at')->get();
透過 or
查詢運算子組合多個 Eloquent 模型範圍,可能需要使用閉包來實現正確的 邏輯分組:
$users = User::popular()->orWhere(function (Builder $query) {
$query->active();
})->get();
然而,由於這可能很繁瑣,Laravel 提供了一個「高階」的 orWhere
方法,讓您可以流暢地串聯範圍而無需使用閉包:
$users = User::popular()->orWhere->active()->get();
動態範圍
有時您可能希望定義一個接受參數的範圍。首先,只需將額外的參數添加到您的範圍方法簽名中。範圍參數應在 $query
參數之後定義:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Attributes\Scope;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* Scope a query to only include users of a given type.
*/
#[Scope]
protected function ofType(Builder $query, string $type): void
{
$query->where('type', $type);
}
}
一旦預期的引數已添加到您的範圍方法簽名中,您可以在調用該範圍時傳遞引數:
$users = User::ofType('admin')->get();
待定屬性
如果您想使用查詢範圍來建立具有與用於限制該範圍的相同屬性的模型,您可以在建構範圍查詢時使用 withAttributes
方法:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Attributes\Scope;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
/**
* Scope the query to only include drafts.
*/
#[Scope]
protected function draft(Builder $query): void
{
$query->withAttributes([
'hidden' => true,
]);
}
}
withAttributes
方法將使用給定的屬性向查詢加入 where
條件,同時也會將這些屬性加入透過該範圍建立的任何模型:
$draft = Post::draft()->create(['title' => 'In Progress']);
$draft->hidden; // true
若要指示 withAttributes
方法不向查詢加入 where
條件,您可以將 asConditions
參數設為 false
:
$query->withAttributes([
'hidden' => true,
], asConditions: false);
比較模型
有時候,您可能需要判斷兩個模型是否「相同」。is
和 isNot
方法可用於快速驗證兩個模型是否具有相同的主鍵、資料表和資料庫連線:
if ($post->is($anotherPost)) {
// ...
}
if ($post->isNot($anotherPost)) {
// ...
}
當使用 belongsTo
、hasOne
、morphTo
和 morphOne
關聯時,is
和 isNot
方法也可用。此方法在您希望比較相關模型而無需發出查詢來擷取該模型時特別有用:
if ($post->author()->is($user)) {
// ...
}
事件
📌 備註
想要將您的 Eloquent 事件直接廣播到客戶端應用程式嗎?請參閱 Laravel 的 模型事件廣播。
Eloquent 模型會觸發多個事件,讓您能介入模型生命週期的以下時刻:retrieved
、creating
、created
、updating
、updated
、saving
、saved
、deleting
、deleted
、trashed
、forceDeleting
、forceDeleted
、restoring
、restored
和 replicating
。
當現有模型從資料庫中擷取時,會觸發 retrieved
事件。當新模型首次儲存時,會觸發 creating
和 created
事件。當現有模型被修改且呼叫了 save
方法時,會觸發 updating
/ updated
事件。當模型被建立或更新時,即使模型的屬性尚未改變,也會觸發 saving
/ saved
事件。以 -ing
結尾的事件會在模型變更持久化前觸發,而以 -ed
結尾的事件則會在模型變更持久化後觸發。
要開始監聽模型事件,請在您的 Eloquent 模型上定義一個 $dispatchesEvents
屬性。此屬性將 Eloquent 模型生命週期的各個點映射到您自己的 事件類別。每個模型事件類別都應該透過其建構函式接收受影響的模型實例:
<?php
namespace App\Models;
use App\Events\UserDeleted;
use App\Events\UserSaved;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable
{
use Notifiable;
/**
* The event map for the model.
*
* @var array<string, string>
*/
protected $dispatchesEvents = [
'saved' => UserSaved::class,
'deleted' => UserDeleted::class,
];
}
定義並映射您的 Eloquent 事件後,您可以使用 事件監聽器 來處理這些事件。
⚠️ 警告
當透過 Eloquent 發出批量更新或刪除查詢時,saved
、updated
、deleting
和 deleted
模型事件將不會為受影響的模型觸發。這是因為在執行批量更新或刪除時,模型實際上並未被擷取。
使用閉包
除了使用自訂事件類別外,您也可以註冊閉包,讓它們在各種模型事件觸發時執行。通常,您應該在模型的 booted
方法中註冊這些閉包:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* The "booted" method of the model.
*/
protected static function booted(): void
{
static::created(function (User $user) {
// ...
});
}
}
如果需要,您可以使用 可佇列的匿名事件監聽器 註冊模型事件。這將指示 Laravel 在背景中,使用您應用程式的 佇列 來執行模型事件監聽器:
use function Illuminate\Events\queueable;
static::created(queueable(function (User $user) {
// ...
}));
觀察者
定義觀察者
如果您監聽一個給定模型的許多事件,您可以使用觀察者將所有監聽器分組到一個類別中。觀察者類別的方法名稱反映了您希望監聽的 Eloquent 事件。這些方法都只接收受影響的模型作為唯一引數。make:observer
Artisan 命令是建立新觀察者類別最簡單的方式:
php artisan make:observer UserObserver --model=User
此命令會將新的觀察者放置在您的 app/Observers
目錄中。如果此目錄不存在,Artisan 會為您建立。您剛建立的觀察者將會如下所示:
<?php
namespace App\Observers;
use App\Models\User;
class UserObserver
{
/**
* Handle the User "created" event.
*/
public function created(User $user): void
{
// ...
}
/**
* Handle the User "updated" event.
*/
public function updated(User $user): void
{
// ...
}
/**
* Handle the User "deleted" event.
*/
public function deleted(User $user): void
{
// ...
}
/**
* Handle the User "restored" event.
*/
public function restored(User $user): void
{
// ...
}
/**
* Handle the User "forceDeleted" event.
*/
public function forceDeleted(User $user): void
{
// ...
}
}
要註冊觀察者,您可以在對應的模型上放置 ObservedBy
屬性:
use App\Observers\UserObserver;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
#[ObservedBy([UserObserver::class])]
class User extends Authenticatable
{
//
}
或者,您可以透過在您希望觀察的模型上呼叫 observe
方法來手動註冊觀察者。您可以在應用程式 AppServiceProvider
類別的 boot
方法中註冊觀察者:
use App\Models\User;
use App\Observers\UserObserver;
/**
* Bootstrap any application services.
*/
public function boot(): void
{
User::observe(UserObserver::class);
}
📌 備註
觀察者還可以監聽其他事件,例如 saving
和 retrieved
。這些事件在 事件 文件中有說明。
觀察者與資料庫交易
當模型在資料庫交易中被建立時,您可能希望指示觀察者僅在資料庫交易提交後才執行其事件處理器。您可以透過在觀察者上實作 ShouldHandleEventsAfterCommit
介面來實現此目的。如果資料庫交易未進行中,事件處理器將立即執行:
<?php
namespace App\Observers;
use App\Models\User;
use Illuminate\Contracts\Events\ShouldHandleEventsAfterCommit;
class UserObserver implements ShouldHandleEventsAfterCommit
{
/**
* Handle the User "created" event.
*/
public function created(User $user): void
{
// ...
}
}
靜音事件
您可能偶爾需要暫時「靜音」模型觸發的所有事件。您可以使用 withoutEvents
方法來實現此目的。withoutEvents
方法只接受一個閉包作為其唯一引數。在此閉包中執行的任何程式碼都不會觸發模型事件,並且閉包返回的任何值都將由 withoutEvents
方法返回:
use App\Models\User;
$user = User::withoutEvents(function () {
User::findOrFail(1)->delete();
return User::find(2);
});
在不觸發事件的情況下儲存單一模型
有時您可能希望在不觸發任何事件的情況下「儲存」給定模型。您可以使用 saveQuietly
方法來實現此目的:
$user = User::findOrFail(1);
$user->name = 'Victoria Faith';
$user->saveQuietly();
您也可以在不觸發任何事件的情況下「更新」、「刪除」、「軟刪除」、「還原」及「複製」給定模型:
$user->deleteQuietly();
$user->forceDeleteQuietly();
$user->restoreQuietly();