本章帶你了解下模型的一些高級(jí)技巧具篇,這些技巧會(huì)讓你在使用模型的過程中更加高效和簡(jiǎn)單,學(xué)習(xí)內(nèi)容主要包含:
條件查詢
我們前面的大多數(shù)例子都是使用主鍵進(jìn)行模型數(shù)據(jù)查詢马昙,事實(shí)上,業(yè)務(wù)邏輯并非都是如此簡(jiǎn)單,條件查詢的必要性是存在的帆调。
最簡(jiǎn)單的方法是直接使用
Db
類的查詢構(gòu)造器查詢,方法是在模型中靜態(tài)調(diào)用Db
類(其實(shí)是查詢類)的任何方法(包括動(dòng)態(tài)查詢)進(jìn)行查詢豆同。
// 查詢單個(gè)記錄
User::where('name', 'thinkphp')->find();
// 調(diào)用動(dòng)態(tài)查詢方法
User::getByName('thinkphp');
// 查詢數(shù)據(jù)集
User::where('id', '>', 0)->limit(10)->order('id desc')->select();
// 刪除數(shù)據(jù)
User::where('status', 0)->delete();
如果你的查詢代碼在模型內(nèi)部番刊,可以直接支持動(dòng)態(tài)方式調(diào)用查詢構(gòu)造器的方法,用法就是從靜態(tài)調(diào)用改為動(dòng)態(tài)調(diào)用诱告。
// 查詢單個(gè)記錄
$this->where('name', 'thinkphp')->find();
// 調(diào)用動(dòng)態(tài)查詢方法
$this->getByName('thinkphp');
// 查詢數(shù)據(jù)集
$this->where('id', '>', 0)->limit(10)->order('id desc')->select();
// 刪除數(shù)據(jù)
$this->where('status', 0)->delete();
不過類似的查詢場(chǎng)景應(yīng)當(dāng)極力避免(個(gè)別場(chǎng)景例如查詢范圍等可能會(huì)涉及到此類用法)撵枢,查詢操作應(yīng)當(dāng)是靜態(tài)調(diào)用,更新和刪除操作則是動(dòng)態(tài)方法調(diào)用精居。如果是在模型方法中查詢其它模型的數(shù)據(jù)锄禽,第一種靜態(tài)調(diào)用方式仍然適用。尤其不建議使用table
方法在同一個(gè)模型實(shí)例中切換數(shù)據(jù)表查詢靴姿,在模型中動(dòng)態(tài)設(shè)置table
屬性的方式更加不可任值(經(jīng)常發(fā)現(xiàn)這種奇葩的用法),模型和數(shù)據(jù)表以及相應(yīng)的業(yè)務(wù)邏輯是應(yīng)當(dāng)在創(chuàng)建的時(shí)候就相對(duì)固定的佛吓,應(yīng)當(dāng)極力避免在一個(gè)模型對(duì)象實(shí)例中查詢操作多次不同數(shù)據(jù)宵晚。
模型查詢的原則應(yīng)當(dāng)是每個(gè)模型對(duì)象實(shí)例操作一個(gè)唯一記錄,對(duì)于數(shù)據(jù)集來(lái)說(shuō)這個(gè)原則也不變维雇,只是每個(gè)數(shù)據(jù)集對(duì)象實(shí)例則包含多個(gè)模型對(duì)象實(shí)例而已淤刃。
對(duì)于自定義查詢,如果統(tǒng)一使用模型類提供的get
和all
進(jìn)行查詢也一樣可以達(dá)到目的吱型,改成下面代碼即可:
// 查詢單個(gè)記錄
User::get(['name' => 'thinkphp']);
// 查詢數(shù)據(jù)集
User::all(function ($query) {
$query->where('id', '>', 0)
->limit(10)
->order('id desc');
});
// 刪除數(shù)據(jù)
User::destroy(['status' => 0]);
User::destroy(function ($query) {
$query->where('id', 'in', [1, 2, 3]);
});
get
逸贾、all
以及destroy
方法的參數(shù)用法記住一個(gè)原則,如果是數(shù)字津滞、字符串或者普通數(shù)組都表示一個(gè)或者多個(gè)主鍵铝侵,如果是索引數(shù)組則表示查詢條件,閉包則支持查詢條件以外的其它鏈?zhǔn)讲僮鞣椒ùバ臁?duì)于get
方法的參數(shù)最好做一次非null
檢查咪鲜,否則查詢的就會(huì)是第一個(gè)數(shù)據(jù)(V5.0.8+
已經(jīng)改進(jìn),不需要檢查是否為null
了)撞鹉。
查詢范圍
對(duì)于一些常用的查詢條件疟丙,我們可以事先定義好颖侄,以便快速調(diào)用,這個(gè)事先定義的查詢條件方法有一個(gè)統(tǒng)一的前綴scope
隆敢,我們稱之為查詢范圍发皿,例如下面給User
模型定義了兩個(gè)查詢范圍方法。
<?php
namespace app\index\model;
use think\Model;
class User extends Model
{
// email查詢
protected function scopeEmail($query)
{
$query->where('email', 'thinkphp@qq.com');
}
// status查詢
protected function scopeStatus($query)
{
$query->where('status', 1);
}
}
現(xiàn)在我們直接使用
$users = User::scope('email,status')->select();
或者使用
$users = User::scope('email')->scope('status')->select();
生成的查詢語(yǔ)句是
SELECT * FROM `user` WHERE `email` = 'thinkphp@qq.com' AND `status` = 1
查詢范圍之外仍然可以使用額外的查詢條件拂蝎,例如:
$users = User::scope('email,status')
->where('nickname', 'like', '%think%')
->order('id desc')
->select();
查詢范圍方法必須首先被調(diào)用
查詢范圍方法支持額外的參數(shù)穴墅,例如scopeEmail
方法改為:
// email查詢
protected function scopeEmail($query, $email = '')
{
$query->where('email', $email);
}
查詢范圍的方法的第一個(gè)參數(shù)必須是查詢對(duì)象,并且支持多個(gè)額外參數(shù)温自。
然后玄货,使用下面的方式調(diào)用即可(帶參數(shù)調(diào)用的時(shí)候每次只能調(diào)用一個(gè)查詢范圍):
$list = User::scope('email', 'thinkphp@qq.com')->select();
查詢范圍有一個(gè)特殊的方法base
,一旦在模型中定義了base
方法后悼泌,無(wú)需顯式調(diào)用scope
方法松捉,系統(tǒng)會(huì)在每次查詢的時(shí)候自動(dòng)調(diào)用,我們稱之為全局查詢范圍馆里。
<?php
namespace app\index\model;
use think\Model;
class User extends Model
{
// 全局查詢范圍
protected static function base($query)
{
// 查詢狀態(tài)為1的數(shù)據(jù)
$query->where('status', 1);
}
// email查詢
protected function scopeEmail($query)
{
$query->where('email', 'thinkphp@qq.com');
}
}
當(dāng)使用下面的查詢操作
User::get(1);
User::scope('email')->select();
最后生成的SQL語(yǔ)句分別是:
SELECT * FROM `user` WHERE `status` = 1 AND `id` = 1 LIMIT 1
SELECT * FROM `user` WHERE `status` = 1 AND `email` = 'thinkphp@qq.com'
無(wú)論是什么查詢都會(huì)默認(rèn)帶上全局查詢范圍中的條件隘世。
可以臨時(shí)關(guān)閉全局查詢范圍進(jìn)行查詢
// 關(guān)閉全局查詢范圍
User::useGlobalScope(false)->get(1);
查詢范圍方法中不僅支持
where
方法,任何查詢構(gòu)造器的方法都可以被支持鸠踪。
字段過濾
經(jīng)常我們會(huì)直接使用表單提交的數(shù)據(jù)來(lái)作為模型數(shù)據(jù)寫入丙者,但并不是所有的數(shù)據(jù)都是數(shù)據(jù)表字段(直接寫入會(huì)導(dǎo)致數(shù)據(jù)庫(kù)異常),或者不希望某些數(shù)據(jù)被用戶通過表單提交的方式更新(為了安全或者邏輯考慮)营密,Request
類自身提供了only
方法來(lái)獲取部分想要的數(shù)據(jù)械媒,例如:
// 只獲取請(qǐng)求變量中的nickname和address變量
$data = request()->only(['nickname', 'address']);
// 獲取當(dāng)前用戶對(duì)象
$user = User::get(request()->session('user_id'));
// 更新用戶數(shù)據(jù)
$user->data($data, true)->save();
模型類提供了allowField
方法用于在數(shù)據(jù)寫入操作的時(shí)候設(shè)置字段過濾,從而避免數(shù)據(jù)庫(kù)因?yàn)樽侄尾淮嬖诙鴪?bào)錯(cuò)评汰,上面的寫法可以簡(jiǎn)化為纷捞。
// 獲取當(dāng)前用戶對(duì)象
$user = User::get(request()->session('user_id'));
// 只允許更新用戶的nickname和address數(shù)據(jù)
$user->allowField(['nickname', 'address'])
->data(requst()->param(), true)
->save();
如果僅僅是希望去除數(shù)據(jù)表之外的字段,可以使用
// 只允許更新數(shù)據(jù)表字段數(shù)據(jù)
$user->allowField(true)
->data(requst()->param(), true)
->save();
為了不必每次都調(diào)用allowField
方法被去,我們可以直接在模型類里面設(shè)置field
屬性主儡,例如:
<?php
namespace app\index\model;
use think\Model;
class User extends Model
{
protected $field = ['name', 'nickname', 'email', 'address'];
}
當(dāng)調(diào)用allowField
方法的時(shí)候,當(dāng)前模型實(shí)例中的該配置的值會(huì)被覆蓋惨缆。
如果使用的是模型的靜態(tài)方法(如create
和update
方法)進(jìn)行數(shù)據(jù)寫入的話糜值,可以使用下面的方式進(jìn)行字段過濾。
User::create(request()->param(), ['nickname', 'address']);
User::update(request()->param(), ['id' => 1], ['nickname', 'address']);
同樣可以傳入true
表示過濾非數(shù)據(jù)表字段
User::create(request()->param(), true);
User::update(request()->param(), ['id' => 1], true);
只讀字段
有些數(shù)據(jù)字段在寫入以后就不允許被更改踪央,例如name
字段和email
字段臀玄,那么我們可以設(shè)置該字段為只讀字段瓢阴,在更新的時(shí)候就會(huì)被自動(dòng)忽略掉畅蹂。
設(shè)置方式:
<?php
namespace app\index\model;
use think\Model;
class User extends Model
{
protected $readonly = ['name','email'];
}
舉個(gè)例子說(shuō)明下:
$user = User::get(5);
echo $user->name;
echo $user->email;
// 更改某些字段的值
$user->name = 'TOPThink';
$user->email = 'Topthink@gmail.com';
$user->address = '上海靜安區(qū)';
// 保存更改后的用戶數(shù)據(jù)
$user->save();
echo $user->name;
echo $user->email;
事實(shí)上,由于我們對(duì)name
和email
字段設(shè)置了只讀荣恐,因此只有address
字段的值被更新了液斜,而name
和email
的值仍然還是更新之前的值累贤。
軟刪除
在實(shí)際項(xiàng)目中,對(duì)數(shù)據(jù)頻繁使用刪除操作會(huì)導(dǎo)致性能問題少漆,因此不推薦直接物理刪除數(shù)據(jù)臼膏,而是用邏輯刪除替代,也就是下面要講的軟刪除示损。軟刪除的作用就是把數(shù)據(jù)加上刪除標(biāo)記渗磅,而不是真正的刪除,同時(shí)也便于需要的時(shí)候進(jìn)行數(shù)據(jù)的恢復(fù)检访。
要使用軟刪除功能始鱼,需要引入SoftDelete trait
,例如User
模型按照下面的定義就可以使用軟刪除功能:
<?php
namespace app\index\model;
use think\Model;
use traits\model\SoftDelete;
class User extends Model
{
use SoftDelete;
}
為了配合軟刪除功能脆贵,你需要在數(shù)據(jù)表中添加
delete_time
字段医清,ThinkPHP5的軟刪除功能使用時(shí)間戳類型(數(shù)據(jù)表默認(rèn)值為Null
),用于記錄數(shù)據(jù)的刪除時(shí)間卖氨。
如果你的軟刪除標(biāo)記字段名稱不是delete_time
的話会烙,需要添加屬性定義:
<?php
namespace app\index\model;
use think\Model;
use traits\model\SoftDelete;
class User extends Model
{
use SoftDelete;
protected $deleteTime = 'delete_field_name';
}
可以用類型轉(zhuǎn)換指定軟刪除字段的類型,建議數(shù)據(jù)表的所有時(shí)間字段統(tǒng)一使用autoWriteTimestamp
屬性規(guī)范時(shí)間類型(支持datetime
筒捺、date
柏腻、timestamp
以及integer
)。
<?php
namespace app\index\model;
use think\Model;
use traits\model\SoftDelete;
class User extends Model
{
use SoftDelete;
protected $autoWriteTimestamp = 'datetime';
protected $deleteTime = 'delete_field_name';
}
定義好模型后焙矛,我們就可以使用:
// 軟刪除
User::destroy(1);
// 真實(shí)刪除
User::destroy(1,true);
$user = User::get(1);
// 軟刪除
$user->delete();
// 真實(shí)刪除
$user->delete(true);
默認(rèn)情況下查詢的數(shù)據(jù)不包含軟刪除數(shù)據(jù)葫盼,如果需要包含軟刪除的數(shù)據(jù),可以使用下面的方式查詢:
User::withTrashed()->find();
User::withTrashed()->select();
如果僅僅需要查詢軟刪除的數(shù)據(jù)村斟,可以使用:
User::onlyTrashed()->find();
User::onlyTrashed()->select();
如果你的查詢條件比較復(fù)雜贫导,尤其是某些特殊情況下使用OR
查詢條件會(huì)把軟刪除數(shù)據(jù)也查詢出來(lái),可以使用閉包查詢的方式解決蟆盹,如下:
User::where(function($query) {
$query->where('id', '>', 10)
->whereOr('name', 'like', 'think');
})->select();
使用閉包查詢條件會(huì)在查詢條件兩邊添加括號(hào)孩灯,從而不會(huì)和軟刪除條件產(chǎn)生混淆或者沖突。
如果你的模型定義了base
基礎(chǔ)查詢逾滥,請(qǐng)確保添加軟刪除的基礎(chǔ)查詢條件峰档,例如:
protected function base($query)
{
// 添加軟刪除條件
$query->whereNull('delete_time')
// 添加額外的基礎(chǔ)查詢條件
->where('id','>',0);
}
自定義查詢類
默認(rèn)情況下,默認(rèn)使用的查詢類是核心內(nèi)置的think\db\Query
類寨昙,如果你需要自己擴(kuò)展額外的查詢方法讥巡,可以自定義查詢類,例如:
<?php
namespace app\db;
use think\db\Query;
class ModelQuery extends Query
{
public function top($num)
{
return $this->limit($num)-select();
}
}
在模型類中設(shè)置query
屬性如下
<?php
namespace app\index\model;
use think\Model;
class User extends Model
{
protected $query = '\app\db\ModelQuery';
}
設(shè)置后舔哪,User
模型就可以使用top
方法查詢
User::where('id desc')->top(10);
如果全局都使用該查詢類的話欢顷,建議直接在數(shù)據(jù)庫(kù)配置文件中使用下面配置:
// 設(shè)置Query類
'query' => '\\app\\db\\ModelQuery',
總結(jié)
本章我們學(xué)習(xí)了模型的一些高級(jí)用法,下一章我們就來(lái)學(xué)習(xí)下模型關(guān)聯(lián)的使用捉蚤。