本章带你了解下模型的一些高级技巧,这些技巧会让你在使用模型的过程中更加高效和简单,学习内容主要包含:
条件查询
我们前面的大多数例子都是使用主键进行模型数据查询,事实上,业务逻辑并非都是如此简单,条件查询的必要性是存在的。
最简单的方法是直接使用
Db
类的查询构造器查询,方法是在模型中静态调用Db
类(其实是查询类)的任何方法(包括动态查询)进行查询。
// 查询单个记录
User::where('name', 'thinkphp')->find();
// 调用动态查询方法
User::getByName('thinkphp');
// 查询数据集
User::where('id', '>', 0)->limit(10)->order('id desc')->select();
// 删除数据
User::where('status', 0)->delete();
如果你的查询代码在模型内部,可以直接支持动态方式调用查询构造器的方法,用法就是从静态调用改为动态调用。
// 查询单个记录
$this->where('name', 'thinkphp')->find();
// 调用动态查询方法
$this->getByName('thinkphp');
// 查询数据集
$this->where('id', '>', 0)->limit(10)->order('id desc')->select();
// 删除数据
$this->where('status', 0)->delete();
不过类似的查询场景应当极力避免(个别场景例如查询范围等可能会涉及到此类用法),查询操作应当是静态调用,更新和删除操作则是动态方法调用。如果是在模型方法中查询其它模型的数据,第一种静态调用方式仍然适用。尤其不建议使用table
方法在同一个模型实例中切换数据表查询,在模型中动态设置table
属性的方式更加不可取(经常发现这种奇葩的用法),模型和数据表以及相应的业务逻辑是应当在创建的时候就相对固定的,应当极力避免在一个模型对象实例中查询操作多次不同数据。
模型查询的原则应当是每个模型对象实例操作一个唯一记录,对于数据集来说这个原则也不变,只是每个数据集对象实例则包含多个模型对象实例而已。
对于自定义查询,如果统一使用模型类提供的get
和all
进行查询也一样可以达到目的,改成下面代码即可:
// 查询单个记录
User::get(['name' => 'thinkphp']);
// 查询数据集
User::all(function ($query) {
$query->where('id', '>', 0)
->limit(10)
->order('id desc');
});
// 删除数据
User::destroy(['status' => 0]);
User::destroy(function ($query) {
$query->where('id', 'in', [1, 2, 3]);
});
get
、all
以及destroy
方法的参数用法记住一个原则,如果是数字、字符串或者普通数组都表示一个或者多个主键,如果是索引数组则表示查询条件,闭包则支持查询条件以外的其它链式操作方法。对于get
方法的参数最好做一次非null
检查,否则查询的就会是第一个数据(V5.0.8+
已经改进,不需要检查是否为null
了)。
查询范围
对于一些常用的查询条件,我们可以事先定义好,以便快速调用,这个事先定义的查询条件方法有一个统一的前缀scope
,我们称之为查询范围,例如下面给User
模型定义了两个查询范围方法。
<?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);
}
}
现在我们直接使用
$users = User::scope('email,status')->select();
或者使用
$users = User::scope('email')->scope('status')->select();
生成的查询语句是
SELECT * FROM `user` WHERE `email` = 'thinkphp@qq.com' AND `status` = 1
查询范围之外仍然可以使用额外的查询条件,例如:
$users = User::scope('email,status')
->where('nickname', 'like', '%think%')
->order('id desc')
->select();
查询范围方法必须首先被调用
查询范围方法支持额外的参数,例如scopeEmail
方法改为:
// email查询
protected function scopeEmail($query, $email = '')
{
$query->where('email', $email);
}
查询范围的方法的第一个参数必须是查询对象,并且支持多个额外参数。
然后,使用下面的方式调用即可(带参数调用的时候每次只能调用一个查询范围):
$list = User::scope('email', 'thinkphp@qq.com')->select();
查询范围有一个特殊的方法base
,一旦在模型中定义了base
方法后,无需显式调用scope
方法,系统会在每次查询的时候自动调用,我们称之为全局查询范围。
<?php
namespace app\index\model;
use think\Model;
class User extends Model
{
// 全局查询范围
protected static function base($query)
{
// 查询状态为1的数据
$query->where('status', 1);
}
// email查询
protected function scopeEmail($query)
{
$query->where('email', 'thinkphp@qq.com');
}
}
当使用下面的查询操作
User::get(1);
User::scope('email')->select();
最后生成的SQL语句分别是:
SELECT * FROM `user` WHERE `status` = 1 AND `id` = 1 LIMIT 1
SELECT * FROM `user` WHERE `status` = 1 AND `email` = 'thinkphp@qq.com'
无论是什么查询都会默认带上全局查询范围中的条件。
可以临时关闭全局查询范围进行查询
// 关闭全局查询范围
User::useGlobalScope(false)->get(1);
查询范围方法中不仅支持
where
方法,任何查询构造器的方法都可以被支持。
字段过滤
经常我们会直接使用表单提交的数据来作为模型数据写入,但并不是所有的数据都是数据表字段(直接写入会导致数据库异常),或者不希望某些数据被用户通过表单提交的方式更新(为了安全或者逻辑考虑),Request
类自身提供了only
方法来获取部分想要的数据,例如:
// 只获取请求变量中的nickname和address变量
$data = request()->only(['nickname', 'address']);
// 获取当前用户对象
$user = User::get(request()->session('user_id'));
// 更新用户数据
$user->data($data, true)->save();
模型类提供了allowField
方法用于在数据写入操作的时候设置字段过滤,从而避免数据库因为字段不存在而报错,上面的写法可以简化为。
// 获取当前用户对象
$user = User::get(request()->session('user_id'));
// 只允许更新用户的nickname和address数据
$user->allowField(['nickname', 'address'])
->data(requst()->param(), true)
->save();
如果仅仅是希望去除数据表之外的字段,可以使用
// 只允许更新数据表字段数据
$user->allowField(true)
->data(requst()->param(), true)
->save();
为了不必每次都调用allowField
方法,我们可以直接在模型类里面设置field
属性,例如:
<?php
namespace app\index\model;
use think\Model;
class User extends Model
{
protected $field = ['name', 'nickname', 'email', 'address'];
}
当调用allowField
方法的时候,当前模型实例中的该配置的值会被覆盖。
如果使用的是模型的静态方法(如create
和update
方法)进行数据写入的话,可以使用下面的方式进行字段过滤。
User::create(request()->param(), ['nickname', 'address']);
User::update(request()->param(), ['id' => 1], ['nickname', 'address']);
同样可以传入true
表示过滤非数据表字段
User::create(request()->param(), true);
User::update(request()->param(), ['id' => 1], true);
只读字段
有些数据字段在写入以后就不允许被更改,例如name
字段和email
字段,那么我们可以设置该字段为只读字段,在更新的时候就会被自动忽略掉。
设置方式:
<?php
namespace app\index\model;
use think\Model;
class User extends Model
{
protected $readonly = ['name','email'];
}
举个例子说明下:
$user = User::get(5);
echo $user->name;
echo $user->email;
// 更改某些字段的值
$user->name = 'TOPThink';
$user->email = 'Topthink@gmail.com';
$user->address = '上海静安区';
// 保存更改后的用户数据
$user->save();
echo $user->name;
echo $user->email;
事实上,由于我们对name
和email
字段设置了只读,因此只有address
字段的值被更新了,而name
和email
的值仍然还是更新之前的值。
软删除
在实际项目中,对数据频繁使用删除操作会导致性能问题,因此不推荐直接物理删除数据,而是用逻辑删除替代,也就是下面要讲的软删除。软删除的作用就是把数据加上删除标记,而不是真正的删除,同时也便于需要的时候进行数据的恢复。
要使用软删除功能,需要引入SoftDelete trait
,例如User
模型按照下面的定义就可以使用软删除功能:
<?php
namespace app\index\model;
use think\Model;
use traits\model\SoftDelete;
class User extends Model
{
use SoftDelete;
}
为了配合软删除功能,你需要在数据表中添加
delete_time
字段,ThinkPHP5的软删除功能使用时间戳类型(数据表默认值为Null
),用于记录数据的删除时间。
如果你的软删除标记字段名称不是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';
}
可以用类型转换指定软删除字段的类型,建议数据表的所有时间字段统一使用autoWriteTimestamp
属性规范时间类型(支持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);
// 真实删除
User::destroy(1,true);
$user = User::get(1);
// 软删除
$user->delete();
// 真实删除
$user->delete(true);
默认情况下查询的数据不包含软删除数据,如果需要包含软删除的数据,可以使用下面的方式查询:
User::withTrashed()->find();
User::withTrashed()->select();
如果仅仅需要查询软删除的数据,可以使用:
User::onlyTrashed()->find();
User::onlyTrashed()->select();
如果你的查询条件比较复杂,尤其是某些特殊情况下使用OR
查询条件会把软删除数据也查询出来,可以使用闭包查询的方式解决,如下:
User::where(function($query) {
$query->where('id', '>', 10)
->whereOr('name', 'like', 'think');
})->select();
使用闭包查询条件会在查询条件两边添加括号,从而不会和软删除条件产生混淆或者冲突。
如果你的模型定义了base
基础查询,请确保添加软删除的基础查询条件,例如:
protected function base($query)
{
// 添加软删除条件
$query->whereNull('delete_time')
// 添加额外的基础查询条件
->where('id','>',0);
}
自定义查询类
默认情况下,默认使用的查询类是核心内置的think\db\Query
类,如果你需要自己扩展额外的查询方法,可以自定义查询类,例如:
<?php
namespace app\db;
use think\db\Query;
class ModelQuery extends Query
{
public function top($num)
{
return $this->limit($num)-select();
}
}
在模型类中设置query
属性如下
<?php
namespace app\index\model;
use think\Model;
class User extends Model
{
protected $query = '\app\db\ModelQuery';
}
设置后,User
模型就可以使用top
方法查询
User::where('id desc')->top(10);
如果全局都使用该查询类的话,建议直接在数据库配置文件中使用下面配置:
// 设置Query类
'query' => '\\app\\db\\ModelQuery',
总结
本章我们学习了模型的一些高级用法,下一章我们就来学习下模型关联的使用。
上一篇:第六章:模型数据处理
下一篇:第八章:模型关联
网友评论