查询
模型中的查询的数据在获取的时候会经过获取器的处理,以及更加对象化的获取方式。
模型查询的最佳实践原则是:在模型外部使用静态方法进行查询,内部使用动态方法查询,包括使用数据库的查询构造器。模型的查询始终返回对象实例,但可以和数组一样使用。
获取单个数据
// 取出主键为1的数据
$user = User::get(1);
echo $user->name;
// 使用查询构造器查询满足条件的数据
$user = User::where('name', 'thinkphp')->find();
echo $user->name;
模型无论使用get
还是find
方法查询,返回的是都当前模型的对象实例,如果查询数据不存在,则返回Null
,除了获取模型数据外,还可以使用模型的方法。
V5.1.5+
版本,模型增加了getOrFail
方法用于查询,当查询的数据不存在的时候会抛出ModelNotFound
异常。
V5.1.23+
版本开始,可以使用findOrEmpty
方法,当查询数据不存在的话,返回空模型而不是Null
。
// 使用查询构造器查询满足条件的数据
$user = User::where('name', 'thinkphp')->findOrEmpty();
如果你是在模型内部获取模型数据,请不要使用
$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;
}
使用查询构造器
// 使用查询构造器查询
$list = User::where('status', 1)->limit(3)->order('id', 'asc')->select();
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;
}
获取某个字段或者某个列的值
// 获取某个用户的积分
User::where('id',10)->value('score');
// 获取某个列的所有值
User::where('status',1)->column('name');
// 以id为索引
User::where('status',1)->column('name','id');
value
和column
方法返回的不再是一个模型对象实例,而是纯粹的值或者某个列的数组。
动态查询
支持数据库的动态查询方法,例如:
// 根据name字段查询用户
$user = User::getByName('thinkphp');
// 根据email字段查询用户
$user = User::getByEmail('thinkphp@qq.com');
聚合查询
同样在模型中也可以调用数据库的聚合方法查询,例如:
User::count();
User::where('status','>',0)->count();
User::where('status',1)->avg('score');
User::max('score');
数据分批处理
模型也可以支持对返回的数据分批处理,这在处理大量数据的时候非常有用,例如:
User::chunk(100,function($users) {
foreach($users as $user){
// 处理user模型对象
}
});
新增
数据库的新增只是单纯的写入给定的数据,而模型的数据写入会包含修改器、自动完成以及模型事件等环节
新增数据的最佳实践原则:使用
create
方法新增数据,使用saveAll
批量新增数据。
添加一条数据
save
方法新增数据返回的是写入的记录数(通常是1
),而不是自增主键值。
实例化模型对象后赋值并保存
$user = new User;
$user->name = 'thinkphp';
$user->email = 'thinkphp@qq.com';
$user->save();
直接传入数据到save
方法批量赋值
$user = new User;
$user->save([
'name' => 'thinkphp',
'email' => 'thinkphp@qq.com'
]);
直接在实例化的时候传入数据
$user = new User([
'name' => 'thinkphp',
'email' => 'thinkphp@qq.com'
]);
$user->save();
* 实例化传入的模型数据也不会经过修改器处理。
过滤非数据表字段的数据
$user = new User;
// 过滤post数组中的非数据表字段数据
$user->allowField(true)->save($_POST);
指定某些字段写入
$user = new User;
// post数组中只有name和email字段会写入
$user->allowField(['name','email'])->save($_POST);
模型数据赋值之前就进行数据过滤
$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->id;
//如果主键不是id,而是user_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)的数据集对象。
静态方法
$user = User::create([
'name' => 'thinkphp',
'email' => 'thinkphp@qq.com'
]);
echo $user->name;
echo $user->email;
echo $user->id; // 获取自增ID
和
save
方法不同的是,create
方法返回的是当前模型的对象实例。
更新
更新的最佳实践原则是:如果需要使用模型事件,那么就先查询后更新,如果不需要使用事件,直接使用静态的
Update
方法进行条件更新,如非必要,尽量不要使用批量更新。
查找并更新
$user = User::get(1);
$user->name = 'thinkphp';
$user->email = 'thinkphp@qq.com';
$user->save();
save
方法更新数据,只会更新变化的数据,对于没有变化的数据是不会进行重新更新的。强制更新数据
$user = User::get(1);
$user->name = 'thinkphp';
$user->email = 'thinkphp@qq.com';
$user->force()->save();
只是字段的增加/减少,也可以直接用inc/dec
方式
$user = User::get(1);
$user->name = 'thinkphp';
$user->email = 'thinkphp@qq.com';
$user->score = ['inc', 1];
$user->save();
直接更新数据
$user = new User;
// save方法第二个参数为更新条件
$user->save([
'name' => 'thinkphp',
'email' => 'thinkphp@qq.com'
],['id' => 1]);
过滤非数据表字段的数据
$user = new User;
// 过滤post数组中的非数据表字段数据
$user->allowField(true)->save($_POST,['id' => 1]);
指定某些字段写入
$user = new User();
// post数组中只有name和email字段会写入
$user->allowField(['name','email'])->save($_POST, ['id' => 1]);
传入模型数据之前就进行过滤
$user = new User();
// post数组中只有name和email字段会写入
$data = Request::only(['name','email']);
$user->save($data, ['id' => 1]);
批量更新数据
使用saveAll
方法批量更新数据,只需要在批量更新的数据中包含主键,批量更新方法返回的是一个数据集对象。
$user = new User;
$list = [
['id'=>1, 'name'=>'thinkphp', 'email'=>'thinkphp@qq.com'],
['id'=>2, 'name'=>'onethink', 'email'=>'onethink@qq.com']
];
$user->saveAll($list);
* 批量更新仅能根据主键值进行更新,其它情况请自行处理。
静态方法
调用数据库的方法直接更新数据
User::where('id', 1)
->update(['name' => 'thinkphp']);
数据库的update
方法返回影响的记录数
使用模型的静态update
方法更新
User::update(['id' => 1, 'name' => 'thinkphp']);
模型的update
方法返回模型的对象实例
自动识别
模型的新增和更新方法都是save
方法
- 实例化模型后调用
save
方法表示新增; - 查询数据后调用
save
方法表示更新; save
方法传入更新条件后表示更新;
可以用isUpdate
方法显式的指定当前调用save
方法是新增操作还是更新操作。
显式更新数据:
// 实例化模型
$user = new User;
// 显式指定更新数据操作
$user->isUpdate(true)
->save(['id' => 1, 'name' => 'thinkphp']);
显式新增数据:
$user = User::get(1);
$user->name = 'thinkphp';
// 显式指定当前操作为新增操作
$user->isUpdate(false)->save();
* 调用save
方法进行多次数据写入的时候,需要注意,第二次save
方法的时候必须使用isUpdate(false)
,否则会视为更新数据。
删除
如果删除当前模型数据,用
delete
方法,如果需要直接删除数据,使用destroy
静态方法。
删除当前模型
删除模型数据,可以在查询后调用delete
方法。
$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
方法的话无法调用模型事件。
获取器
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());
修改器
setFieldNameAttr
修改器的使用场景和读取器类似:
- 时间日期字段的转换写入;
- 集合或枚举类型的写入;
- 数字状态字段的写入;
- 某个字段涉及其它字段的条件或者组合写入;
定义了修改器之后会在下列情况下触发:
- 模型对象赋值;
- 调用模型的
data
方法,并且第二个参数传入true
; - 调用模型的
save
方法,并且传入数据; - 显式调用模型的
setAttr
方法; - 定义了该字段的自动完成;
<?php
class User extends Model
{
public function setNameAttr($value)
{
return strtolower($value);
}
}
修改器方法的第二个参数会自动传入当前的所有数据数组。
搜索器
searchFieldNameAttr
FieldName
为数据表字段的驼峰转换,搜索器仅在调用withSearch
方法的时候触发。
搜索器的场景包括:
- 限制和规范表单的搜索条件;
- 预定义查询条件简化查询;
例如,我们需要给User模型定义name
字段和时间字段的搜索器,可以使用:
<?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();
自动时间戳
系统支持自动写入创建和更新的时间戳字段(默认关闭)
第一种方式是全局开启,在数据库配置文件中进行设置:
// 开启自动写入时间戳字段
'auto_timestamp' => true,
第二种是在需要的模型类里面单独开启:
<?php
namespace app\index\model;
use think\Model;
class User extends Model
{
protected $autoWriteTimestamp = true;
}
又或者首先在数据库配置文件中全局开启,然后在个别不需要使用自动时间戳写入的模型类中单独关闭:
<?php
namespace app\index\model;
use think\Model;
class User extends Model
{
protected $autoWriteTimestamp = false;
}
一旦配置开启的话,会自动写入create_time
和update_time
两个字段的值,默认为整型(int
),如果你的时间字段不是int
类型的话,可以直接使用:
// 开启自动写入时间戳字段
'auto_timestamp' => 'datetime',
或者
<?php
namespace app\index\model;
use think\Model;
class User extends Model
{
protected $autoWriteTimestamp = 'datetime';
}
默认的创建时间字段为create_time
,更新时间字段为update_time
,支持的字段类型包括timestamp/datetime/int
。
写入数据的时候,系统会自动写入create_time
和update_time
字段,而不需要定义修改器,例如:
$user = new User();
$user->name = 'thinkphp';
$user->save();
echo $user->create_time; // 输出类似 2016-10-12 14:20:10
echo $user->update_time; // 输出类似 2016-10-12 14:20:10
时间字段的自动写入仅针对模型的写入方法,如果使用数据库的更新或者写入方法则无效。
时间字段输出的时候会自动进行格式转换,如果不希望自动格式化输出,可以把数据库配置文件的
datetime_format
参数值改为false
datetime_format
参数支持设置为一个时间类名,这样便于你进行更多的时间处理,例如:
// 设置时间字段的格式化类
'datetime_format' => '\org\util\DateTime',
该类应该包含一个__toString
方法定义以确保能正常写入数据库。
如果你的数据表字段不是默认值的话,可以按照下面的方式定义:
<?php
namespace app\index\model;
use think\Model;
class User extends Model
{
// 定义时间戳字段名
protected $createTime = 'create_at';
protected $updateTime = 'update_at';
}
下面是修改字段后的输出代码:
$user = new User();
$user->name = 'thinkphp';
$user->save();
echo $user->create_at; // 输出类似 2016-10-12 14:20:10
echo $user->update_at; // 输出类似 2016-10-12 14:20:10
如果你只需要使用create_time
字段而不需要自动写入update_time
,则可以单独关闭某个字段,例如:
class User extends Model
{
// 关闭自动写入update_time字段
protected $updateTime = false;
}
支持动态关闭时间戳写入功能,例如你希望更新阅读数的时候不修改更新时间,可以使用isAutoWriteTimestamp
方法:
$user = User::get(1);
$user->read +=1;
$user->isAutoWriteTimestamp(false)->save();
只读字段
只读字段用来保护某些特殊的字段值不被更改,这个字段的值一旦写入,就无法更改。 要使用只读字段的功能,我们只需要在模型中定义readonly
属性:
<?php
namespace app\index\model;
use think\Model;
class User extends Model
{
protected $readonly = ['name', 'email'];
}
只读字段仅针对模型的更新方法,如果使用数据库的更新方法则无效,例如下面的方式无效。
类型转换
支持给字段设置类型自动转换,会在写入和读取的时候自动进行类型转换处理,例如:
<?php
class User extends Model
{
protected $type = [
'status' => 'integer',
'score' => 'float',
'birthday' => 'datetime',
'info' => 'array',
];
}
数据完成
数据自动完成指在不需要手动赋值的情况下对字段的值进行处理后写入数据库。
系统支持auto
、insert
和update
三个属性,可以分别在写入、新增和更新的时候进行字段的自动完成机制,auto
属性自动完成包含新增和更新操作,例如我们定义User
模型类如下:
<?php
namespace app\index\model;
use think\Model;
class User extends Model
{
protected $auto = ['name', 'ip'];
protected $insert = ['status' => 1];
protected $update = [];
protected function setNameAttr($value)
{
return strtolower($value);
}
protected function setIpAttr()
{
return request()->ip();
}
}
数据自动完成如果需要写入固定的值,可以直接指定(例如上面的status字段固定写入了1),类似于数据表字段的默认值功能。
查询范围
可以对模型的查询和写入操作进行封装,例如:
<?php
namespace app\index\model;
use think\Model;
class User extends Model
{
public function scopeThinkphp($query)
{
$query->where('name','thinkphp')->field('id,name');
}
public function scopeAge($query)
{
$query->where('age','>',20)->limit(10);
}
}
// 查找name为thinkphp的用户
User::scope('thinkphp')->find();
// 查找年龄大于20的10个用户
User::scope('age')->select();
// 查找name为thinkphp的用户并且年龄大于20的10个用户
User::scope('thinkphp,age')->select();
使用查询范围后,只能使用
find
或者select
查询。
全局查询范围
如果你的所有查询都需要一个基础的查询范围,那么可以在模型类里面定义一个静态的base
方法,例如:
<?php
namespace app\index\model;
use think\Model;
class User extends Model
{
// 定义全局的查询范围
protected function base($query)
{
$query->where('status',1);
}
}
V5.1.26+
版本开始,支持在模型里面设置globalScope
属性,定义全局的查询范围
<?php
namespace app\index\model;
use think\Model;
class User extends Model
{
// 定义全局的查询范围
protected $globalScope = ['status'];
public function scopeStatus($query)
{
$query->where('status',1);
}
}
或者使用useGlobalScope
方法动态指定当前查询的全局查询范围。
User::useGlobalScope(['status'])->select();