簡介 (Introduction)
Laravel 包含 Eloquent,一個讓與資料庫互動變得愉快的物件關聯對映器(ORM)。使用 Eloquent 時,每個資料庫資料表都有一個對應的「Model」用於與該資料表互動。除了從資料庫資料表取得記錄外,Eloquent Model 還允許你對資料表進行新增、更新和刪除記錄。
[!NOTE] 在開始之前,請確保在應用程式的
config/database.php設定檔中設定資料庫連線。有關設定資料庫的更多資訊,請查看資料庫設定文件。
產生 Model 類別 (Generating Model Classes)
首先,讓我們建立一個 Eloquent Model。Model 通常位於 app\Models 目錄中,並繼承 Illuminate\Database\Eloquent\Model 類別。你可以使用 make:model Artisan 指令 來產生新的 Model:
php artisan make:model Flight
如果你想在產生 Model 時同時產生資料庫 Migration,可以使用 --migration 或 -m 選項:
php artisan make:model Flight --migration
你可以在產生 Model 時產生各種其他類型的類別,例如 Factory、Seeder、Policy、Controller 和表單請求。此外,這些選項可以組合使用以一次建立多個類別:
# 產生一個 Model 和一個 FlightFactory 類別...
php artisan make:model Flight --factory
php artisan make:model Flight -f
# 產生一個 Model 和一個 FlightSeeder 類別...
php artisan make:model Flight --seed
php artisan make:model Flight -s
# 產生一個 Model 和一個 FlightController 類別...
php artisan make:model Flight --controller
php artisan make:model Flight -c
# 產生一個 Model、FlightController 資源類別和表單請求類別...
php artisan make:model Flight --controller --resource --requests
php artisan make:model Flight -crR
# 產生一個 Model 和一個 FlightPolicy 類別...
php artisan make:model Flight --policy
# 產生一個 Model 和一個 Migration、Factory、Seeder 和 Controller...
php artisan make:model Flight -mfsc
# 產生一個 Model、Migration、Factory、Seeder、Policy、Controller 和表單請求的快捷方式...
php artisan make:model Flight --all
php artisan make:model Flight -a
# 產生一個樞紐 Model...
php artisan make:model Member --pivot
php artisan make:model Member -p
檢查 Model (Inspecting Models)
有時候僅透過瀏覽程式碼可能很難確定 Model 的所有可用屬性和關聯。試試 model:show Artisan 指令,它提供了 Model 所有屬性和關聯的便捷概覽:
php artisan model:show Flight
Eloquent Model 慣例 (Eloquent Model Conventions)
由 make:model 指令產生的 Model 將放置在 app/Models 目錄中。讓我們檢視一個基本的 Model 類別並討論 Eloquent 的一些關鍵慣例:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
// ...
}
資料表名稱 (Table Names)
看了上面的範例後,你可能已經注意到我們沒有告訴 Eloquent 哪個資料庫資料表對應我們的 Flight Model。按照慣例,除非明確指定另一個名稱,否則類別的「蛇形命名法」複數名稱將被用作資料表名稱。因此,在這種情況下,Eloquent 將假設 Flight Model 將記錄儲存在 flights 資料表中,而 AirTrafficController Model 則會將記錄儲存在 air_traffic_controllers 資料表中。
如果你的 Model 對應的資料庫資料表不符合此慣例,你可以透過在 Model 上定義 table 屬性來手動指定 Model 的資料表名稱:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
/**
* 與 Model 關聯的資料表。
*
* @var string
*/
protected $table = 'my_flights';
}
主鍵 (Primary Keys)
Eloquent 也會假設每個 Model 對應的資料庫資料表都有一個名為 id 的主鍵欄位。如有必要,你可以在 Model 上定義一個受保護的 $primaryKey 屬性,以指定用作 Model 主鍵的不同欄位:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
/**
* 與資料表關聯的主鍵。
*
* @var string
*/
protected $primaryKey = 'flight_id';
}
此外,Eloquent 假設主鍵是一個遞增的整數值,這意味著 Eloquent 會自動將主鍵轉換為整數。如果你想使用非遞增或非數字主鍵,你必須在 Model 上定義一個設為 false 的公開 $incrementing 屬性:
<?php
class Flight extends Model
{
/**
* 指示 Model 的 ID 是否自動遞增。
*
* @var bool
*/
public $incrementing = false;
}
如果你的 Model 主鍵不是整數,你應該在 Model 上定義一個受保護的 $keyType 屬性。此屬性的值應為 string:
<?php
class Flight extends Model
{
/**
* 主鍵 ID 的資料類型。
*
* @var string
*/
protected $keyType = 'string';
}
「複合」主鍵 ("Composite" Primary Keys)
Eloquent 要求每個 Model 至少有一個可以作為主鍵的唯一識別「ID」。Eloquent Model 不支援「複合」主鍵。然而,除了資料表的唯一識別主鍵外,你可以自由地在資料庫資料表中新增額外的多欄位唯一索引。
UUID 與 ULID 鍵 (UUID and ULID Keys)
你可以選擇使用 UUID 而不是自動遞增的整數作為 Eloquent Model 的主鍵。UUID 是長度為 36 個字元的通用唯一字母數字識別碼。
如果你想讓 Model 使用 UUID 鍵而不是自動遞增的整數鍵,你可以在 Model 上使用 Illuminate\Database\Eloquent\Concerns\HasUuids trait。當然,你應該確保 Model 有一個 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 將為你的 Model 產生「有序」UUID。這些 UUID 對於索引資料庫儲存更有效率,因為它們可以按字典順序排序。
你可以透過在 Model 上定義 newUniqueId 方法來覆蓋給定 Model 的 UUID 產生過程。此外,你可以透過在 Model 上定義 uniqueIds 方法來指定哪些欄位應該接收 UUID:
use Ramsey\Uuid\Uuid;
/**
* 為 Model 產生新的 UUID。
*/
public function newUniqueId(): string
{
return (string) Uuid::uuid4();
}
/**
* 取得應該接收唯一識別碼的欄位。
*
* @return array<int, string>
*/
public function uniqueIds(): array
{
return ['id', 'discount_code'];
}
如果你願意,你可以選擇使用「ULID」而不是 UUID。ULID 與 UUID 類似;但是,它們只有 26 個字元長。與有序 UUID 一樣,ULID 可以按字典順序排序以實現高效的資料庫索引。若要使用 ULID,你應該在 Model 上使用 Illuminate\Database\Eloquent\Concerns\HasUlids trait。你還應該確保 Model 有一個 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"
時間戳記 (Timestamps)
預設情況下,Eloquent 期望 Model 對應的資料庫資料表上存在 created_at 和 updated_at 欄位。Eloquent 將在建立或更新 Model 時自動設定這些欄位的值。如果你不希望這些欄位由 Eloquent 自動管理,你應該在 Model 上定義一個值為 false 的 $timestamps 屬性:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
/**
* 指示 Model 是否應該被加上時間戳記。
*
* @var bool
*/
public $timestamps = false;
}
如果你需要自訂 Model 時間戳記的格式,請在 Model 上設定 $dateFormat 屬性。此屬性決定日期屬性如何儲存在資料庫中,以及當 Model 被序列化為陣列或 JSON 時的格式:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
/**
* Model 日期欄位的儲存格式。
*
* @var string
*/
protected $dateFormat = 'U';
}
如果你需要自訂用於儲存時間戳記的欄位名稱,你可以在 Model 上定義 CREATED_AT 和 UPDATED_AT 常數:
<?php
class Flight extends Model
{
const CREATED_AT = 'creation_date';
const UPDATED_AT = 'updated_date';
}
如果你想在不修改 Model 的 updated_at 時間戳記的情況下執行 Model 操作,你可以在傳遞給 withoutTimestamps 方法的閉包內操作 Model:
Model::withoutTimestamps(fn () => $post->increment('reads'));
資料庫連線 (Database Connections)
預設情況下,所有 Eloquent Model 將使用為應用程式設定的預設資料庫連線。如果你想指定與特定 Model 互動時應使用的不同連線,你應該在 Model 上定義 $connection 屬性:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
/**
* Model 應該使用的資料庫連線。
*
* @var string
*/
protected $connection = 'mysql';
}
預設屬性值 (Default Attribute Values)
預設情況下,新實例化的 Model 實例不會包含任何屬性值。如果你想為 Model 的某些屬性定義預設值,你可以在 Model 上定義 $attributes 屬性。放置在 $attributes 陣列中的屬性值應該是原始的、「可儲存的」格式,就像它們剛從資料庫讀取一樣:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
/**
* Model 屬性的預設值。
*
* @var array
*/
protected $attributes = [
'options' => '[]',
'delayed' => false,
];
}
設定 Eloquent 嚴格性 (Configuring Eloquent Strictness)
Laravel 提供了幾種方法,允許你在各種情況下設定 Eloquent 的行為和「嚴格性」。
首先,preventLazyLoading 方法接受一個可選的布林引數,指示是否應該防止延遲載入。例如,你可能只想在非正式環境中停用延遲載入,這樣即使正式環境程式碼中意外存在延遲載入關聯,你的正式環境仍將正常運作。通常,此方法應該在應用程式的 AppServiceProvider 的 boot 方法中呼叫:
use Illuminate\Database\Eloquent\Model;
/**
* 啟動任何應用程式服務。
*/
public function boot(): void
{
Model::preventLazyLoading(! $this->app->isProduction());
}
此外,你可以透過呼叫 preventSilentlyDiscardingAttributes 方法來指示 Laravel 在嘗試填充不可填充的屬性時拋出例外。這可以幫助在本地開發期間防止意外錯誤,當嘗試設定未新增到 Model 的 fillable 陣列的屬性時:
Model::preventSilentlyDiscardingAttributes(! $this->app->isProduction());
取得 Model (Retrieving Models)
一旦你建立了 Model 和其關聯的資料庫資料表,你就可以開始從資料庫取得資料了。你可以將每個 Eloquent Model 視為一個強大的查詢建構器,允許你流暢地查詢與 Model 關聯的資料庫資料表。Model 的 all 方法將從 Model 關聯的資料庫資料表中取得所有記錄:
use App\Models\Flight;
foreach (Flight::all() as $flight) {
echo $flight->name;
}
建構查詢 (Building Queries)
Eloquent all 方法將傳回 Model 資料表中的所有結果。然而,由於每個 Eloquent Model 都充當查詢建構器,你可以向查詢新增額外的約束條件,然後呼叫 get 方法來取得結果:
$flights = Flight::where('active', 1)
->orderBy('name')
->limit(10)
->get();
[!NOTE] 由於 Eloquent Model 是查詢建構器,你應該查看 Laravel 查詢建構器提供的所有方法。在編寫 Eloquent 查詢時,你可以使用這些方法中的任何一個。
重新整理 Model (Refreshing Models)
如果你已經有一個從資料庫取得的 Eloquent Model 實例,你可以使用 fresh 和 refresh 方法「重新整理」Model。fresh 方法將從資料庫重新取得 Model。現有的 Model 實例不會受到影響:
$flight = Flight::where('number', 'FR 900')->first();
$freshFlight = $flight->fresh();
refresh 方法將使用資料庫中的新資料重新填充現有 Model。此外,所有已載入的關聯也將被重新整理:
$flight = Flight::where('number', 'FR 900')->first();
$flight->number = 'FR 456';
$flight->refresh();
$flight->number; // "FR 900"
集合 (Collections)
如我們所見,像 all 和 get 這樣的 Eloquent 方法會從資料庫取得多筆記錄。然而,這些方法不會傳回一個普通的 PHP 陣列。相反,它們會傳回一個 Illuminate\Database\Eloquent\Collection 實例。
Eloquent Collection 類別繼承了 Laravel 的基礎 Illuminate\Support\Collection 類別,該類別提供了各種有用的方法來與資料集合互動。例如,reject 方法可用於根據呼叫閉包的結果從集合中移除 Model:
$flights = Flight::where('destination', 'Paris')->get();
$flights = $flights->reject(function (Flight $flight) {
return $flight->cancelled;
});
除了 Laravel 基礎集合類別提供的方法外,Eloquent 集合類別還提供了一些額外的方法,這些方法專門用於與 Eloquent Model 集合互動。
由於 Laravel 的所有集合都實作了 PHP 的可迭代介面,因此你可以像遍歷陣列一樣遍歷集合:
foreach ($flights as $flight) {
echo $flight->name;
}
分塊結果 (Chunking Results)
如果你嘗試透過 all 或 get 方法載入數萬筆 Eloquent 記錄,你的應用程式可能會耗盡記憶體。與其使用這些方法,不如使用 chunk 方法來更有效率地處理大量 Model。
chunk 方法將取得一個 Eloquent Model 的子集,並將它們傳遞給一個閉包進行處理。由於一次只取得當前塊的 Eloquent Model,因此在處理大量 Model 時,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 欄位大於前一塊中最後一個 Model 的 Model:
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 Collection 分塊 (Chunking Using Lazy Collections) (Chunk Using Lazy Collections)
lazy 方法的運作方式與 chunk 方法類似,因為它在幕後以塊的形式執行查詢。然而,lazy 方法不是將每個塊直接傳遞給回呼,而是傳回一個扁平化的 Eloquent Model LazyCollection,讓你可以將結果作為單一串流進行互動:
use App\Models\Flight;
foreach (Flight::lazy() as $flight) {
// ...
}
如果你基於一個在迭代結果時也會更新的欄位來過濾 lazy 方法的結果,你應該使用 lazyById 方法。在內部,lazyById 方法將始終取得 id 欄位大於前一塊中最後一個 Model 的 Model:
Flight::where('departed', true)
->lazyById(200, column: 'id')
->each->update(['departed' => false]);
你可以使用 lazyByIdDesc 方法根據 id 的降序來過濾結果。
游標 (Cursors)
與 lazy 方法類似,當迭代數萬筆 Eloquent Model 記錄時,cursor 方法可用於顯著減少應用程式的記憶體消耗。
cursor 方法只會執行單一資料庫查詢;然而,個別 Eloquent Model 在實際被迭代之前不會被填充。因此,在迭代游標時,任何時候記憶體中只保留一個 Eloquent Model。
[!WARNING] 由於
cursor方法一次只在記憶體中保留一個 Eloquent Model,它無法預先載入關聯。如果你需要預先載入關聯,請考慮使用lazy方法代替。
在內部,cursor 方法使用 PHP generators 來實現此功能:
use App\Models\Flight;
foreach (Flight::where('destination', 'Zurich')->cursor() as $flight) {
// ...
}
cursor 傳回一個 Illuminate\Support\LazyCollection 實例。Lazy Collection 允許你使用典型 Laravel 集合上可用的許多集合方法,同時一次只將一個 Model 載入記憶體:
use App\Models\User;
$users = User::cursor()->filter(function (User $user) {
return $user->id > 500;
});
foreach ($users as $user) {
echo $user->id;
}
雖然 cursor 方法使用的記憶體遠少於一般查詢(因為一次只在記憶體中保留一個 Eloquent Model),但它最終仍會耗盡記憶體。這是因為 PHP 的 PDO 驅動程式在內部將所有原始查詢結果快取在其緩衝區中。如果你正在處理非常大量的 Eloquent 記錄,請考慮使用 lazy 方法代替。
進階子查詢 (Advanced Subqueries)
子查詢選取 (Subquery Selects)
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();
子查詢排序 (Subquery Ordering)
此外,查詢建構器的 orderBy 函式支援子查詢。繼續使用我們的航班範例,我們可以使用此功能根據最後一班航班到達目的地的時間對所有目的地進行排序。同樣,這可以在執行單一資料庫查詢時完成:
return Destination::orderByDesc(
Flight::select('arrived_at')
->whereColumn('destination_id', 'destinations.id')
->orderByDesc('arrived_at')
->limit(1)
)->get();
取得單一 Model / 彙總 (Retrieving Single Models / Aggregates)
除了取得與給定查詢匹配的所有記錄外,你還可以使用 find、first 或 firstWhere 方法取得單一記錄。這些方法不會傳回 Model 集合,而是傳回單一 Model 實例:
use App\Models\Flight;
// 透過主鍵取得 Model...
$flight = Flight::find(1);
// 取得符合查詢約束的第一個 Model...
$flight = Flight::where('active', 1)->first();
// 取得符合查詢約束的第一個 Model 的替代方法...
$flight = Flight::firstWhere('active', 1);
有時你可能希望在沒有找到結果時執行其他操作。findOr 和 firstOr 方法將傳回單一 Model 實例,或者如果沒有找到結果,則執行給定的閉包。閉包傳回的值將被視為該方法的結果:
$flight = Flight::findOr(1, function () {
// ...
});
$flight = Flight::where('legs', '>', 3)->firstOr(function () {
// ...
});
找不到例外 (Not Found Exceptions)
有時你可能希望在找不到 Model 時拋出例外。這在路由或 Controller 中特別有用。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);
});
取得或建立 Model (Retrieving or Creating Models)
firstOrCreate 方法將嘗試使用給定的欄位/值對來尋找資料庫記錄。如果在資料庫中找不到該 Model,將插入一筆記錄,其屬性由第一個陣列引數與可選的第二個陣列引數合併而成。
firstOrNew 方法與 firstOrCreate 類似,將嘗試在資料庫中尋找與給定屬性匹配的記錄。然而,如果找不到 Model,將傳回一個新的 Model 實例。請注意,firstOrNew 傳回的 Model 尚未持久化到資料庫。你需要手動呼叫 save 方法來持久化它:
use App\Models\Flight;
// 透過名稱取得航班,如果不存在則建立它...
$flight = Flight::firstOrCreate([
'name' => 'London to Paris'
]);
// 透過名稱取得航班,或使用名稱、延遲和到達時間屬性建立它...
$flight = Flight::firstOrCreate(
['name' => 'London to Paris'],
['delayed' => 1, 'arrival_time' => '11:30']
);
// 透過名稱取得航班或實例化新的 Flight 實例...
$flight = Flight::firstOrNew([
'name' => 'London to Paris'
]);
// 透過名稱取得航班,或使用名稱、延遲和到達時間屬性實例化...
$flight = Flight::firstOrNew(
['name' => 'Tokyo to Sydney'],
['delayed' => 1, 'arrival_time' => '11:30']
);
取得聚合 (Retrieving Aggregates)
與 Eloquent Model 互動時,你也可以使用 Laravel Query Builder 提供的 count、sum、max 和其他聚合方法。如你所料,這些方法傳回標量值而不是 Eloquent Model 實例:
$count = Flight::where('active', 1)->count();
$max = Flight::where('active', 1)->max('price');
新增與更新 Model (Inserting and Updating Models)
新增 (Inserts)
當然,使用 Eloquent 時,我們不僅需要從資料庫中取得 Model。我們還需要新增記錄。幸好,Eloquent 讓這變得簡單。要將新記錄新增到資料庫,你應該實例化一個新的 Model 實例並在 Model 上設定屬性。然後,在 Model 實例上呼叫 save 方法:
<?php
namespace App\Http\Controllers;
use App\Models\Flight;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
class FlightController extends Controller
{
/**
* 將新航班儲存到資料庫。
*/
public function store(Request $request): RedirectResponse
{
// 驗證請求...
$flight = new Flight;
$flight->name = $request->name;
$flight->save();
return redirect('/flights');
}
}
在這個範例中,我們將傳入 HTTP 請求的 name 欄位指派給 App\Models\Flight Model 實例的 name 屬性。當我們呼叫 save 方法時,將會在資料庫中新增一筆記錄。Model 的 created_at 和 updated_at 時間戳記會在呼叫 save 方法時自動設定,因此無需手動設定它們。
或者,你可以使用 create 方法透過單一 PHP 陳述式「儲存」新的 Model。create 方法將傳回給你新增的 Model 實例:
use App\Models\Flight;
$flight = Flight::create([
'name' => 'London to Paris',
]);
然而,在使用 create 方法之前,你需要在 Model 類別上指定 fillable 或 guarded 屬性。這些屬性是必需的,因為所有 Eloquent Model 預設都受到批量賦值漏洞的保護。要了解更多關於批量賦值的資訊,請參閱批量賦值文件。
更新 (Updates)
save 方法也可以用於更新資料庫中已存在的 Model。要更新 Model,你應該取得它並設定你希望更新的任何屬性。然後,你應該呼叫 Model 的 save 方法。同樣,updated_at 時間戳記將自動更新,因此無需手動設定其值:
use App\Models\Flight;
$flight = Flight::find(1);
$flight->name = 'Paris to London';
$flight->save();
有時候,你可能需要更新現有的 Model,或者如果不存在匹配的 Model 則建立一個新的。與 firstOrCreate 方法類似,updateOrCreate 方法會持久化 Model,因此不需要手動呼叫 save 方法。
在下面的範例中,如果存在 departure 位置為 Oakland 且 destination 位置為 San Diego 的航班,則其 price 和 discounted 欄位將被更新。如果不存在這樣的航班,將建立一個新航班,其屬性是第一個引數陣列與第二個引數陣列合併的結果:
$flight = Flight::updateOrCreate(
['departure' => 'Oakland', 'destination' => 'San Diego'],
['price' => 99, 'discounted' => 1]
);
當使用 firstOrCreate 或 updateOrCreate 等方法時,你可能不知道是建立了新的 Model 還是更新了現有的 Model。wasRecentlyCreated 屬性指示 Model 是否在其當前生命週期中被建立:
$flight = Flight::updateOrCreate(
// ...
);
if ($flight->wasRecentlyCreated) {
// 新增了新的航班記錄...
}
批量更新 (Mass Updates)
也可以針對匹配給定查詢的 Model 執行更新。在這個範例中,所有 active 且 destination 為 San Diego 的航班將被標記為延遲:
Flight::where('active', 1)
->where('destination', 'San Diego')
->update(['delayed' => 1]);
update 方法需要一個包含欄位和值對的陣列,代表應該更新的欄位。update 方法傳回受影響的列數。
[!WARNING] 透過 Eloquent 發出批量更新時,
saving、saved、updating和updatedModel 事件將不會為更新的 Model 觸發。這是因為在發出批量更新時,Model 實際上從未被取得。
Examining Attribute Changes
Eloquent provides the isDirty, isClean, and wasChanged methods to examine the internal state of your model and determine how its attributes have changed from when the model was originally retrieved.
The isDirty method determines if any of the model's attributes have been changed since the model was retrieved. You may pass a specific attribute name or an array of attributes to the isDirty method to determine if any of the attributes are "dirty". The isClean method will determine if an attribute has remained unchanged since the model was retrieved. This method also accepts an optional attribute argument:
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
The wasChanged method determines if any attributes were changed when the model was last saved within the current request cycle. If needed, you may pass an attribute name to see if a particular attribute was changed:
$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
The getOriginal method returns an array containing the original attributes of the model regardless of any changes to the model since it was retrieved. If needed, you may pass a specific attribute name to get the original value of a particular attribute:
$user = User::find(1);
$user->name; // John
$user->email; // john@example.com
$user->name = 'Jack';
$user->name; // Jack
$user->getOriginal('name'); // John
$user->getOriginal(); // Array of original attributes...
The getChanges method returns an array containing the attributes that changed when the model was last saved, while the getPrevious method returns an array containing the original attribute values before the model was last saved:
$user = User::find(1);
$user->name; // John
$user->email; // john@example.com
$user->update([
'name' => 'Jack',
'email' => 'jack@example.com',
]);
$user->getChanges();
/*
[
'name' => 'Jack',
'email' => 'jack@example.com',
]
*/
$user->getPrevious();
/*
[
'name' => 'John',
'email' => 'john@example.com',
]
*/
批量賦值 (Mass Assignment)
你可以使用 create 方法在一行 PHP 中儲存新的模型實例,該方法會回傳剛剛插入的模型:
use App\Models\Flight;
$flight = Flight::create([
'name' => 'London to Paris',
]);
在使用 create 方法之前,你必須在模型中指定 fillable 或 guarded 屬性。這些屬性是必要的,因為所有 Eloquent 模型預設都會防範批量賦值漏洞。
批量賦值漏洞發生在使用者傳入非預期的 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 方法將新紀錄寫入資料庫,此方法會回傳剛建立的模型:
$flight = Flight::create(['name' => 'London to Paris']);
如果你已經有一個模型實例,可以使用 fill 方法將屬性陣列填入模型:
$flight->fill(['name' => 'Amsterdam to Frankfurt']);
批量賦值與 JSON 欄位 (Mass Assignment and JSON Columns)
當批量賦值 JSON 欄位時,必須在模型的 $fillable 陣列中列出每個欄位的鍵。出於安全性考量,當使用 guarded 屬性時,Laravel 無法更新巢狀的 JSON 屬性:
/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = [
'options->enabled',
];
允許批量賦值所有屬性 (Allowing Mass Assignment)
如果你希望讓所有屬性都可以批量賦值,請將模型的 $guarded 屬性設定為空陣列。解除保護後,你應該特別注意手動處理傳給 fill、create 與 update 的陣列:
/**
* The attributes that aren't mass assignable.
*
* @var array<string>|bool
*/
protected $guarded = [];
批量賦值例外 (Mass Assignment Exceptions)
預設情況下,$fillable 陣列中未包含的屬性在執行批量賦值時會被靜默忽略。正式環境中這是預期行為,但在本機開發時容易讓人困惑,因為你無法理解為何模型變更沒生效。
如果你希望在嘗試填入未允許的屬性時拋出例外,可以在 AppServiceProvider 的 boot 方法中呼叫 preventSilentlyDiscardingAttributes:
use Illuminate\Database\Eloquent\Model;
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Model::preventSilentlyDiscardingAttributes($this->app->isLocal());
}
Upsert
你可以使用 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']);
[!WARNING] 除了 SQL Server 以外的所有資料庫都要求
upsert方法第二個參數所指定的欄位具備「主鍵」或「唯一」索引。此外,MariaDB 與 MySQL 的驅動會忽略此參數,始終使用資料表的「主鍵」與「唯一」索引來判斷是否有現有紀錄。
刪除 Model (Deleting Models)
你可以對模型實例呼叫 delete 方法以刪除該模型:
use App\Models\Flight;
$flight = Flight::find(1);
$flight->delete();
使用主鍵刪除現有模型 (Deleting an Existing Model by its Primary Key)
上述範例先從資料庫撈回模型再呼叫 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);
[!WARNING] >
destroy方法會依序載入每個模型並呼叫它們的delete,以確保deleting及deleted事件能正確觸發。
使用查詢刪除模型 (Deleting Models Using Queries)
你也可以建立 Eloquent 查詢刪除符合條件的所有模型。下列範例中,我們刪除所有標記為非啟用的航班。與批量更新一樣,批量刪除不會觸發模型事件:
$deleted = Flight::where('active', 0)->delete();
若要刪除資料表中的全部模型,只要建立不帶條件的查詢即可:
$deleted = Flight::query()->delete();
[!WARNING] 使用 Eloquent 執行批量刪除時,被刪除的模型不會觸發
deleting與deleted事件,因為模型不會被實際撈回。
軟刪除 (Soft Deleting)
除了實際從資料庫中移除記錄外,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;
}
[!NOTE] >
SoftDeletestrait 會自動將deleted_at屬性轉換為DateTime/Carbon物件。
你也應在資料表中新增 deleted_at 欄位。Laravel 的 schema builder 提供了建立與移除 deleted_at 欄位的方法:
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()) {
// ...
}
還原軟刪除模型 (Restoring Soft Deleted Models)
若想「還原」軟刪除的模型,可在模型實例上呼叫 restore 方法,會將 deleted_at 設為 null:
$flight->restore();
也可以在查詢中使用 restore 還原多筆模型。與其他批量操作相同,不會為還原的模型觸發事件:
Flight::withTrashed()
->where('airline_id', 1)
->restore();
restore 方法也可用於建立 關聯 的查詢:
$flight->history()->restore();
永久刪除模型 (Permanently Deleting Models)
若需要從資料庫中實際移除軟刪除的模型,可使用 forceDelete:
$flight->forceDelete();
此方法也可用於關聯查詢:
$flight->history()->forceDelete();
查詢軟刪除的模型 (Querying Soft Deleted Models)
包含軟刪除的模型 (Including Soft Deleted Models)
如前所述,預設查詢會排除已軟刪除的模型。若要強制包含它們,可在查詢上呼叫 withTrashed:
use App\Models\Flight;
$flights = Flight::withTrashed()
->where('account_id', 1)
->get();
withTrashed 也可在建立 關聯 查詢時使用:
$flight->history()->withTrashed()->get();
只取得軟刪除的模型 (Retrieving Only Soft Deleted Models)
onlyTrashed 方法會只取出軟刪除的模型:
$flights = Flight::onlyTrashed()
->where('airline_id', 1)
->get();
清理 Model (Pruning Models)
如果你希望定期刪除不再需要的模型,可以在想要清理的模型上使用 Illuminate\Database\Eloquent\Prunable 或 Illuminate\Database\Eloquent\MassPrunable trait。加入 trait 後,實作一個回傳 Eloquent 查詢建構器的 prunable 方法,該查詢應會選出需要刪除的模型:
<?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 trait,也可以定義 pruning 方法。該方法會在模型刪除前呼叫,可用來移除任何額外的資源,例如儲存的檔案:
/**
* Prepare the model for pruning.
*/
protected function pruning(): void
{
// ...
}
完成設定後,應在應用程式的 routes/console.php 中排程 Artisan 指令 model:prune,自訂適合的執行頻率:
use Illuminate\Support\Facades\Schedule;
Schedule::command('model:prune')->daily();
model:prune 指令會自動掃描 app/Models 資料夾中的可清理模型。若模型存放位置不同,可透過 --model 選項指定類別名稱:
Schedule::command('model:prune', [
'--model' => [Address::class, Flight::class],
])->daily();
若只想排除特定模型,可加上 --except 選項:
Schedule::command('model:prune', [
'--except' => [Address::class, Flight::class],
])->daily();
你也可以使用 --pretend 選項測試 prunable 查詢。此模式不會真正執行刪除,只會報告如果執行會刪除多少筆資料:
php artisan model:prune --pretend
[!WARNING] 如果軟刪除的模型符合清理條件,仍會被永久刪除(
forceDelete)。
批量清理 (Mass Pruning)
若模型使用 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());
}
}
複製 Model (Replicating Models)
你可以使用 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'
]);
查詢範圍 (Query Scopes)
全域範圍 (Global Scopes)
全域範圍允許你為某個模型的所有查詢加入額外條件。Laravel 本身的 軟刪除 功能即使用全域範圍,只查詢「非刪除」的模型。自己撰寫全域範圍可以方便地確保某個模型的每個查詢都有特定的條件。
產生範圍 (Generating Scopes)
要產生新的全域範圍,可以執行 make:scope Artisan 指令,該指令會在應用程式的 app/Models/Scopes 目錄建立對應類別:
php artisan make:scope AncientScope
編寫全域範圍 (Writing Global Scopes)
撰寫全域範圍非常簡單。透過 make:scope 指令建立類別後,讓它實作 Illuminate\Database\Eloquent\Scope 介面。該介面只要求你實作 apply 方法:
<?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));
}
}
[!NOTE] 如果全域範圍會修改查詢的
select,請使用addSelect而非select,避免不小心覆蓋原本的欄位清單。
套用全域範圍 (Applying Global Scopes)
要將全域範圍套用到模型上,可以使用 ScopedBy attribute:
<?php
namespace App\Models;
use App\Models\Scopes\AncientScope;
use Illuminate\Database\Eloquent\Attributes\ScopedBy;
#[ScopedBy([AncientScope::class])]
class User extends Model
{
//
}
或是覆寫模型的 booted 方法,透過 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
匿名全域範圍 (Anonymous Global Scopes)
你也可以用閉包定義全域範圍,這對於簡單範圍而言特別方便。在使用閉包時,應在 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));
});
}
}
移除全域範圍 (Removing Global Scopes)
若要在某個查詢移除全域範圍,可以使用 withoutGlobalScope,並傳入該範圍的類別名稱:
User::withoutGlobalScope(AncientScope::class)->get();
若你是以閉包定義範圍,則傳入你自訂的文字名稱:
User::withoutGlobalScope('ancient')->get();
若要移除多個或全部的全域範圍,可使用 withoutGlobalScopes 與 withoutGlobalScopesExcept:
// 移除所有全域範圍...
User::withoutGlobalScopes()->get();
// 移除部分全域範圍...
User::withoutGlobalScopes([
FirstScope::class, SecondScope::class
])->get();
// 只保留給定的全域範圍...
User::withoutGlobalScopesExcept([
SecondScope::class,
])->get();
區域範圍 (Local Scopes)
區域範圍讓你定義可重複使用的查詢條件組。如果你經常需要查詢「熱門」使用者,可以使用 Scope attribute 來定義範圍。
範圍方法應該總是回傳同一個查詢建構器或 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);
}
}
使用區域範圍 (Utilizing a Local Scope)
定義範圍後,便可以在查詢時呼叫這些方法,甚至可以串接多個範圍:
use App\Models\User;
$users = User::popular()->active()->orderBy('created_at')->get();
若要將多個範圍以 or 邏輯組合,可能需要使用閉包來維持正確的邏輯分組:
$users = User::popular()->orWhere(function (Builder $query) {
$query->active();
})->get();
為了避免這種繁瑣,Laravel 提供「高階」的 orWhere 連霸語法,可無需閉包直接串接範圍:
$users = User::popular()->orWhere->active()->get();
動態範圍 (Dynamic Scopes)
有時候你可能希望範圍接受參數。只要在範圍方法簽章中加入額外參數即可。所有參數必須寫在 $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();
待處理屬性 (Pending Attributes)
如果你希望透過範圍建立的模型帶有與範圍一致的屬性,可以在建立查詢時使用 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 將屬性轉為查詢條件,可以將 asConditions 參數設為 false:
$query->withAttributes([
'hidden' => true,
], asConditions: false);
比較 Model (Comparing Models)
有時你需要判斷兩個模型是否「相同」。is 與 isNot 方法可以快速檢查兩個模型的主鍵、資料表與資料庫連線是否相同:
if ($post->is($anotherPost)) {
// ...
}
if ($post->isNot($anotherPost)) {
// ...
}
is 與 isNot 也可用於 belongsTo、hasOne、morphTo 與 morphOne 關聯 中,特別適合在不查詢相關模型的情況下進行比較:
if ($post->author()->is($user)) {
// ...
}
事件 (Events)
[!NOTE] 若要將 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 結尾時則在儲存後觸發。
要開始監聽模型事件,可在模型中定義 $dispatchesEvents 屬性,將生命週期的各個階段對應到你自訂的 事件類別。每個事件類別的建構子都應該接受影響的模型實例:
<?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,
];
}
定義完事件後,可使用 事件監聽器 來處理這些事件。
[!WARNING] 透過 Eloquent 執行批量更新或刪除時,
saved、updated、deleting與deleted等模型事件不會被觸發,因為模型從未被撈出來。
使用閉包 (Using Closures)
你也可以註冊閉包來處理模型事件。通常會在模型的 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) {
// ...
}));
觀察者 (Observers)
定義觀察者 (Defining Observers)
如果你需要監聽某個模型的許多事件,可以透過觀察者將所有監聽邏輯集中在單一類別。觀察者方法名稱會對應欲監聽的 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 attribute:
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);
}
[!NOTE] 觀察者還可以監聽
saving、retrieved等事件,這些事件在 事件 文件中有更多說明。
觀察者與資料庫交易 (Observers and Database Transactions)
若模型是在資料庫交易中建立,你可能希望觀察者等到交易提交後再執行。只要讓觀察者實作 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
{
// ...
}
}
靜音事件 (Muting Events)
有時你可能需要暫時「靜音」模型的所有事件。可使用 withoutEvents 方法實現,該方法接受一個閉包,此閉包內的程式碼不會觸發任何模型事件,且會將閉包回傳值回傳:
use App\Models\User;
$user = User::withoutEvents(function () {
User::findOrFail(1)->delete();
return User::find(2);
});
在不觸發事件的情況下儲存單一模型 (Saving a Single Model Without Events)
若你希望在不觸發任何事件的情況下儲存模型,可以使用 saveQuietly 方法:
$user = User::findOrFail(1);
$user->name = 'Victoria Faith';
$user->saveQuietly();
同樣地,也可以在不觸發事件的情況下 update、delete、soft delete、restore 與 replicate 模型:
$user->deleteQuietly();
$user->forceDeleteQuietly();
$user->restoreQuietly();