### 2018 年 11 月 10 日 发布 对于本文涉及到的数据查询的几个基本原则请尽量纳入你的项目规范,也是官方倡导的最佳实践。在此之前,我希望你已经看过之前的一篇博客:「 [你真的了解Db类和模型的正确使用姿势么?](https://blog.thinkphp.cn/810719)」。 ## 尽量不要使用数组条件查询 大部分混乱的查询语法都是使用了数组查询导致的,而`5.1`的数组条件查询用法又和`5.0`是完全不同的,如果你习惯了`5.0`的数组查询方式,建议你阅读下这篇文章:「 [教你使用5.1的数组对象查询](https://blog.thinkphp.cn/778497)」。 下面可能是很多新手比较容易犯的一个查询错误。 ``` $where['id'] = ['in', '1,2,3']; User::where($where)->select(); ``` 显然,这个查询思维深受老版本的影响。`5.1`版本的查询语法相比较`5.0`来说,更加对象化,下面的这种才是正确的用法。 ``` $where['id'] = [1,2,3]; User::where($where)->select(); ``` 也许是因为PHP的数组太好用的缘故,很多人对数组查询条件乐此不疲(或者是对象焦虑?)。但如果你正确的使用查询构造器以及配合模型的相关特性,可以让你的查询逻辑变得更清晰,也更加易于维护。 而且,在一些较为复杂的查询条件下,你无法使用数组完成查询,例如下面的查询用法。 ``` User::where('id', '>', 100) ->whereOr('id', '<', 10) ->where('name', 'like', 'think%') ->whereColumn('name', 'nickname') ->when('80'== $condition, function ($query) { $query->where('score', '>', 80)->limit(10); })->select(); ``` 所以,除非你很清楚`5.1`的数组查询用法,否则请尽量不要用数组条件查询了。 ## 安全使用字符串查询条件 在使用字符串查询条件的时候,如果存在外部变量,请务必使用参数绑定,并最好使用`whereRaw`方法,该方法可以和其它的查询构造器方法混合使用。 ``` User::whereRaw("id = :id AND name = :name", [ 'id' => [$id, \PDO::PARAM_INT] , 'name' => $name ])->where('status', 1) ->order('id', 'desc') ->select(); ``` 对于一些比较在意性能的查询,你也可以直接使用`query`或者`execute`方法,但同样也要注意参数的安全以及考虑不同数据库的移植问题。 ``` Db::query("select * from think_user where id=? AND status=?", [8, 1]); Db::execute("update think_user set name=:name where status=:status", ['name' => 'thinkphp', 'status' => 1]); ``` ## 对使用了`SQL`函数的查询采用`Raw`机制 如果你的查询里面包含了SQL函数,那么请使用`whereRaw`(或者`whereExp`)、`orderRaw`或者`fieldRaw`方法。 ``` User::whereExp('nickname', "= CONCAT(name, '-', id)") ->orderRaw("field(name,'thinkphp', 'kancloud')") ->fieldRaw('id,SUM(score)') ->select(); ``` ## 合理运用闭包,但不要滥用 闭包查询在查询构造器中有一些特殊用途,但如非必要,也无需滥用。 闭包查询的典型使用场景包括下面几个。 条件查询中通常都用闭包来表示一组条件查询。 ``` User::when($condition, function ($query) { // 满足条件后执行 $query->where('score', '>', 80)->limit(10); }, function ($query) { // 不满足条件执行 $query->where('score', '>', 60); })->select(); ``` 在一些子查询中经常会用到闭包。 ``` User::whereIn('id', function ($query) { $query->table('profile') ->where('name', 'like', 'think%') ->field('id'); })->select(); ``` 生成一组闭合的查询条件 ``` User::where('id', '>', 100) ->whereOr(function($query) { $query->where('name', 'like', 'think%') ->whereColumn('name', 'nickname'); })->select(); ``` 在这个查询用法中,闭包里面的查询条件会在两边加上括号而成为一个闭合的查询条件。 在很多的关联预载入查询中可以通过闭包来进行关联数据的筛选。 ``` User::with(['profile' => function($query) { $query->field('user_id,email,phone'); }])->select([1,2,3]); ``` ## 尽量复用你的查询条件 所有的查询条件应该做到一处定义多处复用,例如封装到模型的方法里面,尤其不要直接把一堆复杂的查询条件写到你的控制器代码,否则一旦业务调整,满世界的搜索代码改变你的查询条件将会是一场噩梦。 >[danger] 你也许在官方的手册或者一些教程中看到很多在控制器里面直接封装查询条件的写法,但那仅仅是出于方便展示用法的需要,并不可取。 在一些中大型的应用架构设计中,通常会把模型分成数据层、逻辑层和服务层,控制器只会调用服务层方法。而查询逻辑则基本上被封装到逻辑层里面,数据层仅仅是做模型的各种定义。 而在简单的应用里面,也可以采用PHP的`Trait`机制来实现代码的复用机制。 ## 用查询范围或搜索器简化查询 如果你使用模型查询的话,把你的查询条件尽量封装到查询范围或者搜索器方法里面,查询范围和搜索器的区别主要在于查询范围比较适合定义一组(多个字段)查询条件,如果要调用多个查询范围需要多次调用,而搜索器比较适合定义一个字段(其实并非绝对)的查询条件,只需要调用一次`withSearch`方法。 使用查询范围和搜索器的例子。 ``` <?php namespace app\index\model; use think\Model; class User extends Model { public function scopeVip($query) { $query->where('user_type', 'vip') ->where('status', 1) ->field('id,name'); } public function searchAgeAttr($query, $age) { $query->where('age','>',$age); } public function searchScoreAttr($query, $score) { $query->where('score','<=',$score)->where('score', '>' ,0); } } ``` 控制器代码 ``` <?php namespace app\index\controller; use think\Controller; use think\Request; class index extends Controller { public function index(Request $request) { // 查询VIP会员 User::vip()->select(); // 查询年龄和分数 User::withSearch(['age,'score''], $request->param())->select(); } } ``` 在控制器代码中,我们只关注业务逻辑本身,而不需要关注这个逻辑内部的查询条件是什么。更详细的关于搜索器和查询范围的内容可以参考官方手册。 >[info] 以上就是数据查询的基本原则。下一次,我会给大家讲下数据查询的一些使用技巧。