Laravel Eloquent ORM
Laravel PHP

Laravel Eloquent ORM

商業,創業,業務,職涯,美食,葡萄酒,閱讀,網路科技。

從 Larry 創業以及商業的經驗,希望以白話的口吻,介紹給大家這個商業的世界。

FB粉專會頻繁地更新 Larry 對於商業、社會、人生的觀察與心得,歡迎大家追蹤互動~

https://laravel.com/docs/5.3/eloquent
https://laravel.com/docs/5.3/eloquent-relationships

Mass Assignment

$flight = AppFlight::create(['name' => 'Flight 10']);

mass assignment 目標的 ORM class「一定」要指定 $fillable 或是 $guarded。以上面例子來說,一定要指定 $fillable = [‘name’] 或是 $guarded = [‘other_attribute’],不能不指定 (即使 $guarded = [] 也行)。官網特別提到,建議用 $guarded 而不是 $fillable (當然還是可以用 $fillable)。原因是開發者該關注的是那些不能隨意被更改的欄位。最後提一下,以上 mass assignment 規則「不適用」一般儲存/更新,如下面例子,即使 $guarded = [‘name’] 一樣能儲存:

$flight = new Flight; 
$flight->name = $request->name; 
$flight->save();

或是

$flight = Flight::find(5); 
$flight->name = $request->name; 
$flight->save();

Soft Delete

這是一樣很實用的功能。之前跟朋友聊到,larry 自己也碰到過,有的客戶很喜歡把「已完成」的訂單刪掉 (即使已經做了 archive 頁面)。有經驗的朋友就知道,不要真把已完成的訂單刪啦,要是之後有任何客人/用戶有問題怎麼辦,要去 trace DB 嗎?

Laravel 把 soft delete 的功能包裝的滿好的,在你的 migration file 加上:

$table->softDeletes();

ORM class 加上

use SoftDeletes; // 寫在 class 裡, 它是 trait 
protected $dates = ['deleted_at'];

$table-> sodtDeletes() 會在你的 table 加上 deleted_at 欄位 (type timestamp), 當使用者刪除這個 instance 的時候, deleted_at 會自動填上時間。當你下次 query 這筆資料,就會 query 不到 (然而這筆資料還在 DB)。如果你要 query soft deleted 的資料:

$flights = Flight::withTrashed()->where('your_query_attr', 'your_query_value')->get();

或是

$flights = Flight::onlyTrashed()->where('your_query_attr', 'your_query_value')->get();

用 restore method 可以回復 soft deleted item (用上方 withTrashed / onlyTrashed method 拿到 instance):

$flight->restore();

如果你要真正刪除:

$flight->forceDelete();

Local Scopes

local scope 也是實現商業邏輯很好的實作。在 ORM class 加上

public function scopePopular($query) { 
    return $query->where('votes', '>', 100); 
}

caller side (拿掉 “scope”, “popular” 的 “p” 小寫)

$flights = Flight::popular()->get();

local scope function 也可以吃參數,就不在這邊重複了。

Events

很簡單,在 AppServiceProvider::boot 加上 (updated 是舉例,可以是 created、saved、等)

Flight::updated(function ($flight) { 
    // your callback code 
});

比較漂亮的做法是用 observer class (observer pattern), 在 App/Observers 加上 FlightObserver.php

class FlightObserver { 
    public function updated(Flight $flight) { 
        // your callback code 
    } 
}

AppServiceProvider::boot 只要加上一行

Flight::observe(FlightObserver::class);

這樣就可以了, 不過要注意像 updated,如果 update 的資料跟原資料相同,updated event「不會」觸發。

Relationships: one to one

例如 User 與 Flight 是一對一,在 class User 裡加上

public function flight() { 
    return $this->hasOne('AppFlight'); 
}

上面假設 flights table 裡已經有 user_id 欄位,如果你不想更動 flights table,可以使用

public function flight() { 
    return $this->hasOne('AppFlight', 'your_foreign_key'); 
}

指定你要的 foreign key。生成時:

$request->user()->flight()->create([ 
    'name' => 'China Airlines', 
]);

上例是 massive assignment,注意你的 class Flight 有無設定 $guarded。有 user instance 要取出 flight 時:

$flight = $user->flight; // 注意 flight 沒有括號, laravel 稱此為 "Dynamic Property"

反向,如果有 flight instance 要取出對應的 user, 在 class Flight 加上

public function user() { 
    return $this->belongsTo('AppUser'); // 一樣第二個 argument 可以是 your_foreign_key 
}

caller side

$user = $flight->user; // 注意 user 一樣沒有括號

Relationships: one to many

public function flights() { 
    return $this->hasMany('AppFlight'); 
}

caller side

$flights = $user->flights;

反向一樣是 belongsTo。

Eager Loading

在使用 dynamic property 時有可能會:

$books = AppBook::all(); 

foreach ($books as $book) { 
    echo $book->author->name; 
}

author 是 dynamic property,所以每 loop 依次就等於下一次 sql query,會有效能上的問題 (dynamic property 不真是 property,它是一個 sql query)。使用 Eager Loading 可以改寫成

$books = AppBook::with('author')->get(); // sql query 在這邊 

foreach ($books as $book) { 
    echo $book->author->name; // author 就是真 property 了 
}

Inserting & Updating Related Models

在一對多的情況 (其實也包含一對一的情況),使用 save method 可以新增一筆 child data:

$comment = new AppComment(['message' => 'A new comment.']); 
$post = AppPost::find(1); 
$post->comments()->save($comment);

注意這邊的 save method 與單純新增一筆資料有一點不一樣,單純新增一筆 comment

$comment = new AppComment(['message' => 'A new comment.']); 
$comment-> save();

單純新增一筆 comment 時 $comment 物件為 save method 的「caller」,relation save 時 $comment 物件為 save method 的「callee」。另外,也可以用 massive assignment create

$post = AppPost::find(1); 
$comment = $post->comments()->create([
    'message' => 'A new comment.', 
]);

在 Belongs-To 關係中如果要更新 child model 的 foreign key:

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

注意這例子中 user「belongs to」account,更新的是 user 的 foreign key,不是非常直覺。如果要將 child model 的 foreign key 設為 null:

$user->account()->dissociate(); 
$user->save();

注意 child model  的 foreign key 要是 nullable,不然會 error。

Touching Parent Timestamps

如果你要在更新 child model 時同時更新 parent model 的 updated_at 時間,在 child model 中加上一行

protected $touches = ['post'];

這樣在更新 comment 時就會更新 post 的 updated_at 時間。

商業,創業,業務,職涯,美食,葡萄酒,閱讀,網路科技。

從 Larry 創業以及商業的經驗,希望以白話的口吻,介紹給大家這個商業的世界。

FB粉專會頻繁地更新 Larry 對於商業、社會、人生的觀察與心得,歡迎大家追蹤互動~