Eloquent ORM - Laravel - 為網頁藝術家創造的PHP 框架
文章推薦指數: 80 %
所有的Eloquent 模型都繼承 Illuminate\Database\Eloquent\Model 。
定義一個Eloquent 模型. class User extends Model {}. 也 ...
Laravel
文件
Laracasts
News
Forge
Ecosystem
GitHub
Envoyer
Lumen
Spark
Forums
Jobs
Podcast
Slack
Twitter
5.0
Master
5.3
5.2
5.1
5.0
4.2
☰
首頁
文件
Laracasts
News
Forge
Ecosystem
GitHub
Envoyer
Lumen
Spark
Forums
Jobs
Podcast
Slack
Twitter
文件
前言
發行說明
升級導引
貢獻導引
環境設定
安裝
設定
Homestead
基本功能
路由
中介層
控制器
請求
回應
視圖
系統架構
服務提供者
服務容器
Contracts
Facades
請求的生命週期
應用程式結構
系統服務
認證
交易
快取
集合
CommandBus
核心擴展
Elixir
加密
Envoy
錯誤與日誌
事件
檔案系統與雲端儲存
雜湊
輔助方法
在地化
郵件
套件開發
分頁
隊列
Session
模板
單元測試
驗證
資料庫
基本入門
查詢架構器
EloquentORM
結構生成器
遷移與資料填充
Redis
Artisan命令列工具
概覽
開發
前言
發行說明
升級導引
貢獻導引
環境設定
安裝
設定
Homestead
基本功能
路由
中介層
控制器
請求
回應
視圖
系統架構
服務提供者
服務容器
Contracts
Facades
請求的生命週期
應用程式結構
系統服務
認證
交易
快取
集合
CommandBus
核心擴展
Elixir
加密
Envoy
錯誤與日誌
事件
檔案系統與雲端儲存
雜湊
輔助方法
在地化
郵件
套件開發
分頁
隊列
Session
模板
單元測試
驗證
資料庫
基本入門
查詢架構器
EloquentORM
結構生成器
遷移與資料填充
Redis
Artisan命令列工具
概覽
開發
EloquentORM
介紹
基本用法
批量賦值(MassAssignment)
新增、修改、刪除
軟刪除(SoftDeleting)
時間戳
範圍查詢
全域範圍查詢
關聯
關聯查詢
預載入(EagerLoading)
新增關聯模型
更新上層模型時間戳
操作樞紐表
集合
存取器和修改器
日期轉換器
屬性型別轉換
模型事件
模型觀察者
ModelURLGeneration
轉換陣列/JSON
介紹
Laravel的EloquentORM提供了漂亮、簡潔的ActiveRecord實作來和資料庫互動。
每個資料庫表會和一個對應的「模型」互動。
在開始之前,記得把config/database.php裡的資料庫連線設定好。
基本用法
我們先從建立一個Eloquent模型開始。
模型通常放在app目錄下,但是您可以將它們放在任何地方,只要能通過composer.json自動載入。
所有的Eloquent模型都繼承Illuminate\Database\Eloquent\Model。
定義一個Eloquent模型
classUserextendsModel{}
也可以用make:model命令產生Eloquent模型:
phpartisanmake:modelUser
注意我們並沒有告訴Eloquent,User模型會使用哪個資料表。
若沒有特別指定,系統會預設自動對應「snakecase」且複數的資料表名稱。
所以,在上面的例子中,Eloquent會假設User模型將把資料存在users資料表。
您也可以在類別中定義table屬性自定要對應的資料表名稱。
classUserextendsModel{
protected$table='my_users';
}
注意:Eloquent也會假設每個資料表都有一個欄位名稱為id的主鍵。
您可以在類別裡定義primaryKey屬性覆寫這個預設。
同樣的,你也可以定義connection屬性,指定模型需要的資料庫連線。
定義好模型之後,你就可以從資料表新增及取得資料了。
注意預設上,在資料表裡需要有updated_at和created_at兩個欄位。
如果你不想設定或自動更新這兩個欄位,將類別裡的$timestamps屬性設為false。
取出所有資料
$users=User::all();
根據主鍵取出一筆資料
$user=User::find(1);
var_dump($user->name);
提示:所有查詢構造器裡的方法,查詢Eloquent模型時也可以使用。
根據主鍵取出一條資料或拋出異常
有時,你可能想要在找不到模型資料時拋出例外。
要做到這個,你可以使用firstOrFail方法:
$model=User::findOrFail(1);
$model=User::where('votes','>',100)->firstOrFail();
Doingthiswillletyoucatchtheexceptionsoyoucanloganddisplayanerrorpageasnecessary.TocatchtheModelNotFoundException,addsomelogictoyourapp/Exceptions/Handler.phpfile.
useIlluminate\Database\Eloquent\ModelNotFoundException;
classHandlerextendsExceptionHandler{
publicfunctionrender($request,Exception$e)
{
if($einstanceofModelNotFoundException)
{
//Customlogicformodelnotfound...
}
returnparent::render($request,$e);
}
}
Eloquent模型結合查詢語法
$users=User::where('votes','>',100)->take(10)->get();
foreach($usersas$user)
{
var_dump($user->name);
}
Eloquent聚合查詢
當然,您也可以使用查詢產生器的聚合查詢方法。
$count=User::where('votes','>',100)->count();
如果沒辦法使用流暢介面產生出查詢語句,也可以使用whereRaw方法:
$users=User::whereRaw('age>?andvotes=100',[25])->get();
切分查詢
如果你要處理非常多(數千筆)Eloquent查詢結果,使用chunk方法可以讓你順利作業而不會吃掉記憶體:
User::chunk(200,function($users)
{
foreach($usersas$user)
{
//
}
});
傳到方法裡的第一個參數表示每次「切分」要取出的資料數量。
第二個參數的閉合函數會在每次取出資料時被呼叫。
指定查詢時連線資料庫
您也可以指定在執行Eloquent查詢時要使用哪個資料庫連線。
只要使用on方法:
$user=User::on('connection-name')->find(1);
如果您有使用讀取/寫入連線,可以使用如下方法,強制使用"write"連線進行查詢:
$user=User::onWriteConnection()->find(1);
批量賦值(MassAssignment)
在建立一個新的模型時,你把屬性資料陣列傳入建構子,這些屬性值會經由批量賦值存成模型資料。
這一點非常方便,然而,若盲目地將使用者輸入存到模型,可能會造成嚴重的安全隱患。
如果盲目的存入使用者輸入,使用者可以隨意的修改任何以及所有模型的屬性。
基於這個理由,所有的Eloquent模型預設會防止批量賦值。
我們以在模型裡設定fillable或guarded屬性作為開始。
定義模型Fillable屬性
fillable屬性指定了可以被批量賦值的欄位。
可以設定在類別裡或是建立實例後設定。
classUserextendsModel{
protected$fillable=['first_name','last_name','email'];
}
在上面的例子裡,只有三個屬性允許被批量賦值。
定義模型Guarded屬性
guarded與fillable相反,是作為「黑名單」而不是「白名單」:
classUserextendsModel{
protected$guarded=['id','password'];
}
注意:使用guarded時,Input::get()或任何使用者可以控制的未過濾資料,永遠不應該傳入save或update方法,因為沒有在「黑名單」內的欄位可能被更新。
阻擋所有屬性被批量賦值
上面的例子中,id和password屬性不會被批量賦值,而所有其他的屬性則允許批量賦值。
您也可以使用guard屬性阻止所有屬性被批量賦值:
protected$guarded=['*'];
新增、更新、刪除
要從模型新增一筆資料到資料庫,只要建立一個模型例項並呼叫save方法即可。
儲存新的模型資料
$user=newUser;
$user->name='John';
$user->save();
注意:通常Eloquent模型主鍵值會自動遞增。
但是您若想自定主鍵,將incrementing屬性設成false。
也可以使用create方法存入新的模型資料,新增完後會返回新增的模型實例。
但是在新增前,需要先在模型類別裡設定好fillable或guarded屬性,因為Eloquent預設會防止批量賦值。
在新模型資料被儲存或新增後,若模型有自動遞增主鍵,可以從物件取得id屬性值:
$insertedId=$user->id;
在模型裡設定Guarded屬性
classUserextendsModel{
protected$guarded=['id','account_id'];
}
使用模型的Create方法
//在資料庫中建立一個新的使用者...
$user=User::create(['name'=>'John']);
//以屬性找使用者,若沒有則新增並取得新的實例...
$user=User::firstOrCreate(['name'=>'John']);
//以屬性找使用者,若沒有則建立新的實例...
$user=User::firstOrNew(['name'=>'John']);
更新取出的模型
要更新模型,可以取出它,更改屬性值,然後使用save方法:
$user=User::find(1);
$user->email='[email protected]';
$user->save();
儲存模型和關聯資料
有時你可能不只想要儲存模型本身,也想要儲存關聯的資料。
您可以使用push方法達到目的:
$user->push();
你可以結合查詢語句,批次更新模型:
$affectedRows=User::where('votes','>',100)->update(['status'=>2]);
注意:若使用Eloquent查詢產生器批次更新模型,則不會觸發模型事件。
刪除模型
要刪除模型,只要使用實例呼叫delete方法:
$user=User::find(1);
$user->delete();
依主鍵值刪除模型
User::destroy(1);
User::destroy([1,2,3]);
User::destroy(1,2,3);
當然,您也可以結合查詢語句批次刪除模型:
$affectedRows=User::where('votes','>',100)->delete();
只更新模型的時間戳
如果您只想要更新模型的時間戳,您可以使用touch方法:
$user->touch();
軟刪除(SoftDeleting)
通過軟刪除方式刪除了一個模型後,資料並不是真的從資料庫被移除。
而是會設定deleted_at時間戳。
要讓模型使用軟刪除功能,只要在模型類別加入SoftDeletes:
useIlluminate\Database\Eloquent\SoftDeletes;
classUserextendsModel{
useSoftDeletes;
protected$dates=['deleted_at'];
}
要加入deleted_at欄位到資料庫表,可以在遷移檔案裡使用softDeletes方法:
$table->softDeletes();
現在當您使用模型呼叫delete方法時,deleted_at欄位會被更新成現在的時間戳。
在查詢使用軟刪除功能的模型時,被「刪除」的模型資料不會出現在查詢結果裡。
強制查詢軟刪除資料
要強制讓已被軟刪除的模型資料出現在查詢結果裡,在查詢時使用withTrashed方法:
$users=User::withTrashed()->where('account_id',1)->get();
withTrashed也可以用在關聯查詢:
$user->posts()->withTrashed()->get();
如果你只想查詢被軟刪除的模型資料,可以使用onlyTrashed方法:
$users=User::onlyTrashed()->where('account_id',1)->get();
要把被軟刪除的模型資料恢復,使用restore方法:
$user->restore();
您也可以結合查詢語句使用restore:
User::withTrashed()->where('account_id',1)->restore();
如同withTrashed,restore方法也可以用在關聯物件:
$user->posts()->restore();
如果想要真的從模型資料庫刪除,使用forceDelete方法:
$user->forceDelete();
forceDelete方法也可以用在關聯物件:
$user->posts()->forceDelete();
要確認模型是否被軟刪除了,可以使用trashed方法:
if($user->trashed())
{
//
}
時間戳
預設Eloquent會自動維護資料表的created_at和updated_at欄位。
只要把這兩個「時間戳」欄位加到資料表,Eloquent就會處理剩下的工作。
如果不想讓Eloquent自動維護這些欄位,把下面的屬性加到模型類別裡:
關閉自動更新時間戳
classUserextendsModel{
protected$table='users';
public$timestamps=false;
}
自定義時間戳格式
如果想要自定義時間戳格式,可以在模型類別裡覆寫getDateFormat方法:
classUserextendsModel{
protectedfunctiongetDateFormat()
{
return'U';
}
}
範圍查詢
定義範圍查詢
範圍查詢可以讓你輕鬆的重複利用模型的查詢邏輯。
要設定範圍查詢,只要定義以scope前綴的模型方法:
classUserextendsModel{
publicfunctionscopePopular($query)
{
return$query->where('votes','>',100);
}
publicfunctionscopeWomen($query)
{
return$query->whereGender('W');
}
}
使用範圍查詢
$users=User::popular()->women()->orderBy('created_at')->get();
動態範圍查詢
有時你可能想要定義可接受參數的範圍查詢方法。
只要把參數加到方法裡:
classUserextendsModel{
publicfunctionscopeOfType($query,$type)
{
return$query->whereType($type);
}
}
然後把參數值傳到範圍查詢方法呼叫裡:
$users=User::ofType('member')->get();
全域範圍查詢
有時你可能希望定義一個scope,讓它統一作用在模型的所有查詢中。
本質上,這也是Eloquent的「軟刪除」功能的實現原理。
全域範圍查詢的功能,是通過PHPtraits加上Illuminate\Database\Eloquent\ScopeInterface介面的實作來定義的。
首先,我們需要定義一個trait。
這裡我們用Laravel的SoftDeletes舉例:
traitSoftDeletes{
/**
*Bootthesoftdeletingtraitforamodel.
*
*@returnvoid
*/
publicstaticfunctionbootSoftDeletes()
{
static::addGlobalScope(newSoftDeletingScope);
}
}
如果一個Eloquent模型引入了一個trait,而這個trait中帶有符合bootNameOfTrait形式的命名方法,那麼這個方法會在Eloquent模型啟動的時候呼叫,
您可以在此時註冊全域範圍查詢,或者其他想進行的操作。
scope必須實作ScopeInterface介面,介面定義了兩個方法:apply和remove。
apply方法會傳入Illuminate\Database\Eloquent\Builder查詢產生器物件和要使用它的Model,用來新增這個scope所需的額外的where查詢。
而remove方法同樣接受一個Builder物件和Model,用來反向執行apply操作。
換句話說,remove方法應該移除已經新增的where查詢(或者其他查詢子句)。
因此,以SoftDeletingScope來說,方法看起來如下:
/**
*ApplythescopetoagivenEloquentquerybuilder.
*
*@param\Illuminate\Database\Eloquent\Builder$builder
*@param\Illuminate\Database\Eloquent\Model$model
*@returnvoid
*/
publicfunctionapply(Builder$builder,Model$model)
{
$builder->whereNull($model->getQualifiedDeletedAtColumn());
$this->extend($builder);
}
/**
*RemovethescopefromthegivenEloquentquerybuilder.
*
*@param\Illuminate\Database\Eloquent\Builder$builder
*@param\Illuminate\Database\Eloquent\Model$model
*@returnvoid
*/
publicfunctionremove(Builder$builder,Model$model)
{
$column=$model->getQualifiedDeletedAtColumn();
$query=$builder->getQuery();
foreach((array)$query->wheresas$key=>$where)
{
//Ifthewhereclauseisasoftdeletedateconstraint,wewillremoveitfrom
//thequeryandresetthekeysonthewheres.Thisallowsthisdeveloperto
//includedeletedmodelinarelationshipresultsetthatislazyloaded.
if($this->isSoftDeleteConstraint($where,$column))
{
unset($query->wheres[$key]);
$query->wheres=array_values($query->wheres);
}
}
}
關聯
當然,你的資料表很可能跟另一張表相關聯。
例如,一篇部落格文章可能有很多評論,或是一張訂單跟下單客戶相關聯。
Eloquent讓管理和處理這些關聯變得很容易。
Laravel有很多種關聯類型:
一對一
一對多
多對多
遠層一對多關聯
多型關聯
多型的多對多關聯
一對一
定義一對一關聯
一對一關聯是很基本的關聯。
例如一個User模型會對應到一個Phone。
在Eloquent裡可以像下面這樣定義關聯:
classUserextendsModel{
publicfunctionphone()
{
return$this->hasOne('App\Phone');
}
}
傳到hasOne方法裡的第一個參數是關聯模型的類別名稱。
定義好關聯之後,就可以使用Eloquent的動態屬性取得關聯物件:
$phone=User::find(1)->phone;
SQL會執行如下語句:
select*fromuserswhereid=1
select*fromphoneswhereuser_id=1
注意,Eloquent假設對應的關聯模型資料表裡,外鍵名稱是基於模型名稱。
在這個例子裡,預設Phone模型資料表會以user_id作為外鍵。
如果想要更改這個預設,可以傳入第二個參數到hasOne方法裡。
更進一步,還可以傳入第三個參數,指定關聯的外鍵是與這個模型本身的哪個欄位對應:
return$this->hasOne('App\Phone','foreign_key');
return$this->hasOne('App\Phone','foreign_key','local_key');
定義相對的關聯
要在Phone模型裡定義相對的關聯,可以使用belongsTo方法:
classPhoneextendsModel{
publicfunctionuser()
{
return$this->belongsTo('App\User');
}
}
在上面的例子裡,Eloquent預設會使用phones資料表的user_id欄位查詢關聯。
如果想要自己指定外鍵欄位,可以在belongsTo方法裡傳入第二個參數:
classPhoneextendsModel{
publicfunctionuser()
{
return$this->belongsTo('App\User','local_key');
}
}
除此之外,也可以傳入第三個參數,指定要參照上層資料庫表的哪個欄位:
classPhoneextendsModel{
publicfunctionuser()
{
return$this->belongsTo('App\User','local_key','parent_key');
}
}
一對多
一對多關聯的例子如,一篇部落格文章可能「有很多」評論。
可以像這樣定義關聯:
classPostextendsModel{
publicfunctioncomments()
{
return$this->hasMany('App\Comment');
}
}
現在可以經由動態屬性取得文章的評論:
$comments=Post::find(1)->comments;
如果需要增加更多條件限制,可以在呼叫comments方法後面串接查詢條件方法:
$comments=Post::find(1)->comments()->where('title','=','foo')->first();
同樣的,您可以傳入第二個參數到hasMany方法更改預設的外來鍵名稱。
以及,如同hasOne關聯,可以指定本身的對應欄位:
return$this->hasMany('App\Comment','foreign_key');
return$this->hasMany('App\Comment','foreign_key','local_key');
定義相對的關聯
要在Comment模型定義相對應的關聯,可使用belongsTo方法:
classCommentextendsModel{
publicfunctionpost()
{
return$this->belongsTo('App\Post');
}
}
多對多
多對多關聯更為複雜。
這種關聯的例子如,一個使用者(user)可能用有很多身份(role),而一種身份可能很多使用者都有。
例如很多使用者都是「管理者」。
多對多關聯需要用到三個資料庫表:users、roles和role_user。
role_user樞紐表命名是以相關聯的兩個模型資料庫表,依照字母順序命名,樞紐表裡面應該要有user_id和role_id欄位。
可以使用belongsToMany方法定義多對多關係:
classUserextendsModel{
publicfunctionroles()
{
return$this->belongsToMany('App\Role');
}
}
現在我們可以從User模型取得roles:
$roles=User::find(1)->roles;
如果不想使用預設的樞紐資料表命名方式,可以傳遞資料表名稱作為belongsToMany方法的第二個參數:
return$this->belongsToMany('App\Role','user_roles');
也可以更改預設的關聯欄位名稱:
return$this->belongsToMany('App\Role','user_roles','user_id','foo_id');
當然,也可以在Role模型定義相對的關聯:
classRoleextendsModel{
publicfunctionusers()
{
return$this->belongsToMany('App\User');
}
}
HasManyThrough遠層一對多關聯
「遠層一對多關聯」提供了方便簡短的方法,可以經由多層間的關聯取得遠層的關聯。
例如,一個Country模型可能通過Users關聯到很多Posts模型。
資料庫表間的關係可能看起來如下:
countries
id-integer
name-string
users
id-integer
country_id-integer
name-string
posts
id-integer
user_id-integer
title-string
雖然posts資料庫表本身沒有country_id欄位,但hasManyThrough方法讓我們可以使用$country->posts取得country的posts。
我們可以定義以下關聯:
classCountryextendsModel{
publicfunctionposts()
{
return$this->hasManyThrough('App\Post','App\User');
}
}
如果想要手動指定關聯的欄位名稱,可以傳入第三和第四個參數到方法裡:
classCountryextendsModel{
publicfunctionposts()
{
return$this->hasManyThrough('App\Post','App\User','country_id','user_id');
}
}
多型關聯
多型關聯可以用一個簡單的關聯方法,就讓一個模型同時關聯多個模型。
例如,你可能想定義一個photo模型,可以屬於staff或order模型。
可以定義關聯如下:
classPhotoextendsModel{
publicfunctionimageable()
{
return$this->morphTo();
}
}
classStaffextendsModel{
publicfunctionphotos()
{
return$this->morphMany('App\Photo','imageable');
}
}
classOrderextendsModel{
publicfunctionphotos()
{
return$this->morphMany('App\Photo','imageable');
}
}
取得多型關聯物件
現在我們可以從staff或order模型取得多型關聯物件:
$staff=Staff::find(1);
foreach($staff->photosas$photo)
{
//
}
取得多型關聯物件的擁有者
然而,多型關聯真正神奇的地方,在於要從Photo模型取得staff或order物件時:
$photo=Photo::find(1);
$imageable=$photo->imageable;
Photo模型裡的imageable關聯會返回Staff或Order實例,取決於這是哪一種模型擁有的照片。
多型關聯的資料表結構
為了理解多型關聯的運作機制,來看看它們的資料庫表結構:
staff
id-integer
name-string
orders
id-integer
price-integer
photos
id-integer
path-string
imageable_id-integer
imageable_type-string
要注意的重點是photos資料表的imageable_id和imageable_type。
在上面的例子裡,ID欄位會包含staff或order的ID,而type是擁有者的模型類別名稱。
這就是讓ORM在取得imageable關聯物件時,決定要哪一種模型物件的機制。
多型的多對多關聯
多型的多對多關聯資料表結構
除了一般的多型關聯,也可以使用多對多的多型關聯。
例如,部落格的Post和Video模型可以共用多型的Tag關聯模型。
首先,來看看資料表結構:
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
現在,我們準備好設定模型關聯了。
Post和Video模型都可以經由tags方法建立morphToMany關聯:
classPostextendsModel{
publicfunctiontags()
{
return$this->morphToMany('App\Tag','taggable');
}
}
在Tag模型裡針對每一種關聯建立一個方法:
classTagextendsModel{
publicfunctionposts()
{
return$this->morphedByMany('App\Post','taggable');
}
publicfunctionvideos()
{
return$this->morphedByMany('App\Video','taggable');
}
}
關聯查詢
根據關聯條件查詢
在取得模型資料時,你可能想要以關聯模型作為查詢限制。
例如,您可能想要取得所有「至少有一篇評論」的部落格文章。
可以使用has方法達成目的:
$posts=Post::has('comments')->get();
也可以指定運算子和數量:
$posts=Post::has('comments','>=',3)->get();
也可以使用「點號」的形式建構巢狀的has語句:
$posts=Post::has('comments.votes')->get();
如果想要更進階的用法,可以使用whereHas和orWhereHas方法,在has查詢裡設定"where"條件:
$posts=Post::whereHas('comments',function($q)
{
$q->where('content','like','foo%');
})->get();
動態屬性
Eloquent可以經由動態屬性取得關聯物件。
Eloquent會自動進行關聯查詢,而且會很聰明的知道應該要使用get(用在一對多關聯)或是first(用在一對一關聯)方法。
可以使用和「關聯方法名稱相同」的動態屬性取得物件。
例如,如下面的模型物件$phone:
classPhoneextendsModel{
publicfunctionuser()
{
return$this->belongsTo('App\User');
}
}
$phone=Phone::find(1);
比起像這樣印出使用者的email:
echo$phone->user()->first()->email;
可以簡短寫成:
echo$phone->user->email;
注意:若取得的是許多關聯物件,會返回Illuminate\Database\Eloquent\Collection物件。
預載入(EagerLoading)
預載入是用來減少N+1查詢問題。
例如,一個Book模型資料會關聯到一個Author。
關聯會像下面這樣定義:
classBookextendsModel{
publicfunctionauthor()
{
return$this->belongsTo('App\Author');
}
}
現在考慮下面的程式碼:
foreach(Book::all()as$book)
{
echo$book->author->name;
}
上面的迴圈會執行一次查詢取回所有資料表上的書籍,然而每本書都會執行一次查詢取得作者。
所以若有25本書,就會進行26次查詢。
很幸運地,我們可以使用預載入大量減少查詢次數。
使用with方法指定想要預載入的關聯物件:
foreach(Book::with('author')->get()as$book)
{
echo$book->author->name;
}
現在,上面的迴圈總共只會執行兩次查詢:
select*frombooks
select*fromauthorswhereidin(1,2,3,4,5,...)
使用預載入可以大大提高程式的效能。
當然,也可以同時載入多種關聯:
$books=Book::with('author','publisher')->get();
甚至可以預載入巢狀關聯:
$books=Book::with('author.contacts')->get();
上面的例子中,author關聯會被預載入,author的contacts關聯也會被預載入。
預載入條件限制
有時你可能想要預載入關聯,並且指定預載入的查詢限制。
下面有一個例子:
$users=User::with(['posts'=>function($query)
{
$query->where('title','like','%first%');
}])->get();
上面的例子裡,我們打算預載入user的posts關聯,並限制條件為post的title欄位需包含"first"。
當然,預載入的閉合函數裡不一定只能加上條件限制,也可以加上排序:
$users=User::with(['posts'=>function($query)
{
$query->orderBy('created_at','desc');
}])->get();
延遲預載入
也可以直接從模型的collection預載入關聯物件。
這對於需要根據情況決定是否載入關聯物件時,或是跟快取一起使用時很有用。
$books=Book::all();
$books->load('author','publisher');
YoumayalsopassaClosuretosetconstraintsonthequery:
$books->load(['author'=>function($query)
{
$query->orderBy('published_date','asc');
}]);
新增關聯模型
附加一個關聯模型
你會常常需要加入新的關聯模型。
例如新增一個comment到post。
除了手動設定模型的post_id外鍵,也可以從上層的Post模型新增關聯的comment:
$comment=newComment(['message'=>'Anewcomment.']);
$post=Post::find(1);
$comment=$post->comments()->save($comment);
上面的例子裡,新增的comment模型中,post_id欄位會被自動設定。
如果想要同時新增很多關聯模型:
$comments=[
newComment(['message'=>'Anewcomment.']),
newComment(['message'=>'Anothercomment.']),
newComment(['message'=>'Thelatestcomment.'])
];
$post=Post::find(1);
$post->comments()->saveMany($comments);
從屬關聯模型(BelongsTo)
要更新belongsTo關聯時,可以使用associate方法。
這個方法會設定子模型的外鍵:
$account=Account::find(10);
$user->account()->associate($account);
$user->save();
新增多對多關聯模型(ManyToMany)
您也可以新增多對多的關聯模型。
讓我們繼續使用User和Role模型作為例子。
我們可以使用attach方法簡單地把roles附加給一個user:
附加多對多模型
$user=User::find(1);
$user->roles()->attach(1);
也可以傳入要存在樞紐表中的屬性陣列:
$user->roles()->attach(1,['expires'=>$expires]);
當然,有attach方法就會有相反的detach方法:
$user->roles()->detach(1);
attach和detach都可以接受ID陣列作為參數:
$user=User::find(1);
$user->roles()->detach([1,2,3]);
$user->roles()->attach([1=>['attribute1'=>'value1'],2,3]);
使用Sync方法同步多對多關聯
您也可以使用sync方法附加關聯模型。
sync方法會把根據ID陣列,同步樞紐表裡的關聯。
同步完後,模型只會和ID陣列裡有的id相關聯:
$user->roles()->sync([1,2,3]);
Sync時在樞紐表加入額外資料
也可以在把每個ID加入樞紐表時,加入其他欄位的資料:
$user->roles()->sync([1=>['expires'=>true]]);
有時候,你可能想要能只用一行指令,就建立一個新關聯模型,並且附加到模型上。
可以使用save方法達成目的:
$role=newRole(['name'=>'Editor']);
User::find(1)->roles()->save($role);
上面的例子裡,新的Role模型物件被儲存,同時附加關聯到user模型。
也可以傳入屬性陣列,把資料加到關聯資料表:
User::find(1)->roles()->save($role,['expires'=>$expires]);
更新上層時間戳
當模型belongsTo另一個模型時,比方說一個Comment屬於一個Post,如果能在子模型被更新時,更新上層的時間戳,這將會很有用。
例如,當Comment模型更新時,你可能想要能夠同時自動更新Post的updated_at時間戳。
Eloquent讓事情變得很簡單。
只要在子關聯的類別裡,把關聯方法名稱加入touches屬性即可:
classCommentextendsModel{
protected$touches=['post'];
publicfunctionpost()
{
return$this->belongsTo('App\Post');
}
}
現在,當你更新Comment時,對應的Post會自動更新updated_at欄位:
$comment=Comment::find(1);
$comment->text='Edittothiscomment!';
$comment->save();
使用樞紐表
如你所知,要操作多對多關聯需要一個中介的資料表。
Eloquent提供了一些有用的方法可以和這張表互動。
例如,假設User物件關聯到很多Role物件。
取出這些關聯物件時,我們可以在關聯模型上取得pivot資料表的資料:
$user=User::find(1);
foreach($user->rolesas$role)
{
echo$role->pivot->created_at;
}
注意我們取出的每個Role模型物件,會自動賦予pivot屬性。
這屬性是一個代表樞紐表模型,可以像一般的Eloquent模型一樣使用。
預設pivot物件只包含關聯鍵的屬性。
如果想讓pivot包含樞紐表的其他欄位,可以在定義關聯方法時指定那些欄位:
return$this->belongsToMany('App\Role')->withPivot('foo','bar');
現在可以在Role模型的pivot物件上取得foo和bar屬性了。
如果想要可以自動維護樞紐表的created_at和updated_at時間戳,在定義關聯方法時加上withTimestamps方法:
return$this->belongsToMany('App\Role')->withTimestamps();
刪除樞紐表的關聯資料
要刪除模型在樞紐表的所有關聯,可以使用detach方法:
User::find(1)->roles()->detach();
注意,上面的操作不會移除roles資料表裡面的資料,只會移除樞紐表裡的關聯資料。
更新樞紐表的資料
有時你只想更新樞紐表的資料,而沒有要移除關聯。
如果您想更新樞紐表,可以像下面的例子使用updateExistingPivot方法:
User::find(1)->roles()->updateExistingPivot($roleId,$attributes);
自定義樞紐模型
Laravel允許你自定義樞紐模型。
要自定義模型,首先要建立一個繼承Eloquent的「基本」模型類別。
其他的Eloquent模型要繼承這個自定義的基本類別,而不是預設的Eloquent。
在基本模型類別裡,加入下面的方法傳回自定義的樞紐模型實例:
publicfunctionnewPivot(Model$parent,array$attributes,$table,$exists)
{
returnnewYourCustomPivot($parent,$attributes,$table,$exists);
}
集合
所有Eloquent查詢返回的資料,如果結果多於一條,不管是經由get方法或是relationship,都會轉換成集合物件返回。
這個物件實現了IteratorAggregatePHP介面,所以可以像陣列一般進行迭代。
而集合物件本身還擁有很多有用的方法可以操作模型資料。
確認集合中裡是否包含特定鍵值
例如,我們可以使用contains方法,確認結果資料中,是否包含主鍵為特定值的物件。
$roles=User::find(1)->roles;
if($roles->contains(2))
{
//
}
集合也可以轉換成陣列或JSON:
$roles=User::find(1)->roles->toArray();
$roles=User::find(1)->roles->toJson();
如果集合被轉換成字元串類型,會返回JSON格式:
$roles=(string)User::find(1)->roles;
集合迭代
Eloquent集合裡包含了一些有用的方法可以進行迴圈或是進行過濾:
$roles=$user->roles->each(function($role)
{
//
});
集合過濾
過濾集合時,閉合函數的使用方式和array_filter一樣。
$users=$users->filter(function($user)
{
return$user->isAdmin();
});
注意:如果要在過濾集合之後轉成JSON,考慮轉換之前先呼叫values方法,以重設陣列的鍵值。
迭代傳入集合裡的每個物件到閉合函數
$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');
返回自定義的集合類別
有時您可能想要返回自定義的集合物件,讓您可以在集合類別里加入想要的方法。
可以在Eloquent模型類別裡覆寫newCollection方法:
classUserextendsModel{
publicfunctionnewCollection(array$models=[])
{
returnnewCustomCollection($models);
}
}
存取器和修改器
定義存取器
Eloquent提供了一種便利的方法,可以在取得或設定屬性時進行轉換。
要定義存取器,只要在模型里加入類似getFooAttribute命名的方法。
注意方法名稱應該使用駝峰式大小寫命名,而對應的資料表欄位名稱則是底線分隔小寫命名:
classUserextendsModel{
publicfunctiongetFirstNameAttribute($value)
{
returnucfirst($value);
}
}
上面的例子中,first_name欄位設定了一個存取器。
注意傳入方法的參數是欄位的原始資料。
定義修改器
修改器的定義方式也類似:
classUserextendsModel{
publicfunctionsetFirstNameAttribute($value)
{
$this->attributes['first_name']=strtolower($value);
}
}
日期轉換器
預設Eloquent會把created_at和updated_at欄位屬性轉換成Carbon實例,它提供了很多有用的方法,並繼承了PHP原生的DateTime類。
可以通過覆寫模型的getDates方法,自定義哪個欄位可以被自動轉換,或甚至完全關閉這個轉換:
publicfunctiongetDates()
{
return['created_at'];
}
當欄位是表示日期的時候,可以將值設為UNIXtimestamp、日期字串(Y-m-d)、日期時間(date-time)字串,當然還有DateTime或Carbon實例。
要完全關閉日期轉換功能,只要從getDates方法回傳空陣列即可:
publicfunctiongetDates()
{
return[];
}
屬性型別轉換
如果想要某些屬性始終轉換成另一個型別,可以在模型中增加casts屬性。
否則需要為每個屬性定義修改器,這會很花時間。
這裡有一個使用casts屬性的例子:
/**
*需要被轉換成基本型別的屬性值。
*
*@vararray
*/
protected$casts=[
'is_admin'=>'boolean',
];
現在當你取用is_admin屬性時,總是會被轉換成布林型別,即便原本它在資料庫中是存成整數。
其他支援的型別轉換有:integer、real、float、double、string、boolean、object和array。
如果原本欄位是被儲存的為序列化的JSON時,那麼array型別轉換將會非常有用。
比如,資料表裡有一個TEXT型別的欄位儲存著序列化後的JSON資料,通過增加array型別轉換,當取得這個屬性的時候會自動反序列化成PHP的陣列:
/**
*需要被轉換成基本類型的屬性值。
*
*@vararray
*/
protected$casts=[
'options'=>'array',
];
現在,當你使用Eloquent模型時:
$user=User::find(1);
//$options是一個數組...
$options=$user->options;
//options會自動序列化成JSON...
$user->options=['foo'=>'bar'];
模型事件
Eloquent模型有很多事件可以觸發,讓您可以在模型操作的生命週期的不同時間點,使用下列方法綁定事件:creating、created、updating、updated、saving、saved、deleting、deleted、restoring、restored。
當一個物件初次被儲存到資料庫,creating和created事件會被觸發。
如果不是新物件而呼叫了save方法,updating和updated事件會被觸發。
而兩者的saving和saved事件都會被觸發。
使用事件取消資料庫操作
如果creating、updating、saving、deleting事件返回false的話,就會取消資料庫操作
User::creating(function($user)
{
if(!$user->isValid())returnfalse;
});
註冊事件監聽的方式
你可以在EventServiceProvider中註冊您的模型事件綁定。
比如:
/**
*Registeranyothereventsforyourapplication.
*
*@param\Illuminate\Contracts\Events\Dispatcher$events
*@returnvoid
*/
publicfunctionboot(DispatcherContract$events)
{
parent::boot($events);
User::creating(function($user)
{
//
});
}
模型觀察者
要整合模型的事件處理,可以註冊一個模型觀察者。
觀察者類別裡,設定欲對應模型事件的方法。
例如,觀察者類別裡可能有creating、updating、saving方法,還有其他對應模型事件名稱的方法:
例如,一個模型觀察者類別可能看起來如下:
classUserObserver{
publicfunctionsaving($model)
{
//
}
publicfunctionsaved($model)
{
//
}
}
可以使用observe方法註冊一個觀察者實例:
User::observe(newUserObserver);
ModelURLGeneration
Whenyoupassamodeltotherouteoractionmethods,it'sprimarykeyisinsertedintothegeneratedURI.Forexample:
Route::get('user/{user}','[email protected]');
action('[email protected]',[$user]);
Inthisexamplethe$user->idpropertywillbeinsertedintothe{user}place-holderofthegeneratedURL.However,ifyouwouldliketouseanotherpropertyinsteadoftheID,youmayoverridethegetRouteKeymethodonyourmodel:
publicfunctiongetRouteKey()
{
return$this->slug;
}
轉換成陣列/JSON
將模型資料轉成陣列
建構JSONAPI時,你可能常常需要把模型和關聯物件轉換成陣列或JSON。
所以Eloquent裡已經包含了這些方法。
要把模型和已載入的關聯物件轉成陣列,可以使用toArray方法:
$user=User::with('roles')->first();
return$user->toArray();
注意也可以把模型集合整個轉換成陣列:
returnUser::all()->toArray();
將模型轉換成JSON
要把模型轉換成JSON,可以使用toJson方法:
returnUser::find(1)->toJson();
從路由中回傳模型
注意當模型或集合被轉換成字串時,會自動轉換成JSON格式,這意味著您可以直接從路由返回Eloquent物件!
Route::get('users',function()
{
returnUser::all();
});
轉換成陣列或JSON時隱藏屬性
有時你可能想要限制能出現在陣列或JSON格式的屬性資料,比如密碼欄位。
只要在模型裡增加hidden屬性即可
classUserextendsModel{
protected$hidden=['password'];
}
注意:要隱藏關聯資料,要使用關聯的方法名稱,而不是動態存取的屬性名稱。
此外,可以使用visible屬性定義白名單:
protected$visible=['first_name','last_name'];
有時候你可能想要增加不存在資料庫欄位的屬性資料。
這時候只要定義一個存取器即可:
publicfunctiongetIsAdminAttribute()
{
return$this->attributes['admin']=='yes';
}
定義好存取器之後,再把對應的屬性名稱加到模型裡的appends屬性:
protected$appends=['is_admin'];
把屬性加到appends陣列之後,在模型資料轉換成陣列或JSON格式時就會有對應的值。
在appends陣列中定義的值同樣遵循模型中visible和hidden的設定。
延伸文章資訊
- 1ELOQUENT在劍橋英語詞典中的解釋及翻譯
eloquent的意思、解釋及翻譯:1. giving a clear, strong message: 2. giving a clear, strong message: 3. using ...
- 2Eloquent:入門 - Laravel 道場
所有的Eloquent 模型都繼承 Illuminate\Database\Eloquent\Model 類別。 建立模型實例的最簡單的方法是使用Artisan 指令的 make:model :...
- 3eloquent是什麼意思 - 海词词典
eloquent的用法和樣例:. 例句. 用作形容詞 (adj.) He addressed the audience in an eloquent speech. 他向 ...
- 4Eloquent ORM - Laravel - 為網頁藝術家創造的PHP 框架
所有的Eloquent 模型都繼承 Illuminate\Database\Eloquent\Model 。 定義一個Eloquent 模型. class User extends Model ...
- 5Eloquent: 入門- Laravel - 為網頁藝術家創造的PHP 框架
所有的Eloquent 模型都繼承 Illuminate\Database\Eloquent\Model 類別。 建立模型實例的最簡單的方法是使用 make:model Artisan 指令: ...