Eloquent ORM
PHP:7.2
Laravel:5.7
Larvel 提供了 Eloquent ORM
,只要透過 Model
就可以簡單且快速的操作資料表。
在開始之前,必須先設定 config/database.php
或 .env
與資料庫連線。
建立 Model
建立 Model 可以透過 Artisan
的指令建立。
# 將 Member 建立在 app/Models 底下
php artisan make:model Models/Member
設定 Model
在建立完成以後,可以針對 Model 進行一些操作設定。
範例
namespace AppModels;
// 所有 Model 都繼承這個 class
use Illuminate\Database\Eloquent\Model;
class Album extends Model
{
// 設定連線(參考 config/database.php 的連線名稱)
protected $connection = 'kkbox';
// 設定資料表名稱
protected $table = 'album';
// 預設 primaryKey 為 id,如果不是的話需要另外設定
protected $primaryKey = 'album_id';
// 關閉 timestamps 控制(預設為開啟)
public $timestamps = false;
// 將時間格式改成 Seconds since the Unix Epoch
protected $dateFormat = 'U';
}
Notice:
Eloquent
操作時預設會去修改created_at
、updated_at
這兩個時間欄位,如果原本的資料表沒有這兩個欄位,只要將$timestamps
設成false
就可以正常運作了。
Model 基本使用
設定完成以後,便可以使用 Model 操作資料表。
透過 Model 取得資料
use App\Models\Album;
// 取得資料表所有資訊
$albums = Album::all();
// 轉換成 Array 輸出
$albums->toArray();
// 透過 primary key 搜尋,回傳 Model 物件
$album = Album::find($album_id);
// 特定條件搜尋,回傳 Illuminate\Database\Eloquent\Collection
物件
// 只要使用 get() 以後,就會回傳 Collection 物件
$album = Album::where('album_name', '誰明浪子心')->get();
// 取得第一筆資料,回傳 Model 物件
$album = Album::where('album_name', '誰明浪子心')->get();
// 取 10 筆資料
$album = Album::where('status', '<', 2)->take(10)->get();
// 計算總數
$count = Album::where('status', '<', 2)->count();
// 如果沒有找到指定資料就 throw exception
$album = Album::findOrFail($album_id);
$album = Album::where('status', '<', 2)->firstOrFail();
// 取得包含已經刪除的資料(deleted_at
not null)
$albums = Album::withTrashed()->get();
透過 Model 新增、修改、刪除
// Insert
$album = new Album;
$album->album_name = 'Johnson';
$album->status = 1;
$album->save();
// Update
$album = Album::find($album_id);
$album->name = 'John';
$album->save();
// Delete
$album = Album::find($album_id);
$album->delete();
// destroy(傳入 Primary key 進行批次刪除)
Member::destroy([1, 2, 3]);
Mass Assignment
除了逐個欄位修改之外,也可以透過 Mass Assignment
批次修改,但 Model 必須加入 fillable(白名單)
或 guarded(黑名單)
來允許操作。
加入 fillable
namespace AppModels;
use Illuminate\Database\Eloquent\Model;
class Album extends Model
{
protected $connection = 'kkbox';
protected $table = 'album';
protected $primaryKey = 'album_id';
public $timestamps = false;
protected $fillable = [
'album_name',
'status'
];
}
利用 Mass Assignment 批次操作
// 批次建立
// 因為 test 不在白名單內,因此不會被填入
Album::create([
'name' => 'Johnson',
'status' => 3,
'test' => 'xxxx'
]);
// 多欄位更新
$album = Album::find($album_id);
$album->fill([
'name' => 'TEST'
]);
$album->save();
Query Scopes
如果有一些大量重複性的 query,可以在 Model 中建立 Query Scopes
,定義常用的規則。
以下就用 Local Scopes 來舉例
定義 Scope
namespace AppModels;
use Illuminate\Database\Eloquent\Model;
class Album extends Model
{
protected $connection = 'kkbox';
protected $table = 'album';
protected $primaryKey = 'album_id';
public $timestamps = false;
protected $fillable = [
'album_name',
'status'
];
// 定義 scope 必須以 scope 單字為開頭
public function scopeJohnson($query)
{
return $query->where('name', 'Johnson');
}
}
使用
$album = Album::johnson()->get();
Relationships
操作資料表最常見的用法就是不同資料表間的 Join,除了透過 Query Builder 直接 Join 之外,也可以直接在 Model 中建立 Relationships
,透過 Simple query 達成相同的效果。
設定 Relationships
namespace AppModels;
use Illuminate\Database\Eloquent\Model;
class Album extends Model
{
protected $connection = 'kkbox';
protected $table = 'album';
protected $primaryKey = 'album_id';
public $timestamps = false;
protected $fillable = [
'album_name',
'status'
];
// hasOne 代表一對一對應
public function label()
{
// hasOne('Model', 'foreign_key', 'local_key')
// 如果 local_key 沒有填,就是自動對應到 album table 的 Primary key
return $this->hasOne('FallZuBallAlbumLabel', 'album_id');
}
// hasMany 代表一對多對應
public function songs()
{
// hasMany('Model', 'foreign_key', 'local_key')
// 如果 local_key 沒有填,就是自動對應到 album table 的 Primary key
return $this->hasMany('FallZuBallSong', 'album_id');
}
// belongsTo 一對一反向對應(Artist Model 是設定 hasOne album)
public function artist()
{
// belongsTo('Model', 'foreign_key')
return $this->belongsTo('FallZuBallArtist', 'artist_id');
}
}
使用 Eager Loading 取得 Relation 的資料。
範例
// 使用 songs relation
Album::with(['songs'])->find($album_id)->toArray();
// 針對 Relation 進行欄位定義
Album::with([
'songs' => function ($query) {
// 自訂 Relation Model 的欄位
// 注意:這邊一定要有選擇兩張 table relation 的相關欄位,不然就會拉不到資料
$query->select(['song_id', 'song_name', 'album_id', 'artist_id']);
},
// Relation 的 Model 可以透過 'A.B' 的方式繼續往下 Relation
'songs.artist'
])->find($album_id)->toArray();
如果想要針對 Relation 的 Model 做判斷,就必須使用 whereHas
。
範例
// 撈出有 為何分離
這首歌的專輯
$name = '為何分離';
Album::whereHas('songs', function ($query) use ($name) {
$query->where('song_name', $name);
})->get()->toArray();
另外,如果有修改到 relation model 的資料,就不能再使用 save()
儲存,而是使用 push()
範例
// save() 只會針對 Album Model 儲存,而 push() 會把 relation model 中修改的值一併儲存
$album = Album::with(['songs'])->find(2);
$album->album_grid = 'album_grid';
$album->songs[0]->song_composer = 'song_composer';
$album->push();
Other Creation Methods
Eloquent
也根據實務開發上常見的需求,提供了幾個快速建立、修改方法:
- firstOrCreate
- firstOrNew
- updateOrCreate
這三個方法使用方式基本上都相同,唯一不同的只是使用的情境。
以 firstOrCreate
來說,當你的資料庫沒有該筆資料時,變會自動新建一筆,反之,便會回傳該筆值的 Model Object。
範例
// DB 無資料時建立新資料,並回傳該資料的 Model Object
$obj = Test::firstOrCreate(['name' => 'Johnson']);
// 因為 DB 已有相同的資料,因此會直接改成 first 取得該 Model Object(不會建新資料)
$obj = Test::firstOrCreate(['name' => 'Johnson']);
這是最基本的用法,不過這樣使用會有一個隱性的問題,通常實務上不會只有一個欄位,因此會寫成以下這樣:
// 此行會再建一筆新資料
$obj = Test::firstOrCreate([
'name' => 'Johnson', // DB 以有此名稱
'email' => 'johnsonlu@kkbox.com' // 新資料
'status' => 0 // 新資料
]);
這樣的寫法,它的執行 SQL 會是:
select * from test
where (name
= 'Johnson' and email
= 'johnsonlu@kkbox.com' and status
= 0) limit 1
如果原本只是要判斷 name
是否存在,便會造成重複建檔的問題。
事實上,這類型的 method 都可以輸入兩個參數:
- 用於判斷該資料是否存在
- 建立資料的附加屬性
因此,上述可以需求寫成:
// 用 name 當 key 判斷資料是否存在
$obj = Test::firstOrCreate([
'name' => 'Johnson'
], [
'status' => 0
]);
其他 method 用法
// 如果沒有值,取得一個新的 Model Instance
$obj = Test::firstOrNew(['name' => 'Johnson']);
// 需要 save() 才會真正建資料
$obj->save();
// 如果有值,就會直接取得該資料的 Model Object
$obj = Test::firstOrNew(['name' => 'Johnson']);
dd($obj);
// 如果沒有值,建一筆新資料,並取得該資料的 Model Object
// 如果有值,更新資料後,取得該資料的 Model Object
$obj = Test::updateOrCreate([
'name' => 'Yolanda'
], [
'status' => 1
]);
Soft Deleting
在大型專案中,通常不太會直接刪除 row data,都是透過特定欄位註記而已。而 Eloquent
也提供此功能。
Notice: 要使用此功能,資料表必須有
deleted_at
欄位。
使用前必須先在 Model 裡使用 Illuminate\Database\Eloquent\SoftDeletes;
Model
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Album extends Model
{
// 使用 SoftDeletes Trait
use SoftDeletes;
protected $table = 'album';
protected $dateFormat = 'U';
}
這樣使用 delete()
時,Eloquent
就會將時間戳記寫在 deleted_at
欄位( Eloquent
取得資料的條件,預設都會判斷 deleted_at
)。
Album::find($album_id)->delete();
EAV 架構快速操作
EAV 架構是很常見的資料表設計模式。但在開發上,資料更新的過程非常繁瑣。
Eloquent
可以透過簡單的小技巧搭配 except()
方法快速操作。
範例
$params = [
[
'album_id' => 1,
'attribute' => 'album_name',
'value' => '誰明浪子心'
],
[
'album_id' => 1,
'attribute' => 'artist_name',
'value' => '王傑'
]
];
$mapIDs = [];
foreach ($params as $columnObj) {
$eavObj = AlbumEAV::updateOrCreate([
'album_id' => $columnObj['album_id'],
'attribute' => $columnObj['attribute']
], [
'value' => $columnObj['value']
]);
// 取得有修改或新增的 ID
$mapIDs[] = $eavObj->id;
}
// 取出分母
$originEAV = AlbumEAV::where('album_id', 1)->get();
// 透過 except(),撈出未被更動到的資料
$exceptEav = $originEAV->except($mapIDs);
foreach ($exceptEav as $exceptObj) {
// 如果要 Overwrite,就可以將沒被更新到的刪除
$exceptObj->delete();
}