基于thinkphp with的通用查询封装和测试

2019-06-13  本文已影响0人  鸿雁长飞光不度

上一篇文章(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数组。

上一篇 下一篇

猜你喜欢

热点阅读