Laravel Eloquent Model使用小貼士
說一個大結論
我可以直接在MySQL做好資料表,在Laravel手刻Model直接操作,
不用像Django × SQLite跑任何異動多數後端框架的異動(migration)或許能理解成:
為了從後端服務端發動去創造新或修改資料表等操作
還做了一個實驗 資料庫已經有幾行資料列時新增欄位
- 會不會破壞既有Laravel Model?
- 能否不需要執行異動也能使用該欄位? 其實根據上方的原理就想當然爾了,但還是可以試一下
具體實驗方法:
- 先在該表填入幾筆假資料
- 查詢及insert
- 然後再開一個新欄位
- 重送查詢及insert
20240322另個實驗:不完整更新
也是ok,這樣就不用擔心影響到程式內既有的update語法了
這樣整理起來,新增主鍵以外的欄位真的影響不大
但如果是:
- 改主鍵
- 改欄位型態
- 減少欄位 會影響的地方就很多了
查跟刪的方面像是用了find()或直接用主鍵刪除這種方法..
增跟改的方面,這時以前寫在程式內還沒移除的舊欄位會被提示Unknown column '舊欄位' in 'field list';又或是改變欄位型態導致傳入值變得不匹配
這些都是要去調整的
初次使用
(假設已經是成熟專案有既有Model,新建Model手刻法看下方..)
<?php
DB::connection('設定檔名稱')->beginTransaction();
try {
$list = [內含要加入的資料,要跟欄位對上];
Model::insert($list); // 或其他各種操作
// 註:如果是where然後get(),會回傳一個collection 或以下這樣也是
// $users = App\User::all();
// $users[0]->name; // 取用collection的內容
// 最後才commit 酷!
DB::connection('設定檔名稱')->commit();
} catch (\Excepttion $e) {
DB::connection('設定檔名稱')->rollback();
}
上方提到回傳值為collection的議題提到一種取用方式,或用collect($result)->toArray()轉換,那還能怎麼取用呢?
<?php
$userNames = $users->map(function ($user) {
return $user->name;
});
考慮沒有要打印SQL,也沒有要接續查詢 這時我可能
get()後直接->toArray()其實原本get()方法回傳的型態就是Collection了,多一層collect()有點多餘
那如果只想取特定欄位呢?看這篇[^1]
Function helper詳解
常用的
<?php
// 增
MyModel::insert([
'name' => 'John',
'email' => 'john@gmail.com',
'password' => Hash::make($pwd),
]);
MyModel::create([
'name' => 'John',
'email' => 'john@gmail.com',
'password' => Hash::make($pwd),
]); // 如果有開啟紀錄create_time的設定且時間戳設定為True,create會多帶時間戳,insert則不會,不想要可能不要用create或是修改設定
// 另外就是他可能會出現MassAssignmentException,要你額外做設定才能用create函數幫手
// 查
MyModel::find($pk); // 只能用主鍵,注意!!我在這裡踩了個坑,就是這方法他會直接返回單層的數據結構,要拿東西就接->username像這樣,無須再接續get()..
// 莫像我get()又first()總拿一個0索引號🫠
MyModel::where(阿巴阿巴條件)->get(); // 拿全部結果,回傳型態為Collection
$query = MyModel::where(阿巴阿巴條件)->first(); // 返回Model或NULL
// 這個...水很深 https://stackoverflow.com/questions/24531312/eloquent-first-if-exists
MyModel::where(DB::raw('column1 + column2'), '<', 1000)->get(); // Raw Expressions的使用
MyModel::where('id', '!=' , 2); // 應該是等價於DB支援的運算符 MySQL是<>跟!=都能用
// and not 用whereNot()還原
// 子查詢可以用joinSub()
// 但太複雜乾脆whereRaw算了...?
$users = User::select('欄位A', '欄位B')->get(); // 只拿特定欄位
// 但比較怪異的是Facade DB的select有執行查詢的功能啊..
DB::select('SELECT * FROM users WHERE age > ?', [18]);
$user = User::where('name', 'John')->select('id', 'name')->get(); // 與where的組合技
// 還有一個whereIn()用法大同小異
// 比較怪的是groupBy()除了傳入欄位,也允許你傳raw,像SQL date_format('格式', 欄位)這種的,雖然外框加了`但我在資料庫執行語句發現絲毫不影響查詢🤔🤔🤔(這其實應該是SQL小抄那邊的內容)
// 當然嚴謹點還是要搭配DB::raw()..
// 改
MyModel::where(阿巴阿巴條件)->update(['status' => 0]);
// 刪
MyModel::destroy($id); // 可以批量刪除
MyModel::where('id', $id)->delete();
// 還有軟刪除有興趣可以去查查
如何還原where子句中的各種比較?大概如下
<?php
MyModel::where('x', '比較運算子', $要比的數)
->where('a', 'a') // and where a = 'a'
如何還原括號
where a = 'a'
and where (b = 'b' and c > 1)
會類似這樣:
<?php
->where('a', 'a')
->where(function ($query) use ($要傳入的傢伙如果有的話) { // 不要的話就把use跟後面那段去掉就好
$query->where('b', 'b')
->where('c', '>', 1);
})
Raw Expressions
中文官方文件跳到Raw Expressions的段落
用處:這樣where中就可以先運算好幾個欄位再做比較
一些疑難雜症
- 如果要比較日期,但又不想用Carbon,能在使用Model的狀況下下原生SQL嗎?
- 要留意where裡面用DB::raw會執行包含is的錯誤語法,可以改用whereRaw
- 主要是
where()這個語法會把傳入值組合成前者 比較運算子 後者這樣的SQL語句;select()倒是不會有這個問題
- 主要是
- 要留意where裡面用DB::raw會執行包含is的錯誤語法,可以改用whereRaw
toSql()
在get前改用toSql()可以打印SQL語句
beginTransaction()
自動記錄建立與更新時間
但即便如此你還是可以在update時傳入自己弄的format日期,這應該只是說沒有的時候他會幫你自己做..
話說insert update日期是可以使用符合格式的String,那比較呢?
insert update時可以插入php內置函數date(格式),這個方法預設給的就是今天日期的格式化字串(可看官方文件)
比較應該也是可使用符合格式的字串,這個toSql便能看出來語法是合理的
還有除了format後的str可以直接插入,型態為Carbon實例像Carbon::now()也是可以的
手刻常見的Model眾生相
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class MyModel extends Model
{
protected $connection = 'DB名稱';
protected $table = '表名';
protected $primaryKey = 'PK名';
public $incrementing = false; // 自增嗎?
public $timestamps = true; // 要自動時間戳嗎?
const CREATED_AT = 'create_date'; // 自增建立日期欄位
const UPDATED_AT = null; // 自增異動日期欄位
}
is/is not null語句的還原
whereNotNull() whereNull()
神奇合併查詢代替UNION ALL
補充
- 通常實務排版時,箭頭會跟著換行
- 這有個妙用,當中間有一part不要時,我註解那行不就好了~~前輩教的讚👍
- 上次提到的Facade/DB接地氣寫法,有個函數幫手叫做
select用於單純選擇可以了解下 - 跟Spring Data JPA相比,Laravel Model要委託它的事情雖然少一點,但也因此很多異動程式不會提示,反而耦合度比較高..?JPA看起來託管代碼是多一點,但正是透過抽象層、實體類的建置去減少程式與資料庫之間的耦合
- 如何簡單分辨耦合高低,通常高耦合會造成難以維護,你改一個地方,發現自己還要純手工修改超級多地方;另外複雜的物件託管關係不一定是高耦合,例如抽象工廠模式雖然代碼比較多,但反而做了解耦合的,絕對不能單純依照代碼的複雜度、被呼叫的次數去看耦合這件事情
@@
- 如何簡單分辨耦合高低,通常高耦合會造成難以維護,你改一個地方,發現自己還要純手工修改超級多地方;另外複雜的物件託管關係不一定是高耦合,例如抽象工廠模式雖然代碼比較多,但反而做了解耦合的,絕對不能單純依照代碼的複雜度、被呼叫的次數去看耦合這件事情
Full Join密技
沒好好select會爛掉的(不過這邊是Laravel admin的Model)
一SQL語句
select
chiikawa_profile.id,
chiikawa_profile.created_at,
chiikawa_profile.name,
chiikawa_profile.birthday,
chiikawa_profile.sign,
sign_lucky_color.lucky_color,
sign_lucky_jewelry.lucky_jewelry
from chiikawa_profile
left join sign_lucky_color on chiikawa_profile.sign = sign_lucky_color.sign
left join sign_lucky_jewelry on chiikawa_profile.sign = sign_lucky_jewelry.sign
order by chiikawa_profile.id asc
要這樣寫$grid才不會爛掉
<?php
$grid = new Grid(new ChiikawaProfile());
$grid->model()->selectRaw('
chiikawa_profile.id,
chiikawa_profile.created_at,
chiikawa_profile.name,
chiikawa_profile.birthday,
chiikawa_profile.sign,
sign_lucky_color.lucky_color,
sign_lucky_jewelry.lucky_jewelry')
->leftJoin('sign_lucky_color', 'chiikawa_profile.sign', '=', 'sign_lucky_color.sign')
->leftJoin('sign_lucky_jewelry', 'chiikawa_profile.sign', '=', 'sign_lucky_jewelry.sign');
union應該同理
原因出在沒有好好select欄位,selectRaw('*')會爛掉,ORM跟原生SQL還是不太一樣的..可能要看他打印出來的語句...?
問題是我印出來兩種都是可以執行的欸...
[2024-04-26 02:11:30] local.INFO: select
chiikawa_profile.id,
chiikawa_profile.created_at,
chiikawa_profile.name,
chiikawa_profile.birthday,
chiikawa_profile.sign,
sign_lucky_color.lucky_color,
sign_lucky_jewelry.lucky_jewelry from "chiikawa_profile" left join "sign_lucky_color" on "chiikawa_profile"."sign" = "sign_lucky_color"."sign" left join "sign_lucky_jewelry" on "chiikawa_profile"."sign" = "sign_lucky_jewelry"."sign"
[2024-04-26 02:11:30] local.INFO: select * from "chiikawa_profile" left join "sign_lucky_color" on "chiikawa_profile"."sign" = "sign_lucky_color"."sign" left join "sign_lucky_jewelry" on "chiikawa_profile"."sign" = "sign_lucky_jewelry"."sign"
還是這是grid model的鍋..............
有可能合併兩個Model查詢結果嗎...?待考
網路上常提到關於result的merge似乎是關於collection?
沒查到東西會返回甚麼?
where(條件)->first()沒查到東西會返回NULL
where(條件)->get()呢?
如果在Laravel中使用get方法查詢資料,而該查詢沒有結果,你會得到一個空的集合(EloquentCollection)。這意味著即使沒有匹配的資料,你仍然會收到一個集合物件,但該集合內不包含任何資料。 --By.Copilot
至於where(條件)->get()->first()那個first()便是針對Collection的操作了
其實在Model中關聯挺耗費記憶體的欸
Model中自定義方法的調用方法?
- 關係相關的方法可以用
with('func名稱'),像是user啊post啊 - 9代新增屬性提取
Model::find($pk)->func直接調用方法,不用括號(Copilot的說法是:注意,我們在調用時不需要加括號 (),因為我們想要的是關聯的結果,而不是調用方法本身。)- 補充一個平凡可見的Laravel知識,
::只能調用靜態方法 - (待考)假設
Model->func,那似乎會拿到整張關聯表,還不算查詢結果
- 補充一個平凡可見的Laravel知識,
覺得fillable很煩可以試試這個
想要取值沒煩惱,從看回傳值提示開始
2024五月初在取值上踩了坑,搞得心情很浮躁
反省發生的原因
牽涉到:
- 我不是用沒提示的編輯器寫(然後我也不知道那啥型態,
var_dump()不會用?) - 就是不看VSCode飄過去會顯示的回傳值
Model關聯查詢取不到值會傳什麼
<?php
$something = $first_result->getXxx->something;
會得到ErrorException: Trying to get property 'order_id' of non-object而非null