查询
获取单个数据find
获取单个数据的方法包括:
// 取出主键为1的数据
$user = User::find(1);
echo $user->name;
// 使用查询构造器查询满足条件的数据
$user = User::where('name', 'thinkphp')->find();
echo $user->name;
模型使用find
方法查询,如果数据不存在返回Null
,否则返回当前模型的对象实例。如果希望查询数据不存在则返回一个空模型,可以使用。
$user = User::findOrEmpty(1);
你可以用isEmpty方法来判断当前是否为一个空模型。
$user = User::where('name', 'thinkphp')->findOrEmpty();
if (!$user->isEmpty()) {
echo $user->name;
}
如果你是在模型内部获取数据,请不要使用$this->name
的方式来获取数据,请使用$this->getAttr('name')
替代。
获取多个数据select
取出多个数据:
// 根据主键获取多个数据
$list = User::select([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;
}
查询构造器方式的查询可以支持更多的连贯操作,包括排序、数量限制等。
自定义数据集对象(暂未理解)
模型的select
方法返回的是一个包含多个模型实例的数据集对象(默认为\think\model\Collection
),支持在模型中单独设置查询数据集的返回对象的名称,例如:
<?php
namespace app\index\model;
use think\Model;
class User extends Model
{
// 设置返回数据集的对象名
protected $resultSetType = '\app\common\Collection';
}
resultSetType
属性用于设置自定义的数据集使用的类名,该类应当继承系统的think\model\Collection
类。
使用查询构造器
在模型中仍然可以调用数据库的链式操作和查询方法,可以充分利用数据库的查询构造器的优势。
例如:
User::where('id',10)->find();
User::where('status',1)->order('id desc')->select();
User::where('status',1)->limit(10)->select();
使用查询构造器直接使用静态方法调用即可,无需先实例化模型。
获取某个字段或者某个列的值value
|column
// 获取某个用户的积分
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');
注意,如果你的字段不是数字类型,是使用max
/min
的时候,需要加上第二个参数。
User::max('name', false);
数据分批处理
模型也可以支持对返回的数据分批处理,这在处理大量数据的时候非常有用,例如:
User::chunk(100, function ($users) {
foreach($users as $user){
// 处理user模型对象
}
});
使用游标查询
模型也可以使用数据库的cursor方法进行游标查询,返回生成器对象
foreach(User::where('status', 1)->cursor() as $user){
echo $user->name;
}
user
变量是一个模型对象实例。
查询范围
可以对模型的查询和写入操作进行封装,例如:
<?php
namespace app\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();
查询范围的方法可以定义额外的参数,例如User
模型类定义如下:
<?php
namespace app\model;
use think\Model;
class User extends Model
{
public function scopeEmail($query, $email)
{
$query->where('email', 'like', '%' . $email . '%');
}
public function scopeScore($query, $score)
{
$query->where('score', '>', $score);
}
}
在查询的时候可以如下使用:
// 查询email包含thinkphp和分数大于80的用户
User::email('thinkphp')->score(80)->select();
可以直接使用闭包函数进行查询,例如:
User::scope(function($query){
$query->where('age','>',20)->limit(10);
})->select();
使用查询范围后,只能使用find
或者select
查询。
全局查询范围(全局条件)
支持在模型里面设置globalScope
属性,定义全局的查询范围
<?php
namespace app\model;
use think\Model;
class User extends Model
{
// 定义全局的查询范围
protected $globalScope = ['status'];
public function scopeStatus($query)
{
$query->where('status',1);
}
}
然后,执行下面的代码:
$user = User::find(1);
最终的查询条件会是
status = 1 AND id = 1
如果需要动态关闭所有的全局查询范围,可以使用:
// 关闭全局查询范围
User::withoutGlobalScope()->select();
可以使用withoutGlobalScope
方法动态关闭部分全局查询范围。
User::withoutGlobalScope(['status'])->select();
推荐操作
模型查询的最佳实践原则是:在模型外部使用静态方法进行查询,内部使用动态方法查询,包括使用数据库的查询构造器。
新增
模型数据的新增和数据库的新增数据有所区别,数据库的新增只是单纯的写入给定的数据,而模型的数据写入会包含修改器、自动完成以及模型事件等环节,数据库的数据写入参考数据库章节。
添加一条数据
第一种是实例化模型对象后赋值并保存
$user = new User;
$user->name = 'thinkphp';
$user->email = 'thinkphp@qq.com';
$user->save();
save
方法成功会返回true
,并且只有当before_insert
事件返回false
的时候返回false
,一旦有错误就会抛出异常。所以无需判断返回类型。
也可以直接传入数据到save
方法批量赋值:
$user = new User;
$user->save([
'name' => 'thinkphp',
'email' => 'thinkphp@qq.com'
]);
默认只会写入数据表已有的字段,如果你通过外部提交赋值给模型,并且希望指定某些字段写入,可以使用:
$user = new User;
// post数组中只有name和email字段会写入
$user->allowField(['name','email'])->save($_POST);
最佳的建议是模型数据赋值之前就进行数据过滤,例如:
$user = new User;
// 过滤post数组中的非数据表字段数据
$data = Request::only(['name','email']);
$user->save($data);
save
方法新增数据返回的是写入的记录数(通常是1
),而不是自增主键值。
获取自增ID
如果要获取新增数据的自增ID,可以使用下面的方式:
$user = new User;
$user->name = 'thinkphp';
$user->email = 'thinkphp@qq.com';
$user->save();
// 获取自增ID
echo $user->id;
这里其实是获取模型的主键,如果你的主键不是id
,而是user_id
的话,其实获取自增ID就变成这样:
$user = new User;
$user->name = 'thinkphp';
$user->email = 'thinkphp@qq.com';
$user->save();
// 获取自增ID
echo $user->user_id;
不要在同一个实例里面多次新增数据,如果确实需要多次新增,可以使用后面的静态方法处理。
Repalce写入
save
方法可以支持replace
写入。
$user = new User;
$user->name = 'thinkphp';
$user->email = 'thinkphp@qq.com';
$user->replace()->save();
批量增加
支持批量新增,可以使用:
$user = new User;
$list = [
['name'=>'thinkphp','email'=>'thinkphp@qq.com'],
['name'=>'onethink','email'=>'onethink@qq.com']
];
$user->saveAll($list);
saveAll
方法新增数据返回的是包含新增模型(带自增ID)的数据集对象。saveAll
方法新增数据默认会自动识别数据是需要新增还是更新操作,当数据中存在主键的时候会认为是更新操作。
如:
$user = new User;
$list = [
['id'=>1,'name'=>'thinkphp','email'=>'thinkphp@qq.com'],
['id'=>2,'name'=>'onethink','email'=>'onethink@qq.com']
];
$user->saveAll($list);
以上操作就是批量更新。
静态方法
还可以直接静态调用create方法创建并写入:
$user = User::create([
'name' => 'thinkphp',
'email' => 'thinkphp@qq.com'
]);
echo $user->name;
echo $user->email;
echo $user->id; // 获取自增ID
和save
方法不同的是,create
方法返回的是当前模型的对象实例。create
方法默认会过滤不是数据表的字段信息,可以在第二个参数可以传入允许写入的字段列表,例如:
// 只允许写入name和email字段的数据
$user = User::create([
'name' => 'thinkphp',
'email' => 'thinkphp@qq.com'
], ['name', 'email']);
echo $user->name;
echo $user->email;
echo $user->id; // 获取自增ID
支持replace
操作,使用下面的方法:
$user = User::create([
'name' => 'thinkphp',
'email' => 'thinkphp@qq.com'
], ['name','email'], true);
推荐使用
新增数据的最佳实践原则:使用create
方法新增数据,使用saveAll
批量新增数据。
更新
和模型新增一样,更新操作同样也会经过修改器、自动完成以及模型事件等处理,并不等同于数据库的数据更新,而且更新方法和新增方法使用的是同一个方法,通常系统会自动判断需要新增还是更新数据。
查找并更新
在取出数据后,更改字段内容后使用save
方法更新数据。这种方式是最佳的更新方式。
$user = User::find(1);
$user->name = 'thinkphp';
$user->email = 'thinkphp@qq.com';
$user->save();
save
方法成功返回true
,并只有当before_update
事件返回false
的时候返回false
,有错误则会抛出异常。
对于复杂的查询条件,也可以使用查询构造器来查询数据并更新
$user = User::where('status',1)
->where('name','liuchen')
->find();
$user->name = 'thinkphp';
$user->email = 'thinkphp@qq.com';
$user->save();
save
方法更新数据,只会更新变化的数据,对于没有变化的数据是不会进行重新更新的。如果你需要强制更新数据,可以使用下面的方法:
$user = User::find(1);
$user->name = 'thinkphp';
$user->email = 'thinkphp@qq.com';
$user->force()->save();
这样无论你的修改后的数据是否和之前一样都会强制更新该字段的值。
如果要执行SQL函数更新,可以使用下面的方法
$user = User::find(1);
$user->name = 'thinkphp';
$user->email = 'thinkphp@qq.com';
$user->score = Db::raw('score+1');
$user->save();
以上操作需要引入Db
静态类use think\facade\Db;
字段过滤
默认情况下会过滤非数据表字段的数据,如果你通过外部提交赋值给模型,并且希望指定某些字段写入,可以使用:
$user = User::find(1);
// post数组中只有name和email字段会写入
$user->allowField(['name', 'email'])->save($_POST);
最佳用法是在传入模型数据之前就进行过滤,例如:
$user = User::find(1);
// post数组中只有name和email字段会写入
$data = Request::only(['name','email']);
$user->save($data);
批量更新数据
以使用saveAll
方法批量更新数据,只需要在批量更新的数据中包含主键即可,例如:
$user = new User;
$list = [
['id'=>1, 'name'=>'thinkphp', 'email'=>'thinkphp@qq.com'],
['id'=>2, 'name'=>'onethink', 'email'=>'onethink@qq.com']
];
$user->saveAll($list);
批量更新方法返回的是一个数据集对象。
批量更新仅能根据主键值进行更新,其它情况请自行处理。
静态方法
使用模型的静态update
方法更新:
User::update(['name' => 'thinkphp'], ['id' => 1]);
模型的update
方法返回模型的对象实例
如果你的第一个参数中包含主键数据,可以无需传入第二个参数(更新条件)
User::update(['name' => 'thinkphp', 'id' => 1]);
如果你需要只允许更新指定字段,可以使用
User::update(['name' => 'thinkphp', 'email' => 'thinkphp@qq.com'], ['id' => 1], ['name']);
上面的代码只会更新name
字段的数据。
自动识别
我们已经看到,模型的新增和更新方法都是save
方法,系统有一套默认的规则来识别当前的数据需要更新还是新增。
- 实例化模型后调用
save
方法表示新增; - 查询数据后调用
save
方法表示更新;
不要在一个模型实例里面做多次更新,会导致部分重复数据不再更新,正确的方式应该是先查询后更新或者使用模型类的update
方法更新。
不要调用save
方法进行多次数据写入。
推荐使用
更新的最佳实践原则是:如果需要使用模型事件,那么就先查询后更新,如果不需要使用事件或者不查询直接更新,直接使用静态的Update
方法进行条件更新,如非必要,尽量不要使用批量更新。
删除
模型的删除和数据库的删除方法区别在于,模型的删除会包含模型的事件处理。
删除模型数据
删除模型数据,可以在查询后调用delete方法。
$user = User::find(1);
$user->delete();
delete
方法返回布尔值
根据主键删除
或者直接调用静态方法(根据主键删除)
User::destroy(1);
// 支持批量删除多个数据
User::destroy([1,2,3]);
当destroy
方法传入空值(包括空字符串和空数组)的时候不会做任何的数据删除操作,但传入0
则是有效的
条件删除
条件删除
还支持使用闭包删除,例如:
User::destroy(function($query){
$query->where('id','>',10);
});
或者通过数据库类的查询条件删除
User::where('id','>',10)->delete();
直接调用数据库的delete
方法的话无法调用模型事件。
软删除
在实际项目中,对数据频繁使用删除操作会导致性能问题,软删除的作用就是把数据加上删除标记,而不是真正的删除,同时也便于需要的时候进行数据的恢复。
定义软删除
要使用软删除功能,需要引入SoftDelete trait
,例如User
模型按照下面的定义就可以使用软删除功能:
<?php
namespace app\model;
use think\Model;
use think\model\concern\SoftDelete;
class User extends Model
{
use SoftDelete;
protected $deleteTime = 'delete_time';
}
deleteTime
属性用于定义你的软删除标记字段,ThinkPHP的软删除功能使用时间戳类型(数据表默认值为Null
),用于记录数据的删除时间。
可以支持defaultSoftDelete
属性来定义软删除字段的默认值,在此之前的版本,软删除字段的默认值必须为null
。
<?php
namespace app\model;
use think\Model;
use think\model\concern\SoftDelete;
class User extends Model
{
use SoftDelete;
protected $deleteTime = 'delete_time';
protected $defaultSoftDelete = 0;
}
可以用类型转换指定软删除字段的类型。
软删除以及真实删除
定义好模型后,我们就可以使用:
// 软删除
User::destroy(1);
// 真实删除
User::destroy(1,true);
$user = User::find(1);
// 软删除
$user->delete();
// 真实删除
$user->force()->delete();
查询包含软删除的数据
默认情况下查询的数据不包含软删除数据,如果需要包含软删除的数据,可以使用下面的方式查询:
User::withTrashed()->find();
User::withTrashed()->select();
查询软删除的数据
如果仅仅需要查询软删除的数据,可以使用:
User::onlyTrashed()->find();
User::onlyTrashed()->select();
恢复被软删除的数据
$user = User::onlyTrashed()->find(1);
$user->restore();
软删除的删除操作仅对模型的删除方法有效,如果直接使用数据库的删除方法则无效。
推荐使用
- 删除的最佳实践原则是:如果删除当前模型数据,用
delete
方法,如果需要直接删除数据,使用destroy
静态方法。 - 数据表的所有时间字段统一一种类型
`