定义
一、模型定义
<?php
namespace app\index\model;
use think\Model;
class User extends Model
{
}
模型会自动对应数据表,模型类的命名规则是除去表前缀的数据表名称,采用驼峰法命名,并且首字母大写
模型自动对应的数据表名称都是遵循小写+下划线规范,如果你的表名有大写的情况,必须通过设置模型的table属性。
// 开启应用类库后缀
'class_suffix' => true,
开启后,所有的应用类库定义的时候都需要加上对应后缀,包括控制器类。
<?php
namespace app\index\model;
use think\Model;
class UserModel extends Model
{
}
二、模型设置
默认主键为
id
,如果你没有使用id
作为主键名,需要在模型中设置属性5.1中模型不会自动获取主键名称,必须设置pk属性。
<?php
namespace app\index\model;
use think\Model;
class User extends Model
{
// 设置当前模型对应的完整数据表名称
protected $table = 'think_user';
// 设置当前模型的数据库连接
protected $connection = 'db_config';
// 设置当前模型的主键
protected $pk = 'uid';
}
属性 | 描述 |
---|---|
name | 模型名(默认为当前不含后缀的模型类名) |
table | 数据表名(默认自动获取) |
pk | 主键名(默认为id ) |
connection | 数据库连接(默认读取数据库配置) |
query | 模型使用的查询类名称 |
field | 模型对应数据表的字段列表(数组) |
三、模型初始化
<?php
namespace app\index\model;
use think\Model;
class User extends Model
{
// 模型初始化
protected static function init()
{
//TODO:初始化内容
}
}
模型初始化方法通常用于注册模型的事件操作。
init
必须是静态方法,并且只在第一次实例化的时候执行
四、模型操作
数据库查询
Db::name('user')->where('id','>',10)->select();
模型操作
User::where('id','>',10)->select();
虽然看起来是相同的查询条件,但一个最明显的区别是查询结果的类型不同。第一种方式的查询结果是一个(二维)数组,而第二种方式的查询结果是包含了模型(集合)的数据集。不过,在大多数情况下,这二种返回类型的使用方式并无明显区别。
模型操作和数据库操作的另外一个显著区别是模型支持包括获取器、修改器、自动完成在内的一系列自动化操作和事件,简化了数据的存取操作,但随之而来的是性能有所下降(其实并没下降,而是自动帮你处理了一些原本需要手动处理的操作)。
新增
一、添加一条数据
1. 实例化模型对象后赋值
$user = new User;
$user->name = 'thinkphp';
$user->email = 'thinkphp@qq.com';
$user->save();
save
方法返回影响的记录数
2. 直接传入数据到save方法批量赋值
$user = new User;
$user->save([
'name' => 'thinkphp',
'email' => 'thinkphp@qq.com'
]);
3.直接在实例化的时候传入数据
$user = new User([
'name' => 'thinkphp',
'email' => 'thinkphp@qq.com'
]);
$user->save();
实例化传入的模型数据也不会经过修改器处理。
4.过滤非数据表字段的数据
$user = new User;
// 过滤post数组中的非数据表字段数据
$user->allowField(true)->save($_POST);
5. 通过外部赋值给模型,直接写入某些字段
$user = new User;
// post数组中只有name和email字段会写入
$user->allowField(['name','email'])->save($_POST);
6. 数据过滤后赋值(建议)
$user = new User;
// 过滤post数组中的非数据表字段数据
$data = Request::only(['name','email']);
$user->save($data);
二、Replace
$user = new User;
$user->name = 'thinkphp';
$user->email = 'thinkphp@qq.com';
$user->replace()->save();
三、获取自增ID
$user = new User;
$user->name = 'thinkphp';
$user->email = 'thinkphp@qq.com';
$user->save();
// 获取自增ID
echo $user->user_id;
不要在同一个实例里面多次新增数据,如果确实需要多次新增,可以使用后面的静态方法处理。
四、添加多条数据
$user = new User;
$list = [
['name'=>'thinkphp','email'=>'thinkphp@qq.com'],
['name'=>'onethink','email'=>'onethink@qq.com']
];
$user->saveAll($list);
saveAll方法新增数据返回的是包含新增模型(带自增ID)的数据集对象。
saveAll
方法新增数据默认会自动识别数据是需要新增还是更新操作,当数据中存在主键的时候会认为是更新操作,如果你需要带主键数据批量新增,可以使用下面的方式:
五、静态方法
$user = User::create([
'name' => 'thinkphp',
'email' => 'thinkphp@qq.com'
]);
echo $user->name;
echo $user->email;
echo $user->id; // 获取自增ID
和
save
方法不同的是,create
方法返回的是当前模型的对象实例。
最佳实践
新增数据的最佳实践原则:使用
create
方法新增数据,使用saveAll
批量新增数据
更新
一、查找并更新
$user = User::where('status',1)
->where('name','liuchen')
->find();
$user->name = 'thinkphp';
$user->email = 'thinkphp@qq.com';
$user->save();
//如果只是字段的增加/减少,也可以直接用inc/dec方式
$user = User::get(1);
$user->name = 'thinkphp';
$user->email = 'thinkphp@qq.com';
$user->score = ['inc', 1];
$user->save();
二、直接更新
$user = new User;
// 过滤post数组中的非数据表字段数据
$user->allowField(true)->save($_POST,['id' => 1]);
最佳建议是在传入模型数据之前就进行过滤
$user = new User();
// post数组中只有name和email字段会写入
$data = Request::only(['name','email']);
$user->save($data, ['id' => 1]);
三、批量更新
最佳建议是在传入模型数据之前就进行过滤
批量更新仅能根据主键值进行更新,其它情况请自行处理。
$user = new User();
// post数组中只有name和email字段会写入
$data = Request::only(['name','email']);
$user->save($data, ['id' => 1]);
四、静态方法
模型支持调用数据库的方法直接更新数据
User::where('id', 1)
->update(['name' => 'thinkphp']);
五、自动识别
- 实例化模型后调用
save
方法表示新增; - 查询数据后调用
save
方法表示更新; save
方法传入更新条件后表示更新;
显式更新数据:
// 实例化模型
$user = new User;
// 显式指定更新数据操作
$user->isUpdate(true)
->save(['id' => 1, 'name' => 'thinkphp']);
显式新增数据:
$user = User::get(1);
$user->name = 'thinkphp';
// 显式指定当前操作为新增操作
$user->isUpdate(false)->save();
不要在一个模型实例里面做多次更新,会导致部分重复数据不再更新,正确的方式应该是先查询后更新或者使用模型类的update
方法更新。
如果你调用
save
方法进行多次数据写入的时候,需要注意,第二次save
方法的时候必须使用isUpdate(false)
,否则会视为更新数据。
最佳实践
更新的最佳实践原则是:如果需要使用模型事件,那么就先查询后更新,如果不需要使用事件,直接使用静态的
Update
方法进行条件更新,如非必要,尽量不要使用批量更新。
删除
一、删除当前模型
$user = User::get(1);
$user->delete();
delete
方法返回影响的记录数,V5.1.6+
版本开始返回布尔值
二、根据主键删除
User::destroy(1);
// 支持批量删除多个数据
User::destroy('1,2,3');
// 或者
User::destroy([1,2,3]);
当
destroy
方法传入空值(包括空字符串和空数组)的时候不会做任何的数据删除操作,但传入0则是有效的
三、条件删除
还支持使用闭包删除,例如:
User::destroy(function($query){
$query->where('id','>',10);
});
或者通过数据库类的查询条件删除
User::where('id','>',10)->delete();
直接调用数据库的
delete
方法的话无法调用模型事件。
最佳实践
删除的最佳实践原则是:如果删除当前模型数据,用
delete
方法,如果需要直接删除数据,使用destroy
静态方法。
查询
模型查询和数据库查询方法的区别主要在于,模型中的查询的数据在获取的时候会经过获取器的处理,以及更加对象化的获取方式。
一、获取单个数据
// 取出主键为1的数据
$user = User::get(1);
echo $user->name;
// 使用查询构造器查询满足条件的数据
$user = User::where('name', 'thinkphp')->find();
echo $user->name;
模型无论使用get
还是find
方法查询,返回的是都当前模型的对象实例,如果查询数据不存在,则返回Null
,除了获取模型数据外,还可以使用模型的方法。
模型无论使用
get
还是find
方法查询,返回的是都当前模型的对象实例,如果查询数据不存在,则返回Null
,除了获取模型数据外,还可以使用模型的方法。
如果你是在模型内部获取模型数据,请不要使用$this->name
的方式来获取数据,请使用$this->getAttr('name')
替代。
二、获取多个数据
// 根据主键获取多个数据
$list = User::all('1,2,3');
// 或者使用数组
$list = User::all([1,2,3]);
// 对数据集进行遍历操作
foreach($list as $key=>$user){
echo $user->name;
}
//V5.1.21+版本开始,可以在all方法之前使用Db类的查询链式操作,
// 使用查询构造器查询
$list = User::where('status', 1)->limit(3)->order('id', 'asc')->all();
foreach($list as $key=>$user){
echo $user->name;
}
要更多的查询支持,一样可以使用查询构造器:
// 使用查询构造器查询
$list = User::where('status', 1)->limit(3)->order('id', 'asc')->select();
foreach($list as $key=>$user){
echo $user->name;
}
三、使用查询构造器
1.获取某个字段或者某个列的值
// 获取某个用户的积分
User::where('id',10)->value('score');
// 获取某个列的所有值
User::where('status',1)->column('name');
// 以id为索引
User::where('status',1)->column('name','id');
value
和column
方法返回的不再是一个模型对象实例,而是纯粹的值或者某个列的数组。
2.动态查询
支持数据库的动态查询方法,例如:
// 根据name字段查询用户
$user = User::getByName('thinkphp');
// 根据email字段查询用户
$user = User::getByEmail('thinkphp@qq.com');
四、查询缓存
get
方法和all
方法的支持使用查询缓存,可以直接在第二个参数传入true表示开启查询缓存。
$user = User::get(1,true);
$list = User::all('1,2,3',true);
如果要设置缓存标识,则必须在第三个参数传入缓存标识。
$user = User::get(1,'','user');
$list = User::all('1,2,3','','user_list');
最佳实践
模型查询的最佳实践原则是:在模型外部使用静态方法进行查询,内部使用动态方法查询,包括使用数据库的查询构造器。模型的查询始终返回对象实例,但可以和数组一样使用。
数据库 | 模型 | |
查询 | find()--null,findOrFail()--异常,findOrEmpty()--空数组 select()--空数组,selectOrFail()--异常 value()--null column()--空数组 | getAttr('name')【内部】 get() find() all() select() value() column() |
新增 | insert($data) data($data)->insert() strict(false)->insert($data) insert($data,true) insertGetId($data) insertAll($data) | saveAll() create() save() allowField()->save() replace()->save() |
更新 | updata($data) data($data)->updata() setField() setInc()/setDec() | 先查询,save()【使用模型事件】 静态update() allowField(true)->save() isUpdate(true)->save() |
删除 | delete() | delete() destroy() |
获取器
一、获取器
getFieldNameAttr
FieldName
为数据表字段的驼峰转换,定义了获取器之后会在下列情况自动触发:
- 模型的数据对象取值操作(
$model->field_name
); - 模型的序列化输出操作(
$model->toArray()
及toJson()
); - 显式调用
getAttr
方法($this->getAttr('field_name')
);
获取器的场景包括:
- 时间日期字段的格式化输出;
- 集合或枚举类型的输出;
- 数字状态字段的输出;
- 组合字段的输出;
获取器还可以定义数据表中不存在的字段
获取器方法的第二个参数传入的是当前的所有数据数组。
<?php
class User extends Model
{
public function getStatusTextAttr($value,$data)
{
$status = [-1=>'删除',0=>'禁用',1=>'正常',2=>'待审核'];
return $status[$data['status']];
}
}
二、获取原始数据
$user = User::get(1);
// 通过获取器获取字段
echo $user->status;
// 获取原始字段数据
echo $user->getData('status');
// 获取全部原始数据
dump($user->getData());
三、动态获取器
从V5.1.20+
版本开始,可以支持对模型使用动态获取器,无需在模型类中定义获取器方法。
User::withAttr('name', function($value, $data) {
return strtolower($value);
})->select();
withAttr
方法支持多次调用,定义多个字段的获取器。另外注意,withAttr
方法之后不能再使用模型的查询方法,必须使用Db类的查询方法。
如果同时还在模型里面定义了相同字段的获取器,则动态获取器优先,也就是可以临时覆盖定义某个字段的获取器。
支持对关联模型的字段使用动态获取器,例如:
User::with('profile')->withAttr('profile.name', function($value, $data) {
return strtolower($value);
})->select();
修改器
一、修改器
setFieldNameAttr
修改器的使用场景和读取器类似:
- 时间日期字段的转换写入;
- 集合或枚举类型的写入;
- 数字状态字段的写入;
- 某个字段涉及其它字段的条件或者组合写入;
定义了修改器之后会在下列情况下触发:
- 模型对象赋值;
- 调用模型的
data
方法,并且第二个参数传入true
; - 调用模型的
save
方法,并且传入数据; - 显式调用模型的
setAttr
方法; - 定义了该字段的自动完成;
<?php
class User extends Model
{
public function setNameAttr($value)
{
return strtolower($value);
}
}
二、批量修改
除了赋值的方式可以触发修改器外,还可以用下面的方法批量触发修改器:
$user = new User();
$data['name'] = 'THINKPHP';
$data['email'] = 'thinkphp@qq.com';
$user->data($data, true);
$user->save();
echo $user->name; // thinkphp
如果为name
和email
字段都定义了修改器的话,都会进行处理。
或者直接使用save方法触发,例如:
$user = new User();
$data['name'] = 'THINKPHP';
$data['email'] = 'thinkphp@qq.com';
$user->save($data);
echo $user->name; // thinkphp
修改器方法仅对模型的写入方法有效,调用数据库的写入方法写入无效,例如下面的方式修改器无效。
$user = new User();
$data['name'] = 'THINKPHP';
$data['email'] = 'thinkphp@qq.com';
$user->insert($data);
搜索器
searchFieldNameAttr
FieldName
为数据表字段的驼峰转换,搜索器仅在调用withSearch
方法的时候触发。
搜索器的场景包括:
- 限制和规范表单的搜索条件;
- 预定义查询条件简化查询;
<?php
namespace app\index\model;
use think\Model;
class User extends Model
{
public function searchNameAttr($query, $value, $data)
{
$query->where('name','like', $value . '%');
}
public function searchCreateTimeAttr($query, $value, $data)
{
$query->whereBetweenTime('create_time', $value[0], $value[1]);
}
}
然后,我们可以使用下面的查询
User::withSearch(['name','create_time'], [
'name' => 'think',
'create_time' => ['2018-8-1','2018-8-5'],
'status' => 1
])
->select();
搜索器通常会和查询范围进行比较,搜索器无论定义了多少,只需要一次调用,查询范围如果需要组合查询的时候就需要多次调用。