上一篇文章(https://www.jianshu.com/p/999cb3cb1223
)详细介绍了thinkphp框架with的使用,可以替代传统的join查询的优势,并且对各种使用场景进行了验证。发现可以用with完全可以替代传统的查询条件。下面结合一个对订单列表页的筛选的信息进行分析。
需求:根据客户端传递的字段完成对订单信息的搜索
1.Controller
很简单,直接把客户端传递的参数扔给service层
public function lists(Request $request)
{
$param = $request->param();
$result = CyActivityOrderService::orderList($param);
return $this->json($result);
}
2.Service
根据控制器的参数构造参数,调用模型。
/**
* 根据外面传递进来的参数构造查询对象
* @param $param
* @param $model
* @return CyQuerySet
*/
public static function buildQuerySet($param, Model $model)
{
$cyQuery = new CyQuerySet();
$with = ['refundData', 'createdData', 'activityData', 'userData' => function (Query $query) {
return $query->field("user_id,mobile,email,nickname");
}];
$withJoin = [];
$param = self::filterSearchParam($param);
$condition = [];
$modelTable = $model->getTable();
foreach ($param as $key => $value) {
$key = self::processWithParam($key, $with, $withJoin, $modelTable);
$condition[] = [$key, "=", $value];
}
$cyQuery->with = $with;
$cyQuery->withJoin = $withJoin;
$cyQuery->append = ['pay_status_name', 'from_type_name'];
$cyQuery->setWhere($condition);
$cyQuery->order = self::buildSort($param);
return $cyQuery;
}
/**
* 订单列表
* @param $param
* @return \think\Paginator
*/
public static function orderList($param)
{
return self::search($param)->paginate();
}
protected static function search($param)
{
$cyOrderModel = CyActivityOrderModel::getInstance();
$querySet = self::buildQuerySet($param, $cyOrderModel);
$query = $cyOrderModel->queryWithSet($querySet);
return $query;
}
不论是列表页还是详情页,都是调用了self::search
,这个里面参数和解析和数据查询前的准备,可以返回给不同的业务调用不同的方法,比如select、find、paginate等等。
2.1 BaseService
类 封装了一些常用的方法,其中最常用的参数的解析和过滤,另一个是根据客户端传递进来的条件,选择是with还是withjoin
/**
* 过滤搜索的条件
* @param $param
* @return array
*/
protected static function filterSearchParam($param)
{
$validParam = [];
//获取有效的数组
foreach ($param as $key => $value) {
if (!empty(static::$searchPrefix) && strpos($key,static::$searchPrefix) !== 0){ //不以前置开头
continue;
}
if (trim($value) === ''){
continue;
}
$realKey = !empty(static::$searchPrefix)?substr($key, strlen(static::$searchPrefix)):$key;//去掉前缀获取真正的key
if (empty(self::$searchAllow)) { //为空表示设置搜索条件
$validParam[$realKey] = $value;
} else{
$limit = isset(self::$searchAllow[$realKey])?self::$searchAllow[$realKey]:[];
if (empty($limit)) { //为空表示没设置条件
$validParam[$realKey] = $value;
} elseif (is_array($limit) && count($limit) == 2) {
$allowValue = is_array($limit[1]) ? $limit[1] : explode(",", $limit[1]);
if (($limit[0] == 'in' && in_array($value, $allowValue)) ||
($limit[0] == 'not in' && !in_array($value, $allowValue))
) {
$validParam[$realKey] = $value;
}
}
}
}
return $validParam;
}
protected static function processWithParam($key, &$with, &$withJoin,$mainTable)
{
//带有-符号的表示要连表查询
if (strpos($key, "-") !== false) {
//根据-符号拆分表明和字段
list($joinTable, $field) = explode("-", $key);
//if这里成立表示 原来with里面的只有一个 userData,并没有对应的值,类似于上面的refundData。
if (in_array($joinTable, $with)) {
//将查询从原来的with数组中移除,不在使用with查询
unset($with[array_search($joinTable, $with)]);
//放入withjoin数组里面
$withJoin[] = $joinTable;
} elseif (isset($with[$joinTable])) {
//如果是闭包,里面就类似于
//这里面有东西,说明外面是
/**
'userData' => function(Query $query){
return $query->field("user_id,mobile");
}];
*/
//这样的形式,获取闭包里面的query字段
if (is_callable($with[$joinTable])) {
$callBack = $with[$joinTable];
/** @var $queryParam Query * */
$queryParam = new Query();
$queryParam = $callBack($queryParam);
//将条件放入withjoin,同时将原来的field字段的值,调整为with_field 不然不起作用
$withJoin[$joinTable] = function (Query $query) use ($queryParam) {
return $query->setOption('with_field',$queryParam->getOptions('field'));
};
}
//从with条件移除
unset($with[$joinTable]);
}
return $joinTable.".".$field;
} else {
return $mainTable.".".$key;
}
}
processWithParam
方法格外重要,它用来根据客户端传递进来的参数决定使用with还是withjoin,默认情况下,可以看到所有的都使用with,但是如果查询的字段并不在主表,在被with关联的表里面,用with其实是查询不到的,这个时候就需要使用withjoin,withjoin内部执行的是连表查询。通过上一篇文章里面的实验已经非常清晰的可以验证这一点,理解非常简单。但是如果将根据客户端传递进来的参数写一个通过的方法,自动的选择with、withjoin还是需要仔细研究一下的,processWithParam就是实现了这个功能
。
2.2 CyQuerySet
这个主要是将一些传递给model的参数做了封装,主要用来解决,service调用模型的时候,传递的参数过多问题。
class CyQuerySet
{
/**
* with的选项
* @var array
*/
public $with = [];
/**
* withJoin选项
* @var array
*/
public $withJoin = [];
/**
* where条件
* @var array
*/
protected $where = [];
/**
* 查询的选项
* @var string
*/
public $field = "*";
/**
* @var string
*/
public $selectType = "";
/**
* 排序的字段
* @var
*/
public $order = "";
public $append = "";
/**
* 关联属性
* @var array
*/
public $withAttr = [];
/**
* limit字段
* @var string
*/
public $limit;
/**
* 是否分页
* @var bool
*/
public $isPage = false;
/**
* 隐藏字段
* @var
*/
public $hidden;
public function getWhere()
{
return convert_where($this->where);
}
public function setWhere($where)
{
$this->where = $where;
}
}
2.3 Model
模型类就是继承TP的模型类,下面的queryWithSet就是解析参数,这里面解析的参数可以根据需要封装,我这里拼接到order就不拼接了,拼接的多少完全由实际情况决定。
public function queryWithSet(CyQuerySet $querySet)
{
$deleteW = [];
if (method_exists($this,'remSD')){
$query = $this->remSD()->withTrashed();
$deleteW[] = [$this->getTable().'.deleted_at',"=",0];
}
$query = $query
->field($querySet->field)
->append($querySet->append)
->withAttr($querySet->withAttr)
->withJoin($querySet->withJoin)
->with($querySet->with)
->where($querySet->getWhere())
->where($deleteW);
if ($querySet->order){
$query = $query->order($querySet->order);
}
return $query;
}
案例测试
查询订单下用户特定用户id和ticket_id的记录
admin.order/lists?ticket_id=1&user_id=889720
结果
{
"id": 36,
"uuid": "20190608034350341425",
"cyb_pay_num": "884823052091842778",
"user_id": 889720,
"act_id": 1,
"from_type": 0,
"ticket_id": 1,
"single_price": "0.05",
"ticket_num": 1,
"pay_status": 88,
"coupon_id": 0,
"coupon_price": "0.00",
"amount_price": "0.00",
"pay_price": "0.05",
"surplus_price": "0.05",
"client_type": null,
"pay_type": "WX_APP",
"created_at": "2019-06-08 03:43:49",
"created_by": 1257341,
"updated_at": "2019-06-08 03:49:52",
"updated_by": 1257341,
"deleted_at": 0,
"deleted_by": 0,
"cancel_at": 0,
"pay_at": 0,
"refund_at": 0,
"refund_by": 0,
"refund_reason": "",
"refund_data": null,
"created_data": null,
"activity_data": null,
"user_data": {
"user_id": 889720,
"mobile": "18311159245",
"email": "",
"nickname": "php测试"
},
"pay_status_name": "已支付",
"from_type_name": ""
}
这个时候通过看sql日志可以看出他是用with关联起来的查询。
下面我们要查询的是nickname为 php测试
的用户所产生的订单。
admin.order/lists?ticket_id=1&userData-nickname=php测试
依然能得到上述信息,这个时候其实执行的是withjoin。客户端传递参数为userData-nickname.
通过上述实验,我可以大胆的推测,我们只要写一个接口,不用改代码就支持客户端的任何表里面的任何字段的查询操作,并且返回的数据形式不变,如果想控制客户端查询的范围,我们需要在控制器的接收参数的地方做限制就可以了,对于一些特殊的条件,比如不是相等的条件,可以在service层里面的for循环里面,手动的根据条件写代码并把条件扔给condition数组。
网友评论