Eloquent ORM

2018-12-17 10:58 更新

介紹

Laravel 的 Eloquent ORM 提供了漂亮、簡潔的 ActiveRecord 實(shí)現(xiàn)來和數(shù)據(jù)庫的互動。 每個數(shù)據(jù)庫表會和一個對應(yīng)的「模型」互動。

在開始之前,記得把 config/database.php 里的數(shù)據(jù)庫連接配置好。

           

基本用法

我們先從建立一個 Eloquent 模型開始。模型通常放在 app 目錄下,但是您可以將它們放在任何地方,只要能通過 composer.json 自動載入。所有的 Eloquent 模型都繼承于 Illuminate\Database\Eloquent\Model

定義一個 Eloquent 模型

class User extends Model {}

           

你也可以通過 make:model 命令自動生成 Eloquent 模型:

php artisan make:model User

           

注意我們并沒有告訴 Eloquent User 模型會使用哪個數(shù)據(jù)庫表。若沒有特別指定,系統(tǒng)會默認(rèn)自動對應(yīng)名稱為「類名稱的小寫復(fù)數(shù)形態(tài)」的數(shù)據(jù)庫表。所以,在上面的例子中, Eloquent 會假設(shè) User 模型將把數(shù)據(jù)存在 users 數(shù)據(jù)庫表。您也可以在類中定義 table 屬性自定義要對應(yīng)的數(shù)據(jù)庫表。

class User extends Model {

    protected $table = 'my_users';}

           

注意: Eloquent 也會假設(shè)每個數(shù)據(jù)庫表都有一個字段名稱為 id 的主鍵。您可以在類里定義 primaryKey 屬性來重寫。同樣的,您也可以定義 connection 屬性,指定模型連接到指定的數(shù)據(jù)庫連接。

定義好模型之后,您就可以從數(shù)據(jù)庫表新增及獲取數(shù)據(jù)了。注意在默認(rèn)情況下,在數(shù)據(jù)庫表里需要有 updated_atcreated_at 兩個字段。如果您不想設(shè)定或自動更新這兩個字段,則將類里的 $timestamps 屬性設(shè)為 false即可。

取出所有記錄

$users = User::all();

           

根據(jù)主鍵取出一條數(shù)據(jù)

$user = User::find(1);var_dump($user->name);

           

提示: 所有查詢構(gòu)造器里的方法,查詢 Eloquent 模型時也可以使用。

根據(jù)主鍵取出一條數(shù)據(jù)或拋出異常

有時, 您可能想要在找不到模型數(shù)據(jù)時拋出異常,通過 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);
    }}

           

Eloquent 模型結(jié)合查詢語法

$users = User::where('votes', '>', 100)->take(10)->get();foreach ($users as $user){
    var_dump($user->name);}

           

Eloquent 聚合查詢

當(dāng)然,您也可以使用查詢構(gòu)造器的聚合查詢方法。

$count = User::where('votes', '>', 100)->count();

           

如果沒辦法使用流暢接口產(chǎn)生出查詢語句,也可以使用 whereRaw 方法:

$users = User::whereRaw('age > ? and votes = 100', [25])->get();

           

拆分查詢

如果您要處理非常多(數(shù)千條)Eloquent 查詢結(jié)果,使用 chunk 方法可以讓您順利工作而不會消耗大量內(nèi)存:

User::chunk(200, function($users){
    foreach ($users as $user)
    {
        //    }});

           

傳到方法里的第一個參數(shù)表示每次「拆分」要取出的數(shù)據(jù)數(shù)量。第二個參數(shù)的閉合函數(shù)會在每次取出數(shù)據(jù)時被調(diào)用。

指定查詢時連接數(shù)據(jù)庫

您也可以指定在執(zhí)行 Eloquent 查詢時要使用哪個數(shù)據(jù)庫連接。只要使用 on 方法:

$user = User::on('connection-name')->find(1);

           

如果您在使用 讀取 / 寫入連接, 您可以通過如下命令來強(qiáng)制查詢使用 寫入 連接:

$user = User::onWriteConnection()->find(1);

           

           

批量賦值

在建立一個新的模型時,您把屬性以數(shù)組的方式傳入模型的構(gòu)造方法,這些屬性值會經(jīng)由批量賦值存成模型數(shù)據(jù)。這一點(diǎn)非常方便,然而,若盲目地將用戶輸入存到模型時,可能會造成嚴(yán)重的安全隱患。如果盲目的存入用戶輸入,用戶可以隨意的修改任何以及所有模型的屬性?;谶@個理由,所有的 Eloquent 模型默認(rèn)會阻止批量賦值 。

我們以在模型里設(shè)定 fillableguarded 屬性作為開始。

定義模型 Fillable 屬性

fillable 屬性指定了哪些字段支持批量賦值 ??梢栽O(shè)定在類的屬性里或是實(shí)例化后設(shè)定。

class User extends Model {

    protected $fillable = ['first_name', 'last_name', 'email'];}

           

在上面的例子里,只有三個屬性允許批量賦值。

定義模型 Guarded 屬性

guardedfillable 相反,是作為「黑名單」而不是「白名單」:

class User extends Model {

    protected $guarded = ['id', 'password'];}

           

注意: 使用 guarded 時, Input::get() 或任何用戶可以控制的未過濾數(shù)據(jù),永遠(yuǎn)不應(yīng)該傳入 saveupdate 方法,因?yàn)闆]有在「黑名單」內(nèi)的字段可能被更新。

阻擋所有屬性被批量賦值

上面的例子中, idpassword 屬性不會被批量賦值,而所有其他的屬性則允許批量賦值。您也可以使用 guard 屬性阻止所有屬性被批量賦值:

protected $guarded = ['*'];

           

           

新增,更新,刪除

要從模型新增一條數(shù)據(jù)到數(shù)據(jù)庫,只要建立一個模型實(shí)例并調(diào)用 save 方法即可。

儲存新的模型數(shù)據(jù)

$user = new User;$user->name = 'John';$user->save();

           

注意: 通常 Eloquent 模型主鍵值會自動遞增。但是您若想自定義主鍵,將 incrementing 屬性設(shè)成 false 。

也可以使用 create 方法存入新的模型數(shù)據(jù),新增完后會返回新增的模型實(shí)例。但是在新增前,需要先在模型類里設(shè)定好 fillableguarded 屬性,因?yàn)?Eloquent 默認(rèn)會防止批量賦值。

在新模型數(shù)據(jù)被儲存或新增后,若模型有自動遞增主鍵,可以從對象取得 id 屬性值:

$insertedId = $user->id;

           

在模型里設(shè)定 Guarded 屬性

class User extends Model {

    protected $guarded = ['id', 'account_id'];}

           

使用模型的 Create 方法

// 在數(shù)據(jù)庫中建立一個新的用戶...$user = User::create(['name' => 'John']);// 以屬性找用戶,若沒有則新增并取得新的實(shí)例...$user = User::firstOrCreate(['name' => 'John']);// 以屬性找用戶,若沒有則建立新的實(shí)例...$user = User::firstOrNew(['name' => 'John']);

           

更新取出的模型

要更新模型,可以取出它,更改屬性值,然后使用 save 方法:

$user = User::find(1);$user->email = 'john@foo.com';$user->save();

           

儲存模型和關(guān)聯(lián)數(shù)據(jù)

有時您可能不只想要儲存模型本身,也想要儲存關(guān)聯(lián)的數(shù)據(jù)。您可以使用 push 方法達(dá)到目的:

$user->push();

           

您可以結(jié)合查詢語句,批次更新模型:

$affectedRows = User::where('votes', '>', 100)->update(['status' => 2]);

           

**注意: ** 若使用 Eloquent 查詢構(gòu)造器批次更新模型,則不會觸發(fā)模型事件。

刪除模型

要刪除模型,只要使用實(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é)合查詢語句批次刪除模型:

$affectedRows = User::where('votes', '>', 100)->delete();

           

只更新模型的時間戳

如果您只想要更新模型的時間戳,您可以使用 touch 方法:

$user->touch();

           

           

軟刪除

通過軟刪除方式刪除了一個模型后,模型中的數(shù)據(jù)并不是真的從數(shù)據(jù)庫被移除。而是會設(shè)定 deleted_at時間戳。要讓模型使用軟刪除功能,只要在模型類里加入 SoftDeletingTrait 即可:

use Illuminate\Database\Eloquent\SoftDeletes;class User extends Model {

    use SoftDeletes;

    protected $dates = ['deleted_at'];}

           

要加入 deleted_at 字段到數(shù)據(jù)庫表,可以在遷移文件里使用 softDeletes 方法:

$table->softDeletes();

           

現(xiàn)在當(dāng)您使用模型調(diào)用 delete 方法時, deleted_at字段會被更新成現(xiàn)在的時間戳。在查詢使用軟刪除功能的模型時,被「刪除」的模型數(shù)據(jù)不會出現(xiàn)在查詢結(jié)果里。

強(qiáng)制查詢軟刪除數(shù)據(jù)

要強(qiáng)制讓已被軟刪除的模型數(shù)據(jù)出現(xiàn)在查詢結(jié)果里,在查詢時使用 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é)合查詢語句使用 restore

User::withTrashed()->where('account_id', 1)->restore();

           

如同 withTrashed , restore 方法也可以用在關(guān)聯(lián)對象:

$user->posts()->restore();

           

如果想要真的從模型數(shù)據(jù)庫刪除,使用 forceDelete 方法:

$user->forceDelete();

           

forceDelete 方法也可以用在關(guān)聯(lián)對象:

$user->posts()->forceDelete();

           

要確認(rèn)模型是否被軟刪除了,可以使用 trashed 方法:

if ($user->trashed()){
    //}

           

           

時間戳

默認(rèn) Eloquent 會自動維護(hù)數(shù)據(jù)庫表的 created_atupdated_at 字段。只要把這兩個「時間戳」字段加到數(shù)據(jù)庫表, Eloquent 就會處理剩下的工作。如果不想讓 Eloquent 自動維護(hù)這些字段,把下面的屬性加到模型類里:

關(guān)閉自動更新時間戳

class User extends Model {

    protected $table = 'users';

    public $timestamps = false;}

           

自定義時間戳格式

如果想要自定義時間戳格式,可以在模型類里重寫 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();

           

動態(tài)范圍查詢

有時您可能想要定義可接受參數(shù)的范圍查詢方法。只要把參數(shù)加到方法里:

class User extends Model {

    public function scopeOfType($query, $type)
    {
        return $query->whereType($type);
    }}

           

然后把參數(shù)值傳到范圍查詢方法調(diào)用里:

$users = User::ofType('member')->get();

           

           

Global Scopes

有時您可能希望定義一個 scope 可以用于模型的所有查詢中。本質(zhì)上,這也是 Eloquent 的"軟刪除"功能的實(shí)現(xiàn)原理。Global scopes 是通過 PHP traits 的組合以及實(shí)現(xiàn) Illuminate\Database\Eloquent\ScopeInterface 接口來定義的。

首先,我們需要定義一個 trait。 這里我們用 Laravel 的 SoftDeletes 舉例:

trait SoftDeletes {

    /**
     * Boot the soft deleting trait for a model.
     *
     * @return void
     */
    public static function bootSoftDeletes()
    {
        static::addGlobalScope(new SoftDeletingScope);
    }}

           

如果一個 Eloquent 模型引入了一個 trait ,而這個 trait 中帶有符合 bootNameOfTrait 慣例命名的方法 ,那么這個方法會在 Eloquent 模型啟動的時候調(diào)用, 您可以在此時注冊 global scope ,或者做一些其他您想要的操作。定義的 scope 必須實(shí)現(xiàn) ScopeInterface 接口,這個接口提供了兩個方法:applyremove。

apply 方法接受一個 Illuminate\Database\Eloquent\Builder 查詢構(gòu)造器對象以及它所應(yīng)用的 Model,用來添加這個 scope 所需的額外的 where 子句。而remove 方法同樣接受一個 Builder 對象以及 Model ,用來反向的執(zhí)行 apply                操作。也就是說,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);
        }
    }}

           

           

關(guān)聯(lián)

當(dāng)然,您的數(shù)據(jù)庫表很可能跟另一張表相關(guān)聯(lián)。例如,一篇 blog 文章可能有很多評論,或是一張訂單跟下單客戶相關(guān)聯(lián)。 Eloquent 讓管理和處理這些關(guān)聯(lián)變得很容易。 Laravel 有很多種關(guān)聯(lián)類型:

           

一對一

定義一對一關(guān)聯(lián)

一對一關(guān)聯(lián)是很基本的關(guān)聯(lián)。例如一個 User 模型會對應(yīng)到一個 Phone 。 在 Eloquent 里可以像下面這樣定義關(guān)聯(lián):

class User extends Model {

    public function phone()
    {
        return $this->hasOne('App\Phone');
    }}

           

傳到 hasOne 方法里的第一個參數(shù)是關(guān)聯(lián)模型的類名稱。定義好關(guān)聯(lián)之后,就可以使用 Eloquent 的動態(tài)屬性取得關(guān)聯(lián)對象:

$phone = User::find(1)->phone;

           

SQL 會執(zhí)行如下語句:

select * from users where id = 1select * from phones where user_id = 1

           

注意, Eloquent 假設(shè)對應(yīng)的關(guān)聯(lián)模型數(shù)據(jù)庫表里,外鍵名稱是基于模型名稱。在這個例子里,默認(rèn) Phone 模型數(shù)據(jù)庫表會以 user_id 作為外鍵。如果想要更改這個默認(rèn),可以傳入第二個參數(shù)到 hasOne 方法里。更進(jìn)一步,您可以傳入第三個參數(shù),指定關(guān)聯(lián)的外鍵要對應(yīng)到本身的哪個字段:

return $this->hasOne('App\Phone', 'foreign_key');return $this->hasOne('App\Phone', 'foreign_key', 'local_key');

           

定義相對的關(guān)聯(lián)

要在 Phone 模型里定義相對的關(guān)聯(lián),可以使用 belongsTo 方法:

class Phone extends Model {

    public function user()
    {
        return $this->belongsTo('App\User');
    }}

           

在上面的例子里, Eloquent 默認(rèn)會使用 phones 數(shù)據(jù)庫表的 user_id 字段查詢關(guān)聯(lián)。如果想要自己指定外鍵字段,可以在 belongsTo 方法里傳入第二個參數(shù):

class Phone extends Model {

    public function user()
    {
        return $this->belongsTo('App\User', 'local_key');
    }}

           

除此之外,也可以傳入第三個參數(shù)指定要參照上層數(shù)據(jù)庫表的哪個字段:

class Phone extends Model {

    public function user()
    {
        return $this->belongsTo('App\User', 'local_key', 'parent_key');
    }}

           

           

一對多

一對多關(guān)聯(lián)的例子如,一篇 Blog 文章可能「有很多」評論。可以像這樣定義關(guān)聯(lián):

class Post extends Model {

    public function comments()
    {
        return $this->hasMany('App\Comment');
    }}

           

現(xiàn)在可以經(jīng)由動態(tài)屬性取得文章的評論:

$comments = Post::find(1)->comments;

           

如果需要增加更多條件限制,可以在調(diào)用 comments 方法后面通過鏈?zhǔn)讲樵儣l件方法:

$comments = Post::find(1)->comments()->where('title', '=', 'foo')->first();

           

同樣的,您可以傳入第二個參數(shù)到 hasMany 方法更改默認(rèn)的外鍵名稱。以及,如同 hasOne 關(guān)聯(lián),可以指定本身的對應(yīng)字段:

return $this->hasMany('App\Comment', 'foreign_key');return $this->hasMany('App\Comment', 'foreign_key', 'local_key');

           

定義相對的關(guān)聯(lián)

要在 Comment 模型定義相對應(yīng)的關(guān)聯(lián),可使用 belongsTo 方法:

class Comment extends Model {

    public function post()
    {
        return $this->belongsTo('App\Post');
    }}

           

           

多對多

多對多關(guān)聯(lián)更為復(fù)雜。這種關(guān)聯(lián)的例子如,一個用戶( user )可能用有很多身份( role ),而一種身份可能很多用戶都有。例如很多用戶都是「管理者」。多對多關(guān)聯(lián)需要用到三個數(shù)據(jù)庫表: users , roles ,和 role_user 。 role_user 樞紐表命名是以相關(guān)聯(lián)的兩個模型數(shù)據(jù)庫表,依照字母順序命名,樞紐表里面應(yīng)該要有 user_id                和 role_id 字段。

可以使用 belongsToMany 方法定義多對多關(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ù)庫表命名方式,可以傳遞數(shù)據(jù)庫表名稱作為 belongsToMany 方法的第二個參數(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 模型定義相對的關(guān)聯(lián):

class Role extends Model {

    public function users()
    {
        return $this->belongsToMany('App\User');
    }}

           

           

Has Many Through 遠(yuǎn)層一對多關(guān)聯(lián)

「遠(yuǎn)層一對多關(guān)聯(lián)」提供了方便簡短的方法,可以經(jīng)由多層間的關(guān)聯(lián)取得遠(yuǎn)層的關(guān)聯(lián)。例如,一個 Country 模型可能通過 Users 關(guān)聯(lián)到很多 Posts 模型。 數(shù)據(jù)庫表間的關(guān)系可能看起來如下:

countries
    id - integer
    name - string

users
    id - integer
    country_id - integer
    name - string

posts
    id - integer
    user_id - integer
    title - string

           

雖然 posts 數(shù)據(jù)庫表本身沒有 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');
    }}

           

如果想要手動指定關(guān)聯(lián)的字段名稱,可以傳入第三和第四個參數(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)

多態(tài)關(guān)聯(lián)可以用一個簡單的關(guān)聯(lián)方法,就讓一個模型同時關(guān)聯(lián)多個模型。例如,您可能想讓 photo 模型同時和一個 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');
    }}

           

取得多態(tài)關(guān)聯(lián)對象

現(xiàn)在我們可以從 staff 或 order 模型取得多態(tài)關(guān)聯(lián)對象:

$staff = Staff::find(1);foreach ($staff->photos as $photo){
    //}

           

取得多態(tài)關(guān)聯(lián)對象的擁有者

然而,多態(tài)關(guān)聯(lián)真正神奇的地方,在于要從 Photo 模型取得 staff 或 order 對象時:

$photo = Photo::find(1);$imageable = $photo->imageable;

           

Photo 模型里的 imageable 關(guān)聯(lián)會返回 StaffOrder 實(shí)例,取決于這是哪一種模型擁有的照片。

多態(tài)關(guān)聯(lián)的數(shù)據(jù)庫表結(jié)構(gòu)

為了理解多態(tài)關(guān)聯(lián)的運(yùn)作機(jī)制,來看看它們的數(shù)據(jù)庫表結(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ù)庫表的 imageable_idimageable_type。在上面的例子里, ID 字段會包含 staff 或 order 的 ID,而 type 是擁有者的模型類名稱。這就是讓 ORM 在取得 imageable 關(guān)聯(lián)對象時,決定要哪一種模型對象的機(jī)制。

           

多態(tài)的多對多關(guān)聯(lián)

Polymorphic Many To Many Relation Table Structure 多態(tài)的多對多關(guān)聯(lián)數(shù)據(jù)庫表結(jié)構(gòu)

除了一般的多態(tài)關(guān)聯(lián),也可以使用多對多的多態(tài)關(guān)聯(lián)。例如,Blog 的 PostVideo 模型可以共用多態(tài)的 Tag 關(guān)聯(lián)模型。首先,來看看數(shù)據(jù)庫表結(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)了。 PostVideo 模型都可以經(jīng)由 tags 方法建立 morphToMany 關(guān)聯(lián):

class Post extends Model {

    public function tags()
    {
        return $this->morphToMany('App\Tag', 'taggable');
    }}

           

Tag 模型里針對每一種關(guān)聯(lián)建立一個方法:

class Tag extends Model {

    public function posts()
    {
        return $this->morphedByMany('App\Post', 'taggable');
    }

    public function videos()
    {
        return $this->morphedByMany('App\Video', 'taggable');
    }}

           

           

關(guān)聯(lián)查詢

根據(jù)關(guān)聯(lián)條件查詢

在取得模型數(shù)據(jù)時,您可能想要以關(guān)聯(lián)模型作為查詢限制。例如,您可能想要取得所有「至少有一篇評論」的Blog 文章??梢允褂?has 方法達(dá)成目的:

$posts = Post::has('comments')->get();

           

也可以指定運(yùn)算符和數(shù)量:

$posts = Post::has('comments', '>=', 3)->get();

           

也可以使用"點(diǎn)號"的形式來獲取嵌套的 has 聲明:

$posts = Post::has('comments.votes')->get();

           

如果想要更進(jìn)階的用法,可以使用 whereHasorWhereHas 方法,在 has 查詢里設(shè)置 "where" 條件 :

$posts = Post::whereHas('comments', function($q){
    $q->where('content', 'like', 'foo%');})->get();

           

           

動態(tài)屬性

Eloquent 可以經(jīng)由動態(tài)屬性取得關(guān)聯(lián)對象。 Eloquent 會自動進(jìn)行關(guān)聯(lián)查詢,而且會很聰明的知道應(yīng)該要使用 get(用在一對多關(guān)聯(lián))或是 first (用在一對一關(guān)聯(lián))方法。可以經(jīng)由和「關(guān)聯(lián)方法名稱相同」的動態(tài)屬性取得對象。例如,如下面的模型對象 $phone

class Phone extends Model {

    public function user()
    {
        return $this->belongsTo('App\User');
    }}$phone = Phone::find(1);

           

您可以不用像下面這樣打印用戶的 email :

echo $phone->user()->first()->email;

           

而可以簡寫如下:

echo $phone->user->email;

           

注意: 若取得的是許多關(guān)聯(lián)對象,會返回 Illuminate\Database\Eloquent\Collection 對象。

           

預(yù)載入

預(yù)載入是用來減少 N + 1 查詢問題。例如,一個 Book 模型數(shù)據(jù)會關(guān)聯(lián)到一個 Author 。關(guān)聯(lián)會像下面這樣定義:

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)會執(zhí)行一次查詢?nèi)』厮袛?shù)據(jù)庫表上的書籍,然而每本書籍都會執(zhí)行一次查詢?nèi)〉米髡?。所以若我們?25 本書,就會進(jìn)行 26次查詢。

很幸運(yùn)地,我們可以使用預(yù)載入大量減少查詢次數(shù)。使用 with 方法指定想要預(yù)載入的關(guān)聯(lián)對象:

foreach (Book::with('author')->get() as $book){
    echo $book->author->name;}

           

現(xiàn)在,上面的循環(huán)總共只會執(zhí)行兩次查詢:

select * from books

select * from authors where id in (1, 2, 3, 4, 5, ...)

           

使用預(yù)載入可以大大提高程序的性能。

當(dāng)然,也可以同時載入多種關(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)會被預(yù)載入, author 的 contacts 關(guān)聯(lián)也會被預(yù)載入。

預(yù)載入條件限制

有時您可能想要預(yù)載入關(guān)聯(lián),同時也想要指定載入時的查詢限制。下面有一個例子:

$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();

           

延遲預(yù)載入

也可以直接從模型的 collection 預(yù)載入關(guān)聯(lián)對象。這對于需要根據(jù)情況決定是否載入關(guān)聯(lián)對象時,或是跟緩存一起使用時很有用。

$books = Book::all();$books->load('author', 'publisher');

           

你可以傳入一個閉包來對查詢構(gòu)建器進(jìn)行條件限制:

$books->load(['author' => function($query){
    $query->orderBy('published_date', 'asc');}]);

           

           

新增關(guān)聯(lián)模型

附加一個關(guān)聯(lián)模型

您常常會需要加入新的關(guān)聯(lián)模型。例如新增一個 comment 到 post 。除了手動設(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 字段會被自動設(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);

           

從屬關(guān)聯(lián)模型 ( Belongs To )

要更新 belongsTo 關(guān)聯(lián)時,可以使用 associate 方法。這個方法會設(shè)定子模型的外鍵:

$account = Account::find(10);$user->account()->associate($account);$user->save();

           

新增多對多關(guān)聯(lián)模型 ( Many To Many )

您也可以新增多對多的關(guān)聯(lián)模型。讓我們繼續(xù)使用 UserRole 模型作為例子。我們可以使用 attach 方法簡單地把 roles 附加給一個 user:

附加多對多模型

$user = User::find(1);$user->roles()->attach(1);

           

也可以傳入要存在樞紐表中的屬性數(shù)組:

$user->roles()->attach(1, ['expires' => $expires]);

           

當(dāng)然,有 attach 方法就會有相反的 detach 方法:

$user->roles()->detach(1);

           

attachdetach 都可以接受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 方法附加關(guān)聯(lián)模型。 sync 方法會把根據(jù) ID 數(shù)組把關(guān)聯(lián)存到樞紐表。附加完關(guān)聯(lián)后,樞紐表里的模型只會關(guān)聯(lián)到 ID 數(shù)組里的 id :

$user->roles()->sync([1, 2, 3]);

           

Sync 時在樞紐表加入額外數(shù)據(jù)

也可以在把每個 ID 加入樞紐表時,加入其他字段的數(shù)據(jù):

$user->roles()->sync([1 => ['expires' => true]]);

           

有時您可能想要使用一個命令,在建立新模型數(shù)據(jù)的同時附加關(guān)聯(lián)??梢允褂?save 方法達(dá)成目的:

$role = new Role(['name' => 'Editor']);User::find(1)->roles()->save($role);

           

上面的例子里,新的 Role 模型對象會在儲存的同時關(guān)聯(lián)到 user 模型。也可以傳入屬性數(shù)組把數(shù)據(jù)加到關(guān)聯(lián)數(shù)據(jù)庫表:

User::find(1)->roles()->save($role, ['expires' => $expires]);

           

           

更新上層時間戳

當(dāng)模型 belongsTo 另一個模型時,比方說一個 Comment 屬于一個 Post ,如果能在子模型被更新時,更新上層的時間戳,這將會很有用。例如,當(dāng) Comment 模型更新時,您可能想要能夠同時自動更新 Postupdated_at 時間戳。 Eloquent 讓事情變得很簡單。只要在子關(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 時,對應(yīng)的 Post 會自動更新 updated_at 字段:

$comment = Comment::find(1);$comment->text = 'Edit to this comment!';$comment->save();

           

           

使用樞紐表

如您所知,要操作多對多關(guān)聯(lián)需要一個中間的數(shù)據(jù)庫表。 Eloquent 提供了一些有用的方法可以和這張表互動。例如,假設(shè) User 對象關(guān)聯(lián)到很多 Role 對象。取出這些關(guān)聯(lián)對象時,我們可以在關(guān)聯(lián)模型上取得 pivot 數(shù)據(jù)庫表的數(shù)據(jù):

$user = User::find(1);foreach ($user->roles as $role){
    echo $role->pivot->created_at;}

           

注意我們?nèi)〕龅拿總€ Role 模型對象會自動給一個 pivot 屬性。這屬性包含了樞紐表的模型數(shù)據(jù),可以像一般的 Eloquent 模型一樣使用。

默認(rèn) pivot 對象只會有關(guān)聯(lián)鍵的屬性。如果您想讓 pivot 可以包含其他樞紐表的字段,可以在定義關(guān)聯(lián)方法時指定那些字段:

return $this->belongsToMany('App\Role')->withPivot('foo', 'bar');

           

現(xiàn)在可以在 Role 模型的 pivot 對象上取得 foobar 屬性了。

如果您想要可以自動維護(hù)樞紐表的 created_atupdated_at 時間戳,在定義關(guān)聯(lián)方法時加上 withTimestamps 方法:

return $this->belongsToMany('App\Role')->withTimestamps();

           

刪除樞紐表的關(guān)聯(lián)數(shù)據(jù)

要刪除模型在樞紐表的所有關(guān)聯(lián)數(shù)據(jù),可以使用 detach 方法:

User::find(1)->roles()->detach();

           

注意,如上的操作不會移除 roles 數(shù)據(jù)庫表里面的數(shù)據(jù),只會移除樞紐表里的關(guān)聯(lián)數(shù)據(jù)。

更新樞紐表的數(shù)據(jù)

有時您只想更新樞紐表的數(shù)據(jù),而沒有要移除關(guān)聯(lián)。如果您想更新樞紐表,可以像下面的例子使用 updateExistingPivot 方法:

User::find(1)->roles()->updateExistingPivot($roleId, $attributes);

           

自定義樞紐模型

Laravel 允許您自定義樞紐模型。要自定義模型,首先要建立一個繼承 Eloquent 的「基本」模型類。在其他的 Eloquent 模型繼承這個自定義的基本類,而不是默認(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,都會轉(zhuǎn)換成集合對象返回。這個對象實(shí)現(xiàn)了 IteratorAggregate PHP 接口,所以可以像數(shù)組一般進(jìn)行遍歷。而集合對象本身還擁有很多有用的方法可以操作模型數(shù)據(jù)。

確認(rèn)集合中里是否包含特定鍵值

例如,我們可以使用 contains 方法,確認(rèn)結(jié)果數(shù)據(jù)中,是否包含主鍵為特定值的對象。

$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)換成字符串類型,會返回 JSON 格式:

$roles = (string) User::find(1)->roles;

           

集合遍歷

Eloquent 集合里包含了一些有用的方法可以進(jìn)行循環(huán)或是進(jìn)行過濾:

$roles = $user->roles->each(function($role){
    //});

           

集合過濾

過濾集合時,回調(diào)函數(shù)的使用方式和 array_filter 里一樣。

$users = $users->filter(function($user){
    return $user->isAdmin();});

           

注意: 如果要在過濾集合之后轉(zhuǎn)成 JSON,轉(zhuǎn)換之前先調(diào)用 values 方法重設(shè)數(shù)組的鍵值。

遍歷傳入集合里的每個對象到回調(diào)函數(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');

           

返回自定義的集合對象

有時您可能想要返回自定義的集合對象,讓您可以在集合類里加入想要的方法??梢栽?Eloquent 模型類里重寫 newCollection 方法:

class User extends Model {

    public function newCollection(array $models = [])
    {
        return new CustomCollection($models);
    }}

           

           

獲取器和修改器

定義獲取器

Eloquent 提供了一種便利的方法,可以在獲取或設(shè)定屬性時進(jìn)行轉(zhuǎn)換。要定義獲取器,只要在模型里加入類似 getFooAttribute 的方法。注意方法名稱應(yīng)該使用駝峰式大小寫命名,而對應(yīng)的 database 字段名稱是下劃線分隔小寫命名:

class User extends Model {

    public function getFirstNameAttribute($value)
    {
        return ucfirst($value);
    }}

           

上面的例子中, first_name 字段設(shè)定了一個獲取器。注意傳入方法的參數(shù)是原本的字段數(shù)據(jù)。

定義修改器

修改器的定義方式也是類似的:

class User extends Model {

    public function setFirstNameAttribute($value)
    {
        $this->attributes['first_name'] = strtolower($value);
    }}

           

           

日期轉(zhuǎn)換器

默認(rèn) Eloquent 會把 created_atupdated_at 字段屬性轉(zhuǎn)換成 Carbon 實(shí)例,它提供了很多有用的方法,并繼承了 PHP 原生的 DateTime 類。

您可以通過重寫模型的 getDates 方法,自定義哪個字段可以被自動轉(zhuǎn)換,或甚至完全關(guān)閉這個轉(zhuǎn)換:

public function getDates(){
    return ['created_at'];}

           

當(dāng)字段是表示日期的時候,可以將值設(shè)為 UNIX timestamp 、日期字符串( Y-m-d )、 日期時間( date-time )字符串,當(dāng)然還有 DateTimeCarbon 實(shí)例。

要完全關(guān)閉日期轉(zhuǎn)換功能,只要從 getDates 方法返回空數(shù)組即可:

public function getDates(){
    return [];}

           

           

屬性類型轉(zhuǎn)換

如果您想要某些屬性始終轉(zhuǎn)換成另一個數(shù)據(jù)類型, 您可以在模型中增加 casts 屬性。否則,您需要為每個屬性定義修改器,這樣會增加更多的時間開銷。這里有一個使用 casts 屬性的例子:

/**
 * 需要被轉(zhuǎn)換成基本類型的屬性值。
 *
 * @var array
 */protected $casts = [
    'is_admin' => 'boolean',];

           

現(xiàn)在當(dāng)你獲取 is_admin 屬性時始終會是布爾類型,甚至在數(shù)據(jù)庫中存儲的這個值是一個整型也會被轉(zhuǎn)換。其他支持的類型轉(zhuǎn)換值有: integer, real, float, double, string, boolean, objectarray                。

如果您存儲的值是一個序列化的 JSON 時,那么 array 類型轉(zhuǎn)換將會非常有用。比如,您的數(shù)據(jù)表里有一個 TEXT 類型的字段存儲著序列化后的 JSON 數(shù)據(jù), 通過增加 array 類型轉(zhuǎn)換, 當(dāng)獲取這個屬性的時候會自動反序列化成 PHP 的數(shù)組:

/**
 * 需要被轉(zhuǎn)換成基本類型的屬性值。
 *
 * @var array
 */protected $casts = [
    'options' => 'array',];

           

現(xiàn)在,當(dāng)你使用 Eloquent 模型時:

$user = User::find(1);// $options 是一個數(shù)組...$options = $user->options;// options 會自動序列化成 JSON...$user->options = ['foo' => 'bar'];

           

           

模型事件

Eloquent 模型有很多事件可以觸發(fā),讓您可以在模型操作的生命周期的不同時間點(diǎn),使用下列方法綁定事件: creating, created, updating, updated, saving, saved, deleting, deleted, restoring,                restored。

當(dāng)一個對象初次被儲存到數(shù)據(jù)庫, creatingcreated 事件會被觸發(fā)。如果不是新對象而調(diào)用了 save 方法, updating / updated 事件會被觸發(fā)。而兩者的 saving / saved 事件都會被觸發(fā)。

使用事件取消數(shù)據(jù)庫操作

如果 creating 、 updatingsavingdeleting 事件返回 false 的話,就會取消數(shù)據(jù)庫操作

User::creating(function($user){
    if ( ! $user->isValid()) return false;});

           

注冊事件監(jiān)聽者的方式

您可以在 EventServiceProvider 中注冊您的模型事件綁定。比如:

/**
 * 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)
    {
        //    });}

           

           

模型觀察者

要整合模型的事件處理,可以注冊一個模型觀察者。觀察者類里要設(shè)定對應(yīng)模型事件的方法。例如,觀察者類里可能有 creating、 updating 、 saving 方法,還有其他對應(yīng)模型事件名稱的方法:

例如,一個模型觀察者類可能看起來如下:

class UserObserver {

    public function saving($model)
    {
        //    }

    public function saved($model)
    {
        //    }}

           

可以使用 observe 方法注冊一個觀察者實(shí)例:

User::observe(new UserObserver);

           

           

模型 URL 生成

當(dāng)你把一個模型實(shí)例傳遞給 route 或者 action 方法時,模型的主鍵會被插入到生成的 URI 中。比如:

Route::get('user/{user}', 'UserController@show');action('UserController@show', [$user]);

           

在這個例子中 $user->id 屬性會被插入到生成的 URL 的 {user} 這個占位符中。不過,如果你想使用其他的屬性而不是 ID 的話,你可以覆蓋模型的 getRouteKey 方法:

public function getRouteKey(){
    return $this->slug;}

           

           

轉(zhuǎn)換成數(shù)組 / JSON

將模型數(shù)據(jù)轉(zhuǎn)成數(shù)組

當(dāng)構(gòu)建 JSON API 時,您可能常常需要把模型和關(guān)聯(lián)對象轉(zhuǎn)換成數(shù)組或JSON。所以Eloquent里已經(jīng)包含了這些方法。要把模型和已載入的關(guān)聯(lián)對象轉(zhuǎn)成數(shù)組,可以使用 toArray 方法:

$user = User::with('roles')->first();return $user->toArray();

           

注意也可以把整個的模型集合轉(zhuǎn)換成數(shù)組:

return User::all()->toArray();

           

將模型轉(zhuǎn)換成 JSON

要把模型轉(zhuǎn)換成 JSON,可以使用 toJson 方法:

return User::find(1)->toJson();

           

從路由中返回模型

注意當(dāng)模型或集合被轉(zhuǎn)換成字符串類型時會自動轉(zhuǎn)換成 JSON 格式,這意味著您可以直接從路由返回 Eloquent 對象!

Route::get('users', function(){
    return User::all();});

           

轉(zhuǎn)換成數(shù)組或 JSON 時隱藏屬性

有時您可能想要限制能出現(xiàn)在數(shù)組或 JSON 格式的屬性數(shù)據(jù),比如密碼字段。只要在模型里增加 hidden 屬性即可

class User extends Model {

    protected $hidden = ['password'];}

           

注意: 要隱藏關(guān)聯(lián)數(shù)據(jù),要使用關(guān)聯(lián)的方法名稱,而不是動態(tài)獲取的屬性名稱。

此外,可以使用 visible 屬性定義白名單:

protected $visible = ['first_name', 'last_name'];

           

               有時候您可能想要增加不存在數(shù)據(jù)庫字段的屬性數(shù)據(jù)。這時候只要定義一個獲取器即可:

public function getIsAdminAttribute(){
    return $this->attributes['admin'] == 'yes';}

           

定義好獲取器之后,再把對應(yīng)的屬性名稱加到模型里的 appends 屬性:

protected $appends = ['is_admin'];

           

把屬性加到 appends 數(shù)組之后,在模型數(shù)據(jù)轉(zhuǎn)換成數(shù)組或 JSON 格式時就會有對應(yīng)的值。在 appends 數(shù)組中定義的值同樣遵循模型中 visiblehidden 的設(shè)定。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號