Laravel 的 Eloquent ORM 提供了漂亮、簡(jiǎn)潔的 ActiveRecord 實(shí)現(xiàn)來(lái)和數(shù)據(jù)庫(kù)的互動(dòng)。 每個(gè)數(shù)據(jù)庫(kù)表會(huì)和一個(gè)對(duì)應(yīng)的「模型」互動(dòng)。
在開(kāi)始之前,記得把 config/database.php
里的數(shù)據(jù)庫(kù)連接配置好。
我們先從建立一個(gè) Eloquent 模型開(kāi)始。模型通常放在 app
目錄下,但是您可以將它們放在任何地方,只要能通過(guò) composer.json 自動(dòng)載入。所有的 Eloquent 模型都繼承于 Illuminate\Database\Eloquent\Model
。
class User extends Model {}
你也可以通過(guò) make:model
命令自動(dòng)生成 Eloquent 模型:
php artisan make:model User
注意我們并沒(méi)有告訴 Eloquent User 模型會(huì)使用哪個(gè)數(shù)據(jù)庫(kù)表。若沒(méi)有特別指定,系統(tǒng)會(huì)默認(rèn)自動(dòng)對(duì)應(yīng)名稱為「類名稱的小寫復(fù)數(shù)形態(tài)」的數(shù)據(jù)庫(kù)表。所以,在上面的例子中, Eloquent 會(huì)假設(shè) User
模型將把數(shù)據(jù)存在 users
數(shù)據(jù)庫(kù)表。您也可以在類中定義 table
屬性自定義要對(duì)應(yīng)的數(shù)據(jù)庫(kù)表。
class User extends Model { protected $table = 'my_users';}
注意: Eloquent 也會(huì)假設(shè)每個(gè)數(shù)據(jù)庫(kù)表都有一個(gè)字段名稱為
id
的主鍵。您可以在類里定義primaryKey
屬性來(lái)重寫。同樣的,您也可以定義connection
屬性,指定模型連接到指定的數(shù)據(jù)庫(kù)連接。
定義好模型之后,您就可以從數(shù)據(jù)庫(kù)表新增及獲取數(shù)據(jù)了。注意在默認(rèn)情況下,在數(shù)據(jù)庫(kù)表里需要有 updated_at
和 created_at
兩個(gè)字段。如果您不想設(shè)定或自動(dòng)更新這兩個(gè)字段,則將類里的 $timestamps
屬性設(shè)為 false即可。
$users = User::all();
$user = User::find(1);var_dump($user->name);
提示: 所有查詢構(gòu)造器里的方法,查詢 Eloquent 模型時(shí)也可以使用。
有時(shí), 您可能想要在找不到模型數(shù)據(jù)時(shí)拋出異常,通過(guò) firstOrFail
方法。
$model = User::findOrFail(1);$model = User::where('votes', '>', 100)->firstOrFail();
Doing this will let you catch the exception so you can log and display an error page as necessary. To catch the ModelNotFoundException
, add some logic to your app/Exceptions/Handler.php
file.
use Illuminate\Database\Eloquent\ModelNotFoundException;class Handler extends ExceptionHandler { public function render($request, Exception $e) { if ($e instanceof ModelNotFoundException) { // Custom logic for model not found... } return parent::render($request, $e); }}
$users = User::where('votes', '>', 100)->take(10)->get();foreach ($users as $user){ var_dump($user->name);}
當(dāng)然,您也可以使用查詢構(gòu)造器的聚合查詢方法。
$count = User::where('votes', '>', 100)->count();
如果沒(méi)辦法使用流暢接口產(chǎn)生出查詢語(yǔ)句,也可以使用 whereRaw
方法:
$users = User::whereRaw('age > ? and votes = 100', [25])->get();
如果您要處理非常多(數(shù)千條)Eloquent 查詢結(jié)果,使用 chunk
方法可以讓您順利工作而不會(huì)消耗大量?jī)?nèi)存:
User::chunk(200, function($users){ foreach ($users as $user) { // }});
傳到方法里的第一個(gè)參數(shù)表示每次「拆分」要取出的數(shù)據(jù)數(shù)量。第二個(gè)參數(shù)的閉合函數(shù)會(huì)在每次取出數(shù)據(jù)時(shí)被調(diào)用。
您也可以指定在執(zhí)行 Eloquent 查詢時(shí)要使用哪個(gè)數(shù)據(jù)庫(kù)連接。只要使用 on
方法:
$user = User::on('connection-name')->find(1);
如果您在使用 讀取 / 寫入連接, 您可以通過(guò)如下命令來(lái)強(qiáng)制查詢使用 寫入
連接:
$user = User::onWriteConnection()->find(1);
在建立一個(gè)新的模型時(shí),您把屬性以數(shù)組的方式傳入模型的構(gòu)造方法,這些屬性值會(huì)經(jīng)由批量賦值存成模型數(shù)據(jù)。這一點(diǎn)非常方便,然而,若盲目地將用戶輸入存到模型時(shí),可能會(huì)造成嚴(yán)重的安全隱患。如果盲目的存入用戶輸入,用戶可以隨意的修改任何以及所有模型的屬性?;谶@個(gè)理由,所有的 Eloquent 模型默認(rèn)會(huì)阻止批量賦值 。
我們以在模型里設(shè)定 fillable
或 guarded
屬性作為開(kāi)始。
Fillable
屬性fillable
屬性指定了哪些字段支持批量賦值 ??梢栽O(shè)定在類的屬性里或是實(shí)例化后設(shè)定。
class User extends Model { protected $fillable = ['first_name', 'last_name', 'email'];}
在上面的例子里,只有三個(gè)屬性允許批量賦值。
Guarded
屬性guarded
與 fillable
相反,是作為「黑名單」而不是「白名單」:
class User extends Model { protected $guarded = ['id', 'password'];}
注意: 使用
guarded
時(shí),Input::get()
或任何用戶可以控制的未過(guò)濾數(shù)據(jù),永遠(yuǎn)不應(yīng)該傳入save
或update
方法,因?yàn)闆](méi)有在「黑名單」內(nèi)的字段可能被更新。
上面的例子中, id
和 password
屬性不會(huì)被批量賦值,而所有其他的屬性則允許批量賦值。您也可以使用 guard 屬性阻止所有屬性被批量賦值:
protected $guarded = ['*'];
要從模型新增一條數(shù)據(jù)到數(shù)據(jù)庫(kù),只要建立一個(gè)模型實(shí)例并調(diào)用 save
方法即可。
$user = new User;$user->name = 'John';$user->save();
注意: 通常 Eloquent 模型主鍵值會(huì)自動(dòng)遞增。但是您若想自定義主鍵,將
incrementing
屬性設(shè)成 false 。
也可以使用 create
方法存入新的模型數(shù)據(jù),新增完后會(huì)返回新增的模型實(shí)例。但是在新增前,需要先在模型類里設(shè)定好 fillable
或 guarded
屬性,因?yàn)?Eloquent 默認(rèn)會(huì)防止批量賦值。
在新模型數(shù)據(jù)被儲(chǔ)存或新增后,若模型有自動(dòng)遞增主鍵,可以從對(duì)象取得 id
屬性值:
$insertedId = $user->id;
class User extends Model { protected $guarded = ['id', 'account_id'];}
// 在數(shù)據(jù)庫(kù)中建立一個(gè)新的用戶...$user = User::create(['name' => 'John']);// 以屬性找用戶,若沒(méi)有則新增并取得新的實(shí)例...$user = User::firstOrCreate(['name' => 'John']);// 以屬性找用戶,若沒(méi)有則建立新的實(shí)例...$user = User::firstOrNew(['name' => 'John']);
要更新模型,可以取出它,更改屬性值,然后使用 save
方法:
$user = User::find(1);$user->email = 'john@foo.com';$user->save();
有時(shí)您可能不只想要儲(chǔ)存模型本身,也想要儲(chǔ)存關(guān)聯(lián)的數(shù)據(jù)。您可以使用 push
方法達(dá)到目的:
$user->push();
您可以結(jié)合查詢語(yǔ)句,批次更新模型:
$affectedRows = User::where('votes', '>', 100)->update(['status' => 2]);
**注意: ** 若使用 Eloquent 查詢構(gòu)造器批次更新模型,則不會(huì)觸發(fā)模型事件。
要?jiǎng)h除模型,只要使用實(shí)例調(diào)用 delete
方法:
$user = User::find(1);$user->delete();
User::destroy(1);User::destroy([1, 2, 3]);User::destroy(1, 2, 3);
當(dāng)然,您也可以結(jié)合查詢語(yǔ)句批次刪除模型:
$affectedRows = User::where('votes', '>', 100)->delete();
如果您只想要更新模型的時(shí)間戳,您可以使用 touch
方法:
$user->touch();
通過(guò)軟刪除方式刪除了一個(gè)模型后,模型中的數(shù)據(jù)并不是真的從數(shù)據(jù)庫(kù)被移除。而是會(huì)設(shè)定 deleted_at
時(shí)間戳。要讓模型使用軟刪除功能,只要在模型類里加入 SoftDeletingTrait
即可:
use Illuminate\Database\Eloquent\SoftDeletes;class User extends Model { use SoftDeletes; protected $dates = ['deleted_at'];}
要加入 deleted_at
字段到數(shù)據(jù)庫(kù)表,可以在遷移文件里使用 softDeletes
方法:
$table->softDeletes();
現(xiàn)在當(dāng)您使用模型調(diào)用 delete
方法時(shí), deleted_at
字段會(huì)被更新成現(xiàn)在的時(shí)間戳。在查詢使用軟刪除功能的模型時(shí),被「刪除」的模型數(shù)據(jù)不會(huì)出現(xiàn)在查詢結(jié)果里。
要強(qiáng)制讓已被軟刪除的模型數(shù)據(jù)出現(xiàn)在查詢結(jié)果里,在查詢時(shí)使用 withTrashed
方法:
$users = User::withTrashed()->where('account_id', 1)->get();
withTrashed
也可以用在關(guān)聯(lián)查詢:
$user->posts()->withTrashed()->get();
如果您只想查詢被軟刪除的模型數(shù)據(jù),可以使用 onlyTrashed
方法:
$users = User::onlyTrashed()->where('account_id', 1)->get();
要把被軟刪除的模型數(shù)據(jù)恢復(fù),使用 restore
方法:
$user->restore();
您也可以結(jié)合查詢語(yǔ)句使用 restore
:
User::withTrashed()->where('account_id', 1)->restore();
如同 withTrashed
, restore
方法也可以用在關(guān)聯(lián)對(duì)象:
$user->posts()->restore();
如果想要真的從模型數(shù)據(jù)庫(kù)刪除,使用 forceDelete
方法:
$user->forceDelete();
forceDelete
方法也可以用在關(guān)聯(lián)對(duì)象:
$user->posts()->forceDelete();
要確認(rèn)模型是否被軟刪除了,可以使用 trashed
方法:
if ($user->trashed()){ //}
默認(rèn) Eloquent 會(huì)自動(dòng)維護(hù)數(shù)據(jù)庫(kù)表的 created_at
和 updated_at
字段。只要把這兩個(gè)「時(shí)間戳」字段加到數(shù)據(jù)庫(kù)表, Eloquent 就會(huì)處理剩下的工作。如果不想讓 Eloquent 自動(dòng)維護(hù)這些字段,把下面的屬性加到模型類里:
class User extends Model { protected $table = 'users'; public $timestamps = false;}
如果想要自定義時(shí)間戳格式,可以在模型類里重寫 getDateFormat
方法:
class User extends Model { protected function getDateFormat() { return 'U'; }}
范圍查詢可以讓您輕松的重復(fù)利用模型的查詢邏輯。要設(shè)定范圍查詢,只要定義有 scope
前綴的模型方法:
class User extends Model { public function scopePopular($query) { return $query->where('votes', '>', 100); } public function scopeWomen($query) { return $query->whereGender('W'); }}
$users = User::popular()->women()->orderBy('created_at')->get();
有時(shí)您可能想要定義可接受參數(shù)的范圍查詢方法。只要把參數(shù)加到方法里:
class User extends Model { public function scopeOfType($query, $type) { return $query->whereType($type); }}
然后把參數(shù)值傳到范圍查詢方法調(diào)用里:
$users = User::ofType('member')->get();
有時(shí)您可能希望定義一個(gè) scope 可以用于模型的所有查詢中。本質(zhì)上,這也是 Eloquent 的"軟刪除"功能的實(shí)現(xiàn)原理。Global scopes 是通過(guò) PHP traits 的組合以及實(shí)現(xiàn) Illuminate\Database\Eloquent\ScopeInterface
接口來(lái)定義的。
首先,我們需要定義一個(gè) trait。 這里我們用 Laravel 的 SoftDeletes
舉例:
trait SoftDeletes { /** * Boot the soft deleting trait for a model. * * @return void */ public static function bootSoftDeletes() { static::addGlobalScope(new SoftDeletingScope); }}
如果一個(gè) Eloquent 模型引入了一個(gè) trait ,而這個(gè) trait 中帶有符合 bootNameOfTrait
慣例命名的方法 ,那么這個(gè)方法會(huì)在 Eloquent 模型啟動(dòng)的時(shí)候調(diào)用, 您可以在此時(shí)注冊(cè) global scope ,或者做一些其他您想要的操作。定義的 scope 必須實(shí)現(xiàn) ScopeInterface
接口,這個(gè)接口提供了兩個(gè)方法:apply
和 remove
。
apply
方法接受一個(gè) Illuminate\Database\Eloquent\Builder
查詢構(gòu)造器對(duì)象以及它所應(yīng)用的 Model
,用來(lái)添加這個(gè) scope 所需的額外的 where
子句。而remove
方法同樣接受一個(gè) Builder
對(duì)象以及 Model
,用來(lái)反向的執(zhí)行 apply
操作。也就是說(shuō),remove
方法應(yīng)該移除已經(jīng)添加的 where
子句 (或者其他查詢子句)。因此,我們的 SoftDeletingScope
的方法應(yīng)該如下:
/** * Apply the scope to a given Eloquent query builder. * * @param \Illuminate\Database\Eloquent\Builder $builder * @param \Illuminate\Database\Eloquent\Model $model * @return void */public function apply(Builder $builder, Model $model){ $builder->whereNull($model->getQualifiedDeletedAtColumn()); $this->extend($builder);}/** * Remove the scope from the given Eloquent query builder. * * @param \Illuminate\Database\Eloquent\Builder $builder * @param \Illuminate\Database\Eloquent\Model $model * @return void */public function remove(Builder $builder, Model $model){ $column = $model->getQualifiedDeletedAtColumn(); $query = $builder->getQuery(); foreach ((array) $query->wheres as $key => $where) { // If the where clause is a soft delete date constraint, we will remove it from // the query and reset the keys on the wheres. This allows this developer to // include deleted model in a relationship result set that is lazy loaded. if ($this->isSoftDeleteConstraint($where, $column)) { unset($query->wheres[$key]); $query->wheres = array_values($query->wheres); } }}
當(dāng)然,您的數(shù)據(jù)庫(kù)表很可能跟另一張表相關(guān)聯(lián)。例如,一篇 blog 文章可能有很多評(píng)論,或是一張訂單跟下單客戶相關(guān)聯(lián)。 Eloquent 讓管理和處理這些關(guān)聯(lián)變得很容易。 Laravel 有很多種關(guān)聯(lián)類型:
一對(duì)一關(guān)聯(lián)是很基本的關(guān)聯(lián)。例如一個(gè) User
模型會(huì)對(duì)應(yīng)到一個(gè) Phone
。 在 Eloquent 里可以像下面這樣定義關(guān)聯(lián):
class User extends Model { public function phone() { return $this->hasOne('App\Phone'); }}
傳到 hasOne
方法里的第一個(gè)參數(shù)是關(guān)聯(lián)模型的類名稱。定義好關(guān)聯(lián)之后,就可以使用 Eloquent 的動(dòng)態(tài)屬性取得關(guān)聯(lián)對(duì)象:
$phone = User::find(1)->phone;
SQL 會(huì)執(zhí)行如下語(yǔ)句:
select * from users where id = 1select * from phones where user_id = 1
注意, Eloquent 假設(shè)對(duì)應(yīng)的關(guān)聯(lián)模型數(shù)據(jù)庫(kù)表里,外鍵名稱是基于模型名稱。在這個(gè)例子里,默認(rèn) Phone
模型數(shù)據(jù)庫(kù)表會(huì)以 user_id
作為外鍵。如果想要更改這個(gè)默認(rèn),可以傳入第二個(gè)參數(shù)到 hasOne
方法里。更進(jìn)一步,您可以傳入第三個(gè)參數(shù),指定關(guān)聯(lián)的外鍵要對(duì)應(yīng)到本身的哪個(gè)字段:
return $this->hasOne('App\Phone', 'foreign_key');return $this->hasOne('App\Phone', 'foreign_key', 'local_key');
要在 Phone
模型里定義相對(duì)的關(guān)聯(lián),可以使用 belongsTo
方法:
class Phone extends Model { public function user() { return $this->belongsTo('App\User'); }}
在上面的例子里, Eloquent 默認(rèn)會(huì)使用 phones
數(shù)據(jù)庫(kù)表的 user_id
字段查詢關(guān)聯(lián)。如果想要自己指定外鍵字段,可以在 belongsTo
方法里傳入第二個(gè)參數(shù):
class Phone extends Model { public function user() { return $this->belongsTo('App\User', 'local_key'); }}
除此之外,也可以傳入第三個(gè)參數(shù)指定要參照上層數(shù)據(jù)庫(kù)表的哪個(gè)字段:
class Phone extends Model { public function user() { return $this->belongsTo('App\User', 'local_key', 'parent_key'); }}
一對(duì)多關(guān)聯(lián)的例子如,一篇 Blog 文章可能「有很多」評(píng)論??梢韵襁@樣定義關(guān)聯(lián):
class Post extends Model { public function comments() { return $this->hasMany('App\Comment'); }}
現(xiàn)在可以經(jīng)由動(dòng)態(tài)屬性取得文章的評(píng)論:
$comments = Post::find(1)->comments;
如果需要增加更多條件限制,可以在調(diào)用 comments
方法后面通過(guò)鏈?zhǔn)讲樵儣l件方法:
$comments = Post::find(1)->comments()->where('title', '=', 'foo')->first();
同樣的,您可以傳入第二個(gè)參數(shù)到 hasMany
方法更改默認(rèn)的外鍵名稱。以及,如同 hasOne
關(guān)聯(lián),可以指定本身的對(duì)應(yīng)字段:
return $this->hasMany('App\Comment', 'foreign_key');return $this->hasMany('App\Comment', 'foreign_key', 'local_key');
要在 Comment
模型定義相對(duì)應(yīng)的關(guān)聯(lián),可使用 belongsTo
方法:
class Comment extends Model { public function post() { return $this->belongsTo('App\Post'); }}
多對(duì)多關(guān)聯(lián)更為復(fù)雜。這種關(guān)聯(lián)的例子如,一個(gè)用戶( user )可能用有很多身份( role ),而一種身份可能很多用戶都有。例如很多用戶都是「管理者」。多對(duì)多關(guān)聯(lián)需要用到三個(gè)數(shù)據(jù)庫(kù)表: users
, roles
,和 role_user
。 role_user
樞紐表命名是以相關(guān)聯(lián)的兩個(gè)模型數(shù)據(jù)庫(kù)表,依照字母順序命名,樞紐表里面應(yīng)該要有 user_id
和 role_id
字段。
可以使用 belongsToMany
方法定義多對(duì)多關(guān)系:
class User extends Model { public function roles() { return $this->belongsToMany('App\Role'); }}
現(xiàn)在我們可以從 User
模型取得 roles:
$roles = User::find(1)->roles;
如果不想使用默認(rèn)的樞紐數(shù)據(jù)庫(kù)表命名方式,可以傳遞數(shù)據(jù)庫(kù)表名稱作為 belongsToMany
方法的第二個(gè)參數(shù):
return $this->belongsToMany('App\Role', 'user_roles');
也可以更改默認(rèn)的關(guān)聯(lián)字段名稱:
return $this->belongsToMany('App\Role', 'user_roles', 'user_id', 'foo_id');
當(dāng)然,也可以在 Role
模型定義相對(duì)的關(guān)聯(lián):
class Role extends Model { public function users() { return $this->belongsToMany('App\User'); }}
「遠(yuǎn)層一對(duì)多關(guān)聯(lián)」提供了方便簡(jiǎn)短的方法,可以經(jīng)由多層間的關(guān)聯(lián)取得遠(yuǎn)層的關(guān)聯(lián)。例如,一個(gè) Country
模型可能通過(guò) Users
關(guān)聯(lián)到很多 Posts
模型。 數(shù)據(jù)庫(kù)表間的關(guān)系可能看起來(lái)如下:
countries id - integer name - string users id - integer country_id - integer name - string posts id - integer user_id - integer title - string
雖然 posts
數(shù)據(jù)庫(kù)表本身沒(méi)有 country_id
字段,但 hasManyThrough
方法讓我們可以使用 $country->posts
取得 country 的 posts。我們可以定義以下關(guān)聯(lián):
class Country extends Model { public function posts() { return $this->hasManyThrough('App\Post', 'App\User'); }}
如果想要手動(dòng)指定關(guān)聯(lián)的字段名稱,可以傳入第三和第四個(gè)參數(shù)到方法里:
class Country extends Model { public function posts() { return $this->hasManyThrough('App\Post', 'App\User', 'country_id', 'user_id'); }}
多態(tài)關(guān)聯(lián)可以用一個(gè)簡(jiǎn)單的關(guān)聯(lián)方法,就讓一個(gè)模型同時(shí)關(guān)聯(lián)多個(gè)模型。例如,您可能想讓 photo 模型同時(shí)和一個(gè) staff 或 order 模型關(guān)聯(lián)??梢远x關(guān)聯(lián)如下:
class Photo extends Model { public function imageable() { return $this->morphTo(); }}class Staff extends Model { public function photos() { return $this->morphMany('App\Photo', 'imageable'); }}class Order extends Model { public function photos() { return $this->morphMany('App\Photo', 'imageable'); }}
現(xiàn)在我們可以從 staff 或 order 模型取得多態(tài)關(guān)聯(lián)對(duì)象:
$staff = Staff::find(1);foreach ($staff->photos as $photo){ //}
然而,多態(tài)關(guān)聯(lián)真正神奇的地方,在于要從 Photo
模型取得 staff 或 order 對(duì)象時(shí):
$photo = Photo::find(1);$imageable = $photo->imageable;
Photo 模型里的 imageable
關(guān)聯(lián)會(huì)返回 Staff
或 Order
實(shí)例,取決于這是哪一種模型擁有的照片。
為了理解多態(tài)關(guān)聯(lián)的運(yùn)作機(jī)制,來(lái)看看它們的數(shù)據(jù)庫(kù)表結(jié)構(gòu):
staff id - integer name - string orders id - integer price - integer photos id - integer path - string imageable_id - integer imageable_type - string
要注意的重點(diǎn)是 photos
數(shù)據(jù)庫(kù)表的 imageable_id
和 imageable_type
。在上面的例子里, ID 字段會(huì)包含 staff 或 order 的 ID,而 type 是擁有者的模型類名稱。這就是讓 ORM 在取得 imageable
關(guān)聯(lián)對(duì)象時(shí),決定要哪一種模型對(duì)象的機(jī)制。
除了一般的多態(tài)關(guān)聯(lián),也可以使用多對(duì)多的多態(tài)關(guān)聯(lián)。例如,Blog 的 Post
和 Video
模型可以共用多態(tài)的 Tag
關(guān)聯(lián)模型。首先,來(lái)看看數(shù)據(jù)庫(kù)表結(jié)構(gòu):
posts id - integer name - string videos id - integer name - string tags id - integer name - string taggables tag_id - integer taggable_id - integer taggable_type - string
現(xiàn)在,我們準(zhǔn)備好設(shè)定模型關(guān)聯(lián)了。 Post
和 Video
模型都可以經(jīng)由 tags
方法建立 morphToMany
關(guān)聯(lián):
class Post extends Model { public function tags() { return $this->morphToMany('App\Tag', 'taggable'); }}
在 Tag
模型里針對(duì)每一種關(guān)聯(lián)建立一個(gè)方法:
class Tag extends Model { public function posts() { return $this->morphedByMany('App\Post', 'taggable'); } public function videos() { return $this->morphedByMany('App\Video', 'taggable'); }}
在取得模型數(shù)據(jù)時(shí),您可能想要以關(guān)聯(lián)模型作為查詢限制。例如,您可能想要取得所有「至少有一篇評(píng)論」的Blog 文章??梢允褂?has
方法達(dá)成目的:
$posts = Post::has('comments')->get();
也可以指定運(yùn)算符和數(shù)量:
$posts = Post::has('comments', '>=', 3)->get();
也可以使用"點(diǎn)號(hào)"的形式來(lái)獲取嵌套的 has
聲明:
$posts = Post::has('comments.votes')->get();
如果想要更進(jìn)階的用法,可以使用 whereHas
和 orWhereHas
方法,在 has
查詢里設(shè)置 "where" 條件 :
$posts = Post::whereHas('comments', function($q){ $q->where('content', 'like', 'foo%');})->get();
Eloquent 可以經(jīng)由動(dòng)態(tài)屬性取得關(guān)聯(lián)對(duì)象。 Eloquent 會(huì)自動(dòng)進(jìn)行關(guān)聯(lián)查詢,而且會(huì)很聰明的知道應(yīng)該要使用 get
(用在一對(duì)多關(guān)聯(lián))或是 first
(用在一對(duì)一關(guān)聯(lián))方法。可以經(jīng)由和「關(guān)聯(lián)方法名稱相同」的動(dòng)態(tài)屬性取得對(duì)象。例如,如下面的模型對(duì)象 $phone
:
class Phone extends Model { public function user() { return $this->belongsTo('App\User'); }}$phone = Phone::find(1);
您可以不用像下面這樣打印用戶的 email :
echo $phone->user()->first()->email;
而可以簡(jiǎn)寫如下:
echo $phone->user->email;
注意: 若取得的是許多關(guān)聯(lián)對(duì)象,會(huì)返回
Illuminate\Database\Eloquent\Collection
對(duì)象。
預(yù)載入是用來(lái)減少 N + 1 查詢問(wèn)題。例如,一個(gè) Book
模型數(shù)據(jù)會(huì)關(guān)聯(lián)到一個(gè) Author
。關(guān)聯(lián)會(huì)像下面這樣定義:
class Book extends Model { public function author() { return $this->belongsTo('App\Author'); }}
現(xiàn)在考慮下面的代碼:
foreach (Book::all() as $book){ echo $book->author->name;}
上面的循環(huán)會(huì)執(zhí)行一次查詢?nèi)』厮袛?shù)據(jù)庫(kù)表上的書籍,然而每本書籍都會(huì)執(zhí)行一次查詢?nèi)〉米髡?。所以若我們?25 本書,就會(huì)進(jìn)行 26次查詢。
很幸運(yùn)地,我們可以使用預(yù)載入大量減少查詢次數(shù)。使用 with
方法指定想要預(yù)載入的關(guān)聯(lián)對(duì)象:
foreach (Book::with('author')->get() as $book){ echo $book->author->name;}
現(xiàn)在,上面的循環(huán)總共只會(huì)執(zhí)行兩次查詢:
select * from books select * from authors where id in (1, 2, 3, 4, 5, ...)
使用預(yù)載入可以大大提高程序的性能。
當(dāng)然,也可以同時(shí)載入多種關(guān)聯(lián):
$books = Book::with('author', 'publisher')->get();
甚至可以預(yù)載入巢狀關(guān)聯(lián):
$books = Book::with('author.contacts')->get();
上面的例子中, author
關(guān)聯(lián)會(huì)被預(yù)載入, author 的 contacts
關(guān)聯(lián)也會(huì)被預(yù)載入。
有時(shí)您可能想要預(yù)載入關(guān)聯(lián),同時(shí)也想要指定載入時(shí)的查詢限制。下面有一個(gè)例子:
$users = User::with(['posts' => function($query){ $query->where('title', 'like', '%first%');}])->get();
上面的例子里,我們預(yù)載入了 user 的 posts 關(guān)聯(lián),并限制條件為 post 的 title 字段需包含 "first" 。
當(dāng)然,預(yù)載入的閉合函數(shù)里不一定只能加上條件限制,也可以加上排序:
$users = User::with(['posts' => function($query){ $query->orderBy('created_at', 'desc');}])->get();
也可以直接從模型的 collection 預(yù)載入關(guān)聯(lián)對(duì)象。這對(duì)于需要根據(jù)情況決定是否載入關(guān)聯(lián)對(duì)象時(shí),或是跟緩存一起使用時(shí)很有用。
$books = Book::all();$books->load('author', 'publisher');
你可以傳入一個(gè)閉包來(lái)對(duì)查詢構(gòu)建器進(jìn)行條件限制:
$books->load(['author' => function($query){ $query->orderBy('published_date', 'asc');}]);
您常常會(huì)需要加入新的關(guān)聯(lián)模型。例如新增一個(gè) comment 到 post 。除了手動(dòng)設(shè)定模型的 post_id
外鍵,也可以從上層的 Post
模型新增關(guān)聯(lián)的 comment :
$comment = new Comment(['message' => 'A new comment.']);$post = Post::find(1);$comment = $post->comments()->save($comment);
上面的例子里,新增的 comment 模型中 post_id
字段會(huì)被自動(dòng)設(shè)定。
如果想要同時(shí)新增很多關(guān)聯(lián)模型:
$comments = [ new Comment(['message' => 'A new comment.']), new Comment(['message' => 'Another comment.']), new Comment(['message' => 'The latest comment.'])];$post = Post::find(1);$post->comments()->saveMany($comments);
要更新 belongsTo
關(guān)聯(lián)時(shí),可以使用 associate
方法。這個(gè)方法會(huì)設(shè)定子模型的外鍵:
$account = Account::find(10);$user->account()->associate($account);$user->save();
您也可以新增多對(duì)多的關(guān)聯(lián)模型。讓我們繼續(xù)使用 User
和 Role
模型作為例子。我們可以使用 attach
方法簡(jiǎn)單地把 roles 附加給一個(gè) user:
$user = User::find(1);$user->roles()->attach(1);
也可以傳入要存在樞紐表中的屬性數(shù)組:
$user->roles()->attach(1, ['expires' => $expires]);
當(dāng)然,有 attach
方法就會(huì)有相反的 detach
方法:
$user->roles()->detach(1);
attach
和 detach
都可以接受ID數(shù)組作為參數(shù):
$user = User::find(1);$user->roles()->detach([1, 2, 3]);$user->roles()->attach([1 => ['attribute1' => 'value1'], 2, 3]);
您也可以使用 sync
方法附加關(guān)聯(lián)模型。 sync
方法會(huì)把根據(jù) ID 數(shù)組把關(guān)聯(lián)存到樞紐表。附加完關(guān)聯(lián)后,樞紐表里的模型只會(huì)關(guān)聯(lián)到 ID 數(shù)組里的 id :
$user->roles()->sync([1, 2, 3]);
也可以在把每個(gè) ID 加入樞紐表時(shí),加入其他字段的數(shù)據(jù):
$user->roles()->sync([1 => ['expires' => true]]);
有時(shí)您可能想要使用一個(gè)命令,在建立新模型數(shù)據(jù)的同時(shí)附加關(guān)聯(lián)。可以使用 save
方法達(dá)成目的:
$role = new Role(['name' => 'Editor']);User::find(1)->roles()->save($role);
上面的例子里,新的 Role
模型對(duì)象會(huì)在儲(chǔ)存的同時(shí)關(guān)聯(lián)到 user
模型。也可以傳入屬性數(shù)組把數(shù)據(jù)加到關(guān)聯(lián)數(shù)據(jù)庫(kù)表:
User::find(1)->roles()->save($role, ['expires' => $expires]);
當(dāng)模型 belongsTo
另一個(gè)模型時(shí),比方說(shuō)一個(gè) Comment
屬于一個(gè) Post
,如果能在子模型被更新時(shí),更新上層的時(shí)間戳,這將會(huì)很有用。例如,當(dāng) Comment
模型更新時(shí),您可能想要能夠同時(shí)自動(dòng)更新 Post
的 updated_at
時(shí)間戳。 Eloquent 讓事情變得很簡(jiǎn)單。只要在子關(guān)聯(lián)的類里,把關(guān)聯(lián)方法名稱加入 touches
屬性即可:
class Comment extends Model { protected $touches = ['post']; public function post() { return $this->belongsTo('App\Post'); }}
現(xiàn)在,當(dāng)您更新 Comment
時(shí),對(duì)應(yīng)的 Post
會(huì)自動(dòng)更新 updated_at
字段:
$comment = Comment::find(1);$comment->text = 'Edit to this comment!';$comment->save();
如您所知,要操作多對(duì)多關(guān)聯(lián)需要一個(gè)中間的數(shù)據(jù)庫(kù)表。 Eloquent 提供了一些有用的方法可以和這張表互動(dòng)。例如,假設(shè) User
對(duì)象關(guān)聯(lián)到很多 Role
對(duì)象。取出這些關(guān)聯(lián)對(duì)象時(shí),我們可以在關(guān)聯(lián)模型上取得 pivot
數(shù)據(jù)庫(kù)表的數(shù)據(jù):
$user = User::find(1);foreach ($user->roles as $role){ echo $role->pivot->created_at;}
注意我們?nèi)〕龅拿總€(gè) Role
模型對(duì)象會(huì)自動(dòng)給一個(gè) pivot
屬性。這屬性包含了樞紐表的模型數(shù)據(jù),可以像一般的 Eloquent 模型一樣使用。
默認(rèn) pivot
對(duì)象只會(huì)有關(guān)聯(lián)鍵的屬性。如果您想讓 pivot 可以包含其他樞紐表的字段,可以在定義關(guān)聯(lián)方法時(shí)指定那些字段:
return $this->belongsToMany('App\Role')->withPivot('foo', 'bar');
現(xiàn)在可以在 Role
模型的 pivot
對(duì)象上取得 foo
和 bar
屬性了。
如果您想要可以自動(dòng)維護(hù)樞紐表的 created_at
和 updated_at
時(shí)間戳,在定義關(guān)聯(lián)方法時(shí)加上 withTimestamps
方法:
return $this->belongsToMany('App\Role')->withTimestamps();
要?jiǎng)h除模型在樞紐表的所有關(guān)聯(lián)數(shù)據(jù),可以使用 detach
方法:
User::find(1)->roles()->detach();
注意,如上的操作不會(huì)移除 roles
數(shù)據(jù)庫(kù)表里面的數(shù)據(jù),只會(huì)移除樞紐表里的關(guān)聯(lián)數(shù)據(jù)。
有時(shí)您只想更新樞紐表的數(shù)據(jù),而沒(méi)有要移除關(guān)聯(lián)。如果您想更新樞紐表,可以像下面的例子使用 updateExistingPivot
方法:
User::find(1)->roles()->updateExistingPivot($roleId, $attributes);
Laravel 允許您自定義樞紐模型。要自定義模型,首先要建立一個(gè)繼承 Eloquent 的「基本」模型類。在其他的 Eloquent 模型繼承這個(gè)自定義的基本類,而不是默認(rèn)的 Eloquent 。在基本模型類里,加入下面的方法返回自定義的樞紐模型實(shí)例:
public function newPivot(Model $parent, array $attributes, $table, $exists){ return new YourCustomPivot($parent, $attributes, $table, $exists);}
所有 Eloquent 查詢返回的數(shù)據(jù),如果結(jié)果多于一條,不管是經(jīng)由 get
方法或是 relationship
,都會(huì)轉(zhuǎn)換成集合對(duì)象返回。這個(gè)對(duì)象實(shí)現(xiàn)了 IteratorAggregate
PHP 接口,所以可以像數(shù)組一般進(jìn)行遍歷。而集合對(duì)象本身還擁有很多有用的方法可以操作模型數(shù)據(jù)。
例如,我們可以使用 contains
方法,確認(rèn)結(jié)果數(shù)據(jù)中,是否包含主鍵為特定值的對(duì)象。
$roles = User::find(1)->roles;if ($roles->contains(2)){ //}
集合也可以轉(zhuǎn)換成數(shù)組或 JSON:
$roles = User::find(1)->roles->toArray();$roles = User::find(1)->roles->toJson();
如果集合被轉(zhuǎn)換成字符串類型,會(huì)返回 JSON 格式:
$roles = (string) User::find(1)->roles;
Eloquent 集合里包含了一些有用的方法可以進(jìn)行循環(huán)或是進(jìn)行過(guò)濾:
$roles = $user->roles->each(function($role){ //});
過(guò)濾集合時(shí),回調(diào)函數(shù)的使用方式和 array_filter 里一樣。
$users = $users->filter(function($user){ return $user->isAdmin();});
注意: 如果要在過(guò)濾集合之后轉(zhuǎn)成 JSON,轉(zhuǎn)換之前先調(diào)用
values
方法重設(shè)數(shù)組的鍵值。
$roles = User::find(1)->roles;$roles->each(function($role){ //});
$roles = $roles->sortBy(function($role){ return $role->created_at;});$roles = $roles->sortByDesc(function($role){ return $role->created_at;});
$roles = $roles->sortBy('created_at');$roles = $roles->sortByDesc('created_at');
有時(shí)您可能想要返回自定義的集合對(duì)象,讓您可以在集合類里加入想要的方法??梢栽?Eloquent 模型類里重寫 newCollection
方法:
class User extends Model { public function newCollection(array $models = []) { return new CustomCollection($models); }}
Eloquent 提供了一種便利的方法,可以在獲取或設(shè)定屬性時(shí)進(jìn)行轉(zhuǎn)換。要定義獲取器,只要在模型里加入類似 getFooAttribute
的方法。注意方法名稱應(yīng)該使用駝峰式大小寫命名,而對(duì)應(yīng)的 database 字段名稱是下劃線分隔小寫命名:
class User extends Model { public function getFirstNameAttribute($value) { return ucfirst($value); }}
上面的例子中, first_name 字段設(shè)定了一個(gè)獲取器。注意傳入方法的參數(shù)是原本的字段數(shù)據(jù)。
修改器的定義方式也是類似的:
class User extends Model { public function setFirstNameAttribute($value) { $this->attributes['first_name'] = strtolower($value); }}
默認(rèn) Eloquent 會(huì)把 created_at
和 updated_at
字段屬性轉(zhuǎn)換成 Carbon 實(shí)例,它提供了很多有用的方法,并繼承了 PHP 原生的 DateTime
類。
您可以通過(guò)重寫模型的 getDates
方法,自定義哪個(gè)字段可以被自動(dòng)轉(zhuǎn)換,或甚至完全關(guān)閉這個(gè)轉(zhuǎn)換:
public function getDates(){ return ['created_at'];}
當(dāng)字段是表示日期的時(shí)候,可以將值設(shè)為 UNIX timestamp 、日期字符串( Y-m-d )、 日期時(shí)間( date-time )字符串,當(dāng)然還有 DateTime
或 Carbon
實(shí)例。
要完全關(guān)閉日期轉(zhuǎn)換功能,只要從 getDates
方法返回空數(shù)組即可:
public function getDates(){ return [];}
如果您想要某些屬性始終轉(zhuǎn)換成另一個(gè)數(shù)據(jù)類型, 您可以在模型中增加 casts
屬性。否則,您需要為每個(gè)屬性定義修改器,這樣會(huì)增加更多的時(shí)間開(kāi)銷。這里有一個(gè)使用 casts
屬性的例子:
/** * 需要被轉(zhuǎn)換成基本類型的屬性值。 * * @var array */protected $casts = [ 'is_admin' => 'boolean',];
現(xiàn)在當(dāng)你獲取 is_admin
屬性時(shí)始終會(huì)是布爾類型,甚至在數(shù)據(jù)庫(kù)中存儲(chǔ)的這個(gè)值是一個(gè)整型也會(huì)被轉(zhuǎn)換。其他支持的類型轉(zhuǎn)換值有: integer
, real
, float
, double
, string
, boolean
, object
和 array
。
如果您存儲(chǔ)的值是一個(gè)序列化的 JSON 時(shí),那么 array
類型轉(zhuǎn)換將會(huì)非常有用。比如,您的數(shù)據(jù)表里有一個(gè) TEXT 類型的字段存儲(chǔ)著序列化后的 JSON 數(shù)據(jù), 通過(guò)增加 array
類型轉(zhuǎn)換, 當(dāng)獲取這個(gè)屬性的時(shí)候會(huì)自動(dòng)反序列化成 PHP 的數(shù)組:
/** * 需要被轉(zhuǎn)換成基本類型的屬性值。 * * @var array */protected $casts = [ 'options' => 'array',];
現(xiàn)在,當(dāng)你使用 Eloquent 模型時(shí):
$user = User::find(1);// $options 是一個(gè)數(shù)組...$options = $user->options;// options 會(huì)自動(dòng)序列化成 JSON...$user->options = ['foo' => 'bar'];
Eloquent 模型有很多事件可以觸發(fā),讓您可以在模型操作的生命周期的不同時(shí)間點(diǎn),使用下列方法綁定事件: creating
, created
, updating
, updated
, saving
, saved
, deleting
, deleted
, restoring
, restored
。
當(dāng)一個(gè)對(duì)象初次被儲(chǔ)存到數(shù)據(jù)庫(kù), creating
和 created
事件會(huì)被觸發(fā)。如果不是新對(duì)象而調(diào)用了 save
方法, updating
/ updated
事件會(huì)被觸發(fā)。而兩者的 saving
/ saved
事件都會(huì)被觸發(fā)。
如果 creating
、 updating
、 saving
、 deleting
事件返回 false 的話,就會(huì)取消數(shù)據(jù)庫(kù)操作
User::creating(function($user){ if ( ! $user->isValid()) return false;});
您可以在 EventServiceProvider
中注冊(cè)您的模型事件綁定。比如:
/** * Register any other events for your application. * * @param \Illuminate\Contracts\Events\Dispatcher $events * @return void */public function boot(DispatcherContract $events){ parent::boot($events); User::creating(function($user) { // });}
要整合模型的事件處理,可以注冊(cè)一個(gè)模型觀察者。觀察者類里要設(shè)定對(duì)應(yīng)模型事件的方法。例如,觀察者類里可能有 creating
、 updating
、 saving
方法,還有其他對(duì)應(yīng)模型事件名稱的方法:
例如,一個(gè)模型觀察者類可能看起來(lái)如下:
class UserObserver { public function saving($model) { // } public function saved($model) { // }}
可以使用 observe
方法注冊(cè)一個(gè)觀察者實(shí)例:
User::observe(new UserObserver);
當(dāng)你把一個(gè)模型實(shí)例傳遞給 route
或者 action
方法時(shí),模型的主鍵會(huì)被插入到生成的 URI 中。比如:
Route::get('user/{user}', 'UserController@show');action('UserController@show', [$user]);
在這個(gè)例子中 $user->id
屬性會(huì)被插入到生成的 URL 的 {user}
這個(gè)占位符中。不過(guò),如果你想使用其他的屬性而不是 ID 的話,你可以覆蓋模型的 getRouteKey
方法:
public function getRouteKey(){ return $this->slug;}
當(dāng)構(gòu)建 JSON API 時(shí),您可能常常需要把模型和關(guān)聯(lián)對(duì)象轉(zhuǎn)換成數(shù)組或JSON。所以Eloquent里已經(jīng)包含了這些方法。要把模型和已載入的關(guān)聯(lián)對(duì)象轉(zhuǎn)成數(shù)組,可以使用 toArray
方法:
$user = User::with('roles')->first();return $user->toArray();
注意也可以把整個(gè)的模型集合轉(zhuǎn)換成數(shù)組:
return User::all()->toArray();
要把模型轉(zhuǎn)換成 JSON,可以使用 toJson
方法:
return User::find(1)->toJson();
注意當(dāng)模型或集合被轉(zhuǎn)換成字符串類型時(shí)會(huì)自動(dòng)轉(zhuǎn)換成 JSON 格式,這意味著您可以直接從路由返回 Eloquent 對(duì)象!
Route::get('users', function(){ return User::all();});
有時(shí)您可能想要限制能出現(xiàn)在數(shù)組或 JSON 格式的屬性數(shù)據(jù),比如密碼字段。只要在模型里增加 hidden
屬性即可
class User extends Model { protected $hidden = ['password'];}
注意: 要隱藏關(guān)聯(lián)數(shù)據(jù),要使用關(guān)聯(lián)的方法名稱,而不是動(dòng)態(tài)獲取的屬性名稱。
此外,可以使用 visible
屬性定義白名單:
protected $visible = ['first_name', 'last_name'];
有時(shí)候您可能想要增加不存在數(shù)據(jù)庫(kù)字段的屬性數(shù)據(jù)。這時(shí)候只要定義一個(gè)獲取器即可:
public function getIsAdminAttribute(){ return $this->attributes['admin'] == 'yes';}
定義好獲取器之后,再把對(duì)應(yīng)的屬性名稱加到模型里的 appends
屬性:
protected $appends = ['is_admin'];
把屬性加到 appends
數(shù)組之后,在模型數(shù)據(jù)轉(zhuǎn)換成數(shù)組或 JSON 格式時(shí)就會(huì)有對(duì)應(yīng)的值。在 appends
數(shù)組中定義的值同樣遵循模型中 visible
和 hidden
的設(shè)定。
更多建議: