让 where() 链式操作可以不被覆盖 [ 技术分享 ]
首先抛出一个问题:yii\db\Query中where()方法往往用做第一个条件,后面继续用连接条件要使用andWhere/orWhere/.... 而链式操作->where()->where()会导致后者覆盖前者,所以我们在使用where()时往往要很小心,这样做方便吗?
其实我认为很不方便,因为有时在用where()的时候需要预防前面是否也用过where()方法,用过的话则改用andWhere()。当然,我们也可以一直用andWhere代替where来使用,但这样并不十分方便,我们毕竟用where时居多。
先看看Yii2的源码:
yii\db\Query中:
public function where($condition, $params = [])
{
$this->where = $condition;
$this->addParams($params);
return $this;
}
public function andWhere($condition, $params = [])
{
if ($this->where === null) {
$this->where = $condition;
} else {
$this->where = ['and', $this->where, $condition];
}
$this->addParams($params);
return $this;
}
差别非常明显,where显然就当做一个SQL的Where Clause中第一个条件,而且显然不支持链式操作,两个where()链接一起不是两个条件相与,而是后者覆盖前者。相比andWhere则功能更为强大,功能兼容了前者,可以完全替换where()来使用。
窃以为,这是Yii2设计的不太好的一个方面。干嘛不把where()的功能弄的再强大点?
框架代码当然不能随便改动,但是我们可以通过重载做到这点。在Gii创建模型(如Student)时,生成模型自己的Active Query,而让这个Active Query继承一个基类BaseActiveQuery. 在BaseActiveQuery中重载where()方法:
class BaseActiveQuery extends yii\db\ActiveQuery
{
/**
* where级联的效果等同于andWhere(),不再区分是否是第一个where条件.
*
* @param array|string $condition
* @param array $params
*
* @return $this
*/
public function where($condition, $params = [])
{
if (null === $this->where) {
$this->where = $condition;
} else {
$this->where = ['and', $this->where, $condition];
}
$this->addParams($params);
return $this;
}
}
就是将andWhere的逻辑完全搬过来。 后面,例如在StudentQuery中,自己定义where条件就简单多了:
public function active()
{
$this->where('[[status]]=1');
return $this;
}
同样,级联条件也简单不少:
Studend::find()->where(['age' => 18])->active()->where(...)->orWhere()..->all();
再者,当使用joinWith进行联表查询时,也更加放心:
Teacher::find()->where(...)->joinWith('student' => function($query) {
$query->active()->where(...);
})
大家觉得这样是不是会更方便一点?
共 3 条回复
-
Yii 其中一个特点是高度可定制,任何一个核心类都可以经过自定义,变得适合自己的项目或操作习惯。例如你觉得默认的 where() 不好用,那就自己改一下,让它用着更加顺手。
这是Yii2设计的不太好的一个方面。干嘛不把where()的功能弄的再强大点
我认为这见仁见智吧。如前所述,Yii 已经提供了让核心类“变强大”的方式,像 where() 这种使用频率高、基础的方法,更多考虑的是如何设计得足够通用,就像《迷你世界》里面的石块一样,使用同
一个的石块,不同的玩家可以发挥想象力,盖出各种漂亮的建筑。最后简单一个我日常使用 ActiveQuery 的习惯,抛砖引玉,仅供参考:
常用的查询条件尽可能在 AcriveQuery 内以方法的形式定义
以楼主举的学生、老师模型为例,可以在 StudtentQuery 内定义如下常用方法:
public function youngerThan($age) { return $this->andWhere(['<', '{{%student}}.age', $age]); } public function male() { return $this->andWhere(['{{%student}}.sex' => 1]); } public function toughtBy($teacherIds) { return $this->andWhere(['{{%teacher}}.id' => $teacherIds]); }
方法只要定义得恰当,构建 AcriveQuery 时可以少用甚至不用 where() , andWhere() 或 orWhere(). 例如,我想查一下所有年龄小于 18 岁、同时被张三和李四两位老师教的男同学,就可以这样写:
$students = Student::find()->joinWith(['teacher'])->youngerThan(18)->taughtBy([3, 5])->male()->all();
共 1 条回复
米粒人生 苏州
最后登录:2021-04-25
在线时长:47小时54分
- 粉丝111
- 金钱6555
- 威望230
- 积分9325