为什么要filterWhere
使用TP5开发时,当遇到获取数据库数据,需要筛选前端传输的筛选项时,你的代码会不会类似下面这样?
$searchId = Request::get('id', null); //用户ID
$searchName = Request::get('name', ''); //用户名称
$searchSexArr = Request::get('sex', [0, 1]); //0-男 1-女
$searchStartTime = Request::get('start_time', date('Y-m-d H:i:s')); //用户注册起始时间
$query = UserModel::field(['id', 'name', 'sex', 'create_time']);
if (!empty($searchId)) {
$query->where('id', $searchId);
}
if (!empty($searchName)) {
$query->where('name', 'like', "%$searchName%");
}
if (!empty($searchSexArr)) {
$query->where('sex', 'in', $searchSexArr);
}
if (!empty($searchStartTime)) {
$query->where('create_time', '>=', $searchStartTime);
}
$query->select();
或者
$whereMap = [];
$searchId = Request::get('id', null); //用户ID
$searchName = Request::get('name', ''); //用户名称
$searchSexArr = Request::get('sex', [0, 1]); //0-男 1-女
$searchStartTime = Request::get('start_time', date('Y-m-d H:i:s')); //用户注册起始时间
if (!empty($searchId)) {
$whereMap = ['id', $searchId];
}
if (!empty($searchName)) {
$whereMap = ['name', 'like', "%$searchName%"];
}
if (!empty($searchSexArr)) {
$whereMap = ['sex', 'in', $searchSexArr];
}
if (!empty($searchStartTime)) {
$whereMap = ['create_time', '>=', $searchStartTime];
}
UserModel::field(['id', 'name', 'sex', 'create_time'])
->where($whereMap)
->select();
可是,如果使用Yii2来处理同样的功能,完全可以使用filterWhere方法来处理
$searchId = $this->getRequest()->get('id', null); //用户ID
$searchName = $this->getRequest()->get('name', ''); //用户名称
$searchSexArr = $this->getRequest()->get('sex', [0, 1]); //0-男 1-女
$searchStartTime = $this->getRequest()->get('start_time', date('Y-m-d H:i:s')); //用户注册起始时间
UserModel::find()
->filterWhere(['id' => $searchId])
->andFilterWhere(['like', 'name', $searchName])
->andFilterWhere(['in', 'sex', $searchSexArr])
->andFilterWhere(['>=', 'create_time', $searchStartTime])
->all();
使用filterWhere这代码也太简洁、太好看了吧!不行,给TP也安排个。
创建filterWhere的基本设计思路
TP5中,并没有支持filterWhere方法,那么要自己去框架内部实现这个方法?可以,但也强烈不推荐。原因如下:
- 改动了第三方依赖,那么后续第三方依赖更新,将会存在功能错乱及重复的风险
- 需要深入了解TP5内部逻辑,才能尽可能好的创造出该功能,耗时耗力
那么,不改动框架内部代码,要怎么样去实现呢?很简单,框架内部不改动,那么只要在基于框架创建一个属于自己的底层来改动便足够了。也就是创建一个基于think\Model
类的公共底层Model(下文简称BaseModel
),在其上实现自己的功能即可。
这里利用了TP5的查询范围调用方式,感兴趣的同学可以去看看
完整代码和使用示例
由于本人懒癌发作 为了看官大人能够快速判断本文是否为您所需要使用的内容,下面直接抛出最终结果。如果对该方法的使用存在兴趣或疑问,也非常欢迎在屏幕下方留言评论。
1. 创建BaseModel文件,完成scopeFilterWhere方法
创建BaseModel类文件,只要其他的Model层继承它,那么便能够正常的使用filterWhere方法了
# 根据自己需要,将BaseModel文件放到自己业务需要的地方,修改命名空间
<?php
namespace app\common\model;
use think\db\Query;
use think\Model;
/**
* @method $this filterWhere(mixed $field, string $op = null, mixed $condition = null) static 筛选不为空的where条件
*/
class BaseModel extends Model
{
public function scopeFilterWhere(Query $query, $field, $op = null, $condition = null)
{
if (is_null(params_filter($field))) {
return;
}
if ($op !== null
&& is_null(params_filter($op))) {
return;
}
if ($condition !== null
&& is_null(params_filter($condition))) {
return;
}
$query->where($field, $op, $condition);
}
}
2. 补充一下params_filter函数
params_filter函数其实改造自array_filter_recursive函数,是用来过滤掉多维数组里面所有空的内容,不过此处的话需要特别处理:将%
也过滤掉,为的是防止模糊查询的%语句没有成功过滤。
# 可以将本函数放在公共函数文件中,或者放在类中也是没问题的,只是注意上文调用可能变化
function params_filter(&$fieldOps)
{
if (!is_array($fieldOps)) {
if (is_string($fieldOps)
&& trim($fieldOps, " \t\n\r\0\x0B%") === ''
) {
return null;
}
return $fieldOps;
}
foreach ($fieldOps as $k => $v) {
if (is_null(params_filter($v))) {
unset($fieldOps[$k]);
}
}
$fieldOps = $fieldOps ?: null;
return $fieldOps;
}
3. 创建业务Model文件,继承BaseModel
这一步建议所有业务Model都是继承BaseModel,要搞特殊也当然ok,根据自己需要即可
# 根据自己需要,将Model文件放到自己业务需要的地方,但一定要继承刚才创建的BaseModel
<?php
namespace app\common\model;
/**
* @property int $id
* @property string name
* @property integer sex
* @property string create_time
*/
class UserModel extends BaseModel
{
protected $name = 'user';
protected $pk = 'id';
}
4. 调用业务Model的filterWhere方法(使用示例)
下面直接给几个正常实现filterWhere的示例(由于TP5.0和TP5.1中where方法存在调整,下面示例以TP5.1为准)
示例一:表达式查询(TP5.0 & TP5.1)
# 下面代码实现查询 name、sex
$searchId = Request::get('id', null); //用户ID
$searchName = Request::get('name', ''); //用户名称
$searchSexArr = Request::get('sex', [0, 1]); //0-男 1-女
$searchStartTime = Request::get('start_time', date('Y-m-d H:i:s')); //用户注册起始时间
UserModel::field(['id', 'name', 'sex', 'create_time'])
->filterWhere('id', $searchId)
->filterWhere('name', 'like', "%$searchName%")
->filterWhere('sex', 'in', $searchSexArr)
->filterWhere('create_time', '>=', $searchStartTime)
->select();
注意:如果查询值为0的话,是不会过滤的,所以上面获取id时的默认值不为0,避免错误筛选
示例二:数组条件普通查询(TP5.0 & TP5.1)
$whereMap['id'] = Request::get('id', null); //用户ID
$whereMap['sex'] = Request::get('sex', [0, 1]); //0-男 1-女
UserModel::field(['id', 'name', 'sex', 'create_time'])
->filterWhere($whereMap)
->select();
示例三:数组条件表达式查询(TP5.1)
$whereMap[] = ['name', 'like', '%' . Request::get('name', '') . '%']; //用户名称
$whereMap[] = ['sex', 'in', Request::get('sex', [0, 1])]; //0-男 1-女
$whereMap[] = ['create_time', '>=', Request::get('start_time', date('Y-m-d H:i:s'))]; //用户注册起始时间
UserModel::field(['id', 'name', 'sex', 'create_time'])
->filterWhere($whereMap)
->select();
附录
测试用表结构及数据
CREATE TABLE `user` (
`id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`name` varchar(20) NOT NULL DEFAULT '' COMMENT '用户名',
`sex` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '性别:0-男 1-女',
`create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '注册日期',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB COMMENT = '用户表';
INSERT INTO `user` VALUES (1, 'test1', 1, '2021-03-28 21:37:17');
INSERT INTO `user` VALUES (2, 'test2', 0, '2021-03-10 21:37:26');
INSERT INTO `user` VALUES (3, 'three', 1, '2021-03-26 21:38:04');
让编辑器识别filterWhere方法
该方法目前已经能够被正常调用了,不过phpstorm编辑器却识别这个方法不存在。嘛,怎么说实际的方法名也是scopeFilerWhere
,编辑器能够基于类来判断是否存在某方法已经很智能了,可不能期待它跟xx框架功能有什么强耦合。
但是这个看起来是真的丑啊,有没有什么办法可以让编辑器识别该类存在这个方法呢?有!在类的块注释中添加下面注释即可
/**
* @method filterWhere(mixed $field, string $op = null, mixed $condition = null) static 筛选不为空的where条件
*/