如何让Yii框架Users模型的getRating方法仅执行一次查询?
解决查询重复执行的问题
问题根源
你当前的getRating()方法每次被调用都会触发一次数据库查询,若视图中该方法被多次调用(比如模板解析时重复计算表达式),就会执行多次查询。另外代码存在SQL注入风险,直接把$id拼到SQL里是严重的安全问题。
改写方案
方案1:缓存查询结果到模型属性
在Users模型里加个私有属性存储查询结果,避免重复执行:
class Users extends \yii\db\ActiveRecord { private $_rating = null; // 缓存评分结果 public function getRating() { if ($this->_rating === null) { // 用参数绑定替代字符串拼接,彻底避免SQL注入 $this->_rating = \Yii::$app->db->createCommand( "SELECT * FROM ( SELECT *, (@position:=@position+1) as rate FROM ( SELECT executor_id, SUM(rate) / COUNT(rate) as pts FROM user_replies, (SELECT @position:=0) as a GROUP BY executor_id ORDER BY pts DESC ) AS subselect ) as general WHERE executor_id = :id", [':id' => $this->id] // 用模型自身ID替代从request取,逻辑更合理 )->queryOne(); } return $this->_rating; } }
方案2:用Yii查询构建器重构(更符合框架规范)
用Yii自带的查询构建器改写,可读性和可维护性更好:
class Users extends \yii\db\ActiveRecord { private $_rating = null; public function getRating() { if ($this->_rating === null) { // 第一步:计算所有执行者的平均分 $avgQuery = (new \yii\db\Query()) ->select(['executor_id', 'SUM(rate)/COUNT(rate) as pts']) ->from('user_replies') ->groupBy('executor_id') ->orderBy('pts DESC'); // 第二步:计算排名 $rankQuery = (new \yii\db\Query()) ->select(['*', '(@position:=@position+1) as rate']) ->from(['subselect' => $avgQuery]) ->crossJoin(['(SELECT @position:=0) as a']); // 第三步:筛选当前用户的排名数据 $this->_rating = (new \yii\db\Query()) ->select('*') ->from(['general' => $rankQuery]) ->where(['executor_id' => $this->id]) ->createCommand() ->queryOne(); } return $this->_rating; } }
视图调用保持不变
视图里依然可以用原来的写法:
<?php echo $singleUser->getRating()['rate']; ?>
现在不管调用多少次getRating(),只会执行一次数据库查询。
额外提醒:原来从request取id的逻辑不合理,因为Users模型对应具体的用户实例,直接用$this->id才是面向对象的正确做法,还能避免请求参数和模型实例不匹配的问题。
内容的提问来源于stack exchange,提问作者user17045604
相关产品推荐
相关产品推荐

