快速入門
Eloquent:入門
在開始之前,請確保在
config/database.php
#首先,建立一個 Eloquent 模型。模型通常在
app 目錄中,但你可以根據
composer.json 檔案將他們放置在可以被自動載入的任意位置。所有的 Eloquent 模型都繼承到
Illuminate\Database\Eloquent\Model
建立模型最簡單的方法就是使用
make:model
php artisan make:model Flight
如果要在產生模型的時候產生資料庫遷移,可以使用
- -migration 或
-m 選項:php artisan make:model Flight --migration php artisan make:model Flight -m#Eloquent 模型約定
<?php namespace App; use Illuminate\Database\Eloquent\Model; class Flight extends Model{ // }######
資料表名稱
請注意,我們並沒有告訴 Eloquent 我們的 Flight
模型使用哪個資料表。除非明確地指定了其它名稱,否則將使用類別的複數形式“蛇形命名”來作為表名。因此,在這種情況下,Eloquent 將假設 Flight
模型儲存的是 flights
資料表中的資料。你可以透過在模型上定義table
屬性來指定自訂資料表:
<?php namespace App; use Illuminate\Database\Eloquent\Model; class Flight extends Model{ /** * 与模型关联的表名 * * @var string */ protected $table = 'my_flights'; }
主鍵
Eloquent 也會假設每個資料表都有一個名為id
的主鍵列。你可以定義一個受保護的 $primaryKey
屬性來重寫約定。
此外,Eloquent 假設主鍵是一個自增的整數值,這意味著預設主鍵會自動轉換為 int
類型。如果您希望使用非遞增或非數字的主鍵則需要設定公共的 $incrementing
屬性設定為 false
。如果你的主鍵不是整數,你需要將模型上受保護的 $keyType
屬性設為 string
。
時間戳
預設情況下,Eloquent 預期你的資料表中存在 created_at
和 updated_at
。如果你不想讓Eloquent 自動管理這兩個列, 請將模型中的$timestamps
屬性設為false
:
<?php namespace App; use Illuminate\Database\Eloquent\Model; class Flight extends Model{ /** * 指示模型是否自动维护时间戳 * * @var bool */ public $timestamps = false; }
如果需要自訂時間戳的格式,在你的模型中設定$dateFormat
屬性。這個屬性決定日期屬性在資料庫的儲存方式,以及模型序列化為陣列或JSON 的格式:
<?php namespace App; use Illuminate\Database\Eloquent\Model; class Flight extends Model{ /** * 模型日期列的存储格式。 * * @var string */ protected $dateFormat = 'U'; }
如果你需要自訂儲存時間戳記的欄位名,可以在模型中設定CREATED_AT
和UPDATED_AT
常數的值來實現:
<?php class Flight extends Model{ const CREATED_AT = 'creation_date'; const UPDATED_AT = 'last_update'; }
資料庫連接
預設情況下,Eloquent 模型將使用你的應用程式配置的預設資料庫連線。如果你想為模型指定一個不同的連接,設定$connection
屬性:
<?php namespace App; use Illuminate\Database\Eloquent\Model; class Flight extends Model{ /** * 模型的连接名称 * * @var string */ protected $connection = 'connection-name'; }##預設屬性值如果要為模型的某些屬性定義預設值,可以在模型上定義
$attributes 屬性:
<?php namespace App; use Illuminate\Database\Eloquent\Model; class Flight extends Model{ /** * 模型的默认属性值。 * * @var array */ protected $attributes = [ 'delayed' => false, ]; }模型檢索建立模型和它關聯的資料庫表後,你就可以從資料庫中查詢資料了。將每個 Eloquent 模型想像成一個強大的查詢建構器 query builder ,你可以用它更快速的查詢與其相關聯的資料表。例如:
<?php $flights = App\Flight::all(); foreach ($flights as $flight) { echo $flight->name; }
附加約束
Eloquent 的 all
方法會傳回模型中所有的結果。由於每個Eloquent 模型都會作為一個查詢建構器,所以你也可以加入查詢條件,然後使用get
方法來取得查詢結果:
$flights = App\Flight::where('active', 1) ->orderBy('name', 'desc') ->take(10) ->get();
{tip} 因為Eloquent 模型也是查詢建構器,所以你也應該閱讀查詢建構器可用的所有方法。你可以在 Eloquent 查詢中使用這些方法。
重新載入模型
你可以使用 fresh
和 refresh
方法重新載入模型。 fresh
方法會重新從資料庫中檢索模型。現有的模型實例不受影響:
$flight = App\Flight::where('number', 'FR 900')->first(); $freshFlight = $flight->fresh();
refresh
方法使用資料庫中的新資料重新賦值現有模型。此外,已經載入的關係會被重新載入:
$flight = App\Flight::where('number', 'FR 900')->first(); $flight->number = 'FR 456';$flight->refresh(); $flight->number; // "FR 900"
#集合
對於Eloquent 中的 all
和get
方法可以查詢多個結果,傳回一個Illuminate\Database\Eloquent\Collection
實例。 Collection
類別提供了許多輔助函數來處理Eloquent 結果:
$flights = $flights->reject(function ($flight) { return $flight->cancelled; });
你可以像陣列一樣遍歷集合:
foreach ($flights as $flight) { echo $flight->name; }
分塊結果
如果你需要處理數以千計的Eloquent 結果,使用chunk
指令。 chunk
方法會檢索 Eloquent 模型中的『分塊』將他們提供給指定的 Closure
處理。在處理大型結果集時,使用 chunk
方法可以節省記憶體:
Flight::chunk(200, function ($flights) { foreach ($flights as $flight) { // } });
傳遞到方法的第一個參數是希望每個『分塊』接收的資料量。閉包作為第二個參數傳遞,它在每次從資料庫中檢索分塊的時候調用。它將執行資料庫查詢把檢索分塊的結果傳遞給閉包方法。
使用遊標
cursor
方法允許你使用遊標遍歷資料庫,它只執行一次查詢。處理大量的資料時, cursor
方法可以大幅減少記憶體的使用量:
foreach (Flight::where('foo', 'bar')->cursor() as $flight) { // }##擷取單一模型/ 集合除了從指定的資料表檢索所有記錄外,你可以使用
find 或
first 方法來檢索單一記錄。這些方法傳回單一模型實例,而不是傳回模型集合:
// 通过主键检索一个模型... $flight = App\Flight::find(1); // 检索符合查询限制的第一个模型... $flight = App\Flight::where('active', 1)->first();你也可以使用主鍵陣列作為參數呼叫
find 方法,它將傳回符合記錄的集合:
$flights = App\Flight::find([1, 2, 3]);
‘未找到’異常
有時你希望在未找到模型時拋出異常。這在控制器和路由中非常有用。 findOrFail
和firstOrFail
方法會擷取查詢的第一個結果,如果找不到,將拋出Illuminate\Database\Eloquent\ModelNotFoundException
例外:
$model = App\Flight::findOrFail(1); $model = App\Flight::where('legs', '>', 100)->firstOrFail();
如果沒有捕獲異常,則會自動傳回404
回應給使用者。也就是說,使用這些方法時,沒有必要再寫個檢查來傳回404
回應::
Route::get('/api/flights/{id}', function ($id) { return App\Flight::findOrFail($id); });
檢索集合
你也可以使用查詢建構器提供的count
, sum
, max
, 和其他的聚合函數。這些方法只會傳回適當的標量值而不是一個模型實例:
$count = App\Flight::where('active', 1)->count(); $max = App\Flight::where('active', 1)->max('price');
插入& 更新模型
插入
要新增一筆記錄,先建立新模型實例,給實例設定屬性,然後呼叫 save
方法:
<?php namespace App\Http\Controllers; use App\Flight;use Illuminate\Http\Request; use App\Http\Controllers\Controller; class FlightController extends Controller{ /** * 创建一个新的航班实例 * * @param Request $request * @return Response */ public function store(Request $request) { // 校验请求... $flight = new Flight; $flight->name = $request->name; $flight->save(); } }
在這個範例中,我們將HTTP 請求參數name
賦值給了App\Flight
模型實例的name
屬性。當呼叫 save
方法時,將會插入一筆新記錄。 created_at
和 updated_at
時間戳記將會自動設置,不需要手動賦值。
#更新
save
方法也可以用來更新資料庫已經存在的模型。更新模型,你需要先檢索出來,設定要更新的屬性,然後呼叫 save
方法。同樣,updated_at
時間戳會自動更新,所以也不需要手動賦值:
$flight = App\Flight::find(1); $flight->name = 'New Flight Name';$flight->save();
批次更新
也可以更新符合查詢條件的多個模型。在這個範例中,所有的active
和 destination
為San Diego
的航班會被標記為延誤:
App\Flight::where('active', 1) ->where('destination', 'San Diego') ->update(['delayed' => 1]);
#update
方法接受一個鍵為欄位名稱資料為值的陣列。
#{note} 透過 Eloquent 批次更新時, 更新的模型不會觸發
saved
和updated
事件。因為在批次更新時,從來不會去檢索模型。
批量賦值
你也可以使用 create
方法來儲存新模型,此方法會傳回模型實例。不過,在使用之前,你需要在模型上指定 fillable
或 guarded
屬性,因為所有的 Eloquent 模型都預設不可進行批量賦值。
當使用者透過 HTTP 請求傳入一個意外的參數,而該參數更改了資料庫中你不需要更改的欄位。例如:惡意使用者可能會透過 HTTP 請求傳入 is_admin
參數,然後將其傳遞給 create
方法,此操作可讓使用者將自己升級為管理員。
所以,在開始之前,你應該先定義好模型上的哪些屬性是可以被批次賦值的。你可以透過模型上的 $fillable
屬性來實現。例如:讓Flight
模型的name
屬性可以被批次賦值:
<?php namespace App; use Illuminate\Database\Eloquent\Model; class Flight extends Model{ /** * 可以被批量赋值的属性。 * * @var array */ protected $fillable = ['name']; }
一旦我們設定好了可以批次賦值的屬性,就可以透過create
方法插入新資料到資料庫中了。 create
方法會傳回已儲存的模型實例:
$flight = App\Flight::create(['name' => 'Flight 10']);
如果你已經有一個模型實例,你可以傳遞一個陣列給fill
方法來賦值:
$flight->fill(['name' => 'Flight 22']);
保護屬性
$fillable
可以看作批量賦值的「白名單」, 你也可以使用$guarded
屬性來實現。 $guarded
屬性包含的是不允許批次賦值的陣列。也就是說, $guarded
從功能上將更像是一個「黑名單」。注意:你只能使用 $fillable
或 $guarded
二者中的一個,不可同時使用。在下面這個範例中,除了price
屬性,其他的屬性都可以批次賦值:
<?php namespace App; use Illuminate\Database\Eloquent\Model; class Flight extends Model{ /** * 不可批量赋值的属性。 * * @var array */ protected $guarded = ['price']; }
如果你想讓所有屬性都可以批次賦值, 你可以將$guarded
定義成一個空數組:
/** * 不可以批量赋值的属性。 * * @var array */ protected $guarded = [];
其他建立方法
firstOrCreate
/ firstOrNew
這裡有兩個你可能用來批次賦值的方法: firstOrCreate
和firstOrNew
。 firstOrCreate
方法會透過給定的 列 / 值 來匹配資料庫中的資料。如果在資料庫中找不到對應的模型, 則會從第一個參數的屬性乃至第二個參數的屬性中建立一筆記錄插入到資料庫。
firstOrNew
方法像 firstOrCreate
方法一樣嘗試透過給定的屬性來尋找資料庫中的記錄。不同的是,如果 firstOrNew
方法找不到對應的模型,會傳回一個新的模型實例。注意 firstOrNew
傳回的模型實例尚未儲存到資料庫中,你需要手動呼叫 save
方法來儲存:
// 通过 name 来查找航班,不存在则创建... $flight = App\Flight::firstOrCreate(['name' => 'Flight 10']); // 通过 name 查找航班,不存在则使用 name 和 delayed 属性创建... $flight = App\Flight::firstOrCreate(['name' => 'Flight 10'], ['delayed' => 1]); // 通过 name 查找航班,不存在则创建一个实例... $flight = App\Flight::firstOrNew(['name' => 'Flight 10']); // 通过 name 查找航班,不存在则使用 name 和 delayed 属性创建一个实例... $flight = App\Flight::firstOrNew(['name' => 'Flight 10'], ['delayed' => 1]);
updateOrCreate
你也可能會遇到希望更新現有模型或在不存在的情況下則建立新的模型的情境。 Laravel 提供了 updateOrCreate
方法只有一個步驟就可以實作。跟firstOrCreate
方法一樣,updateOrCreate
配對到對應模型,所以不需要呼叫save()
方法:
// 如果有从奥克兰到圣地亚哥的航班,则价格定为99美元。 // 如果没匹配到存在的模型,则创建一个。 $flight = App\Flight::updateOrCreate( ['departure' => 'Oakland', 'destination' => 'San Diego'], ['price' => 99] );
#刪除模型
可以在模型實例上呼叫delete
方法來刪除實例:
$flight = App\Flight::find(1); $flight->delete();
透過主鍵刪除模型
在上面的範例中,在呼叫delete
之前需要先去資料庫中尋找對應的模型。事實上,如果你知道了模型的主鍵,你可以直接使用 destroy
方法來刪除模型,而不用先去資料庫中尋找。 destroy
方法除了接受單一主鍵作為參數之外,還接受多個主鍵,或使用數組,集合來保存多個主鍵:
App\Flight::destroy(1); App\Flight::destroy(1, 2, 3); App\Flight::destroy([1, 2, 3]); App\Flight::destroy(collect([1, 2, 3]));
透過查詢刪除模型
你也可以在模型上執行刪除語句。在這個例子中,我們將刪除所有標記為非活躍的航班。與批次更新一樣,批次刪除不會為刪除的模型啟動任何模型事件:
$deletedRows = App\Flight::where('active', 0)->delete();
{note} 透過Eloquent 執行批次刪除語句時,不會觸發
deleting
和deleted
模型事件。因此,執行刪除語句時,從不檢索模型範例。
刪除刪除
除了真實刪除資料庫記錄,Eloquent 也可以「軟體刪除」模型。軟刪除的模型並不是真的從資料庫中刪除了。事實上,是在模型上設定了 deleted_at
屬性並將其值寫入資料庫。如果 deleted_at
值非空,代表這個模型已被軟刪除。如果要開啟模型軟體移除功能,你需要在模型上使用Illuminate\Database\Eloquent\SoftDeletes
trait:
<?php namespace App; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; class Flight extends Model{ use SoftDeletes; }
{tip}
SoftDeletes
trait會自動將deleted_at
屬性轉換成DateTime
/Carbon
實例
當然,你需要把deleted_at
欄位加入到資料表中。 Laravel 的資料庫遷移有建立這個欄位的方法:
Schema::table('flights', function (Blueprint $table) { $table->softDeletes(); });
那現在,當你在模型實例上使用delete
方法, 當前日期時間會寫入deleted_at
字段。同時,查詢出來的結果也會自動排除已被軟體刪除的記錄。
你可以使用trashed
方法來驗證目前模型是否軟體刪除:
if ($flight->trashed()) { // }
查詢軟刪除模型
包括已軟刪除的模型
前面提到,查詢結果會自動剔除已被軟刪除的結果。當然,你可以使用withTrashed
方法來取得包含軟刪除模型在內的模型:
$flights = App\Flight::withTrashed() ->where('account_id', 1) ->get();
withTrashed
方法也可以用在關聯查詢:
$flight->history()->withTrashed()->get();
檢索軟體刪除模型
onlyTrashed
方法 只 取得已軟體刪除的模型:
$flights = App\Flight::onlyTrashed() ->where('airline_id', 1) ->get();
#恢復軟體刪除模型
有時會對軟刪除模型進行「撤銷」,在已軟體刪除的資料上使用restore
方法即可恢復到有效狀態:
$flight->restore();
你也可以在查詢中使用restore
方法,從而快速還原多個模型。和其他批次」操作一樣,這個操作不會觸發模型的任何事件:
App\Flight::withTrashed() ->where('airline_id', 1) ->restore();
類似withTrashed
方法, restore
方法也用在關聯上:
$flight->history()->restore();
永久刪除
要真實刪除資料時,使用forceDelete
方法即可:
// 单个模型实例的永久删除... $flight->forceDelete(); // 关联模型的永久删除... $flight->history()->forceDelete();
查詢作用域
#全域作用域
全域作用域可以為模型的查詢都加上上約束。 Laravel 的 軟體刪除 功能就是利用此功能從資料庫取得「未刪除」的模型。你可以寫自己的全域作用域,很簡單、方便的為每個模型查詢都加上約束條件:
#寫全域作用域
寫全域作用域很簡單。定義一個實作 Illuminate\Database\Eloquent\Scope
介面的類,並實作 apply
這個方法。根據你的需求,在apply
方法中加入查詢的where
條件:
<?php namespace App\Scopes; use Illuminate\Database\Eloquent\Scope; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Builder; class AgeScope implements Scope{ /** * 把约束加到 Eloquent 查询构造中。 * * @param \Illuminate\Database\Eloquent\Builder $builder * @param \Illuminate\Database\Eloquent\Model $model * @return void */ public function apply(Builder $builder, Model $model) { $builder->where('age', '>', 200); } }
{tip} 如果需要在select 語句裡添加字段,應使用
addSelect
方法,而不是select
方法。這將有效防止無意中替換現有 select 語句的情況。
應用全域作用域
要將全域作用域指派給模型,需要重寫模型的boot
方法並使用addGlobalScope
方法:
<?php namespace App;use App\Scopes\AgeScope; use Illuminate\Database\Eloquent\Model; class User extends Model{ /** * 模型的 「启动」 方法. * * @return void */ protected static function boot() { parent::boot(); static::addGlobalScope(new AgeScope); } }
新增作用域後,對User::all()
的查詢會產生以下SQL 查詢語句:
select * from `users` where `age` > 200
匿名全域作用域
Eloquent 同樣允許使用閉包定義全域作用域,這樣就不需要為一個簡單的作用域而寫一個單獨的類別:
<?php namespace App; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Builder; class User extends Model{ /** *模型的「启动」方法. * * @return void */ protected static function boot() { parent::boot(); static::addGlobalScope('age', function (Builder $builder) { $builder->where('age', '>', 200); }); } }
取消全域作用域
如果需要對目前查詢取消全域作用域,需要使用 withoutGlobalScope
方法。這個方法只接受全域作用域類別名稱作為它唯一的參數:
User::withoutGlobalScope(AgeScope::class)->get();
或者,如果使用閉包定義全域作用域的話:
User::withoutGlobalScope('age')->get();
如果你需要取消部分或全部的全域作用域的話,需要使用 withoutGlobalScopes
方法:
// 取消所有的全局作用域... User::withoutGlobalScopes()->get(); // 取消部分全局作用域... User::withoutGlobalScopes([ FirstScope::class, SecondScope::class ])->get();##本地作用域
本地作用域
前綴:
作用域總是傳回一個查詢建構器實例:<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model{
/**
* 只查询受欢迎的用户的作用域.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopePopular($query)
{
return $query->where('votes', '>', 100);
}
/**
* 只查询 active 用户的作用域.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeActive($query)
{
return $query->where('active', 1);
}
}
#使用本機作用域一旦定義了作用域,就可以在查詢該模型時呼叫作用域方法。不過,在呼叫這些方法時不必包含
前綴。甚至可以鍊式呼叫多個作用域,例如:
借助$users = App\User::popular()->active()->orderBy('created_at')->get();
$users = App\User::popular()->orWhere(function (Builder $query) { $query->active(); })->get();
因為這樣可能會有點麻煩,Laravel 提供了「高階的」
orWhere 方法,它允許你在鍊式呼叫作用域時不使用閉包:$users = App\User::popular()->orWhere->active()->get();
#動態作用域
有時可能會希望定義一個可以接受參數的作用域。把額外參數傳遞給作用域就可以達到此目的。作用域參數要放在$query 參數之後:
<?php namespace App; use Illuminate\Database\Eloquent\Model; class User extends Model{ /** * 将查询作用域限制为仅包含给定类型的用户。 * * @param \Illuminate\Database\Eloquent\Builder $query * @param mixed $type * @return \Illuminate\Database\Eloquent\Builder */ public function scopeOfType($query, $type) { return $query->where('type', $type); } }
$users = App\User::ofType('admin')->get();
if ($post->is($anotherPost)) { // }##################
事件
Eloquent 模型觸發幾個事件,讓你掛接到模型生命週期的下列節點: retrieved
、 creating
、 created
、 updating
、 updated
、 saving
、 saved
、 deleting
、 deleted
、 restoring
和restored
。事件允許你每當特定模型保存或更新資料庫時執行程式碼。每個事件透過其建構器接受模型實例。
retrieved
事件在現有模型從資料庫中尋找資料時觸發。當新模型每一次儲存時, creating
和 created
事件被觸發。如果資料庫中已經存在模型並且呼叫了 save
方法, updating
/ updated
事件被觸發。在這些情況下, saving
/ saved
事件也被觸發。
{note} 透過 Eloquent 進行批次更新時,被更新模型的
saved
和updated
事件不會被觸發。這是因為批量更新時,並沒有真的獲取模型。
首先,在Eloquent 模型上定義一個$dispatchesEvents
屬性,將Eloquent 模型生命週期的幾個節點對應到你自己的event 類別:
<?php namespace App; use App\Events\UserSaved; use App\Events\UserDeleted; use Illuminate\Notifications\Notifiable; use Illuminate\Foundation\Auth\User as Authenticatable; class User extends Authenticatable{ use Notifiable; /** * 为模型事件。 * * @var array */ protected $dispatchesEvents = [ 'saved' => UserSaved::class, 'deleted' => UserDeleted::class, ]; }
定義並且映射了Eloquent 事件,就可以使用event 監聽器listeners 處理這些事件了。
觀察者
#定義觀察者
如果在一個模型上監聽了多個事件,可以使用觀察者將這些監聽器組織到一個單獨的類別中。觀察者類別的方法名稱會對應到你希望監聽的 Eloquent 事件。這些方法都以模型作為其唯一參數。 make:observer
Artisan 指令可以快速建立新的觀察者類別:
php artisan make:observer UserObserver --model=User
此指令將在 App/Observers
資料夾放置新的觀察者類別。如果這個目錄不存在,Artisan 會為你創建。使用以下方式開啟觀察者:
<?php namespace App\Observers; use App\User; class UserObserver{ /** * 处理 User 「新建」事件。 * * @param \App\User $user * @return void */ public function created(User $user) { // } /** * 处理 User 「更新」 事件。 * * @param \App\User $user * @return void */ public function updated(User $user) { // } /** * 处理 User 「删除」 事件。 * * @param \App\User $user * @return void */ public function deleted(User $user) { // } }
在你希望觀察的模型上使用 observe
方法註冊觀察者。也可以在服務提供者的 boot
方法註冊觀察者。以下是在 AppServiceProvider
中註冊觀察者的範例:
<?php namespace App\Providers; use App\User;use App\Observers\UserObserver; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider{ /** * 启动应用服务。 * * @return void */ public function boot() { User::observe(UserObserver::class); } /** * 注册服务提供者。 * * @return void */ public function register() { // } }