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_atupdated_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 都可以輸入兩個參數:

  1. 用於判斷該資料是否存在
  2. 建立資料的附加屬性

因此,上述可以需求寫成:

// 用 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();
}
Categories: Laravel