AR 是用sql查询填充而来的 每个AR中的字段列表默认是跟其对应的表是一致的
如果要查询不属于自己表中的额外字段,并且想通过Ar来访问 此时有两种做法
user 与 comment 为一对多关系
此时需要在查询user实体的基础上查询其对应的评论数,关于这个场景在yii中有个STAT关系是来处理的,但yii2中取消了这个关系。 如果要实现可以参考:yii2-activerecord-stat-relations
// You should simply use the same ActiveQuery, e.g. :
public function getOrders()
{
return $this->hasMany(Order::className(), ['customer_id' => 'id']);
}
public function getOrdersCount()
{
return $this->getOrders()->count();
}
对应上面的例子 就是需要查询用户 连带其评论数。
上面给出了两种解决方法:
张冠李戴 : 就是随便找一个ar已经有的属性 把查询的评论统计数赋值给他 。 还有一种设计,user表中多一个额外字段就好了(comment_count),这个字段在评论增删时需要联动修改。 评论增加 ,递增此表的值,评论删除,递减此表。
目前我还没有做评论的联动修改,此值为零。准确值是想在实际使用时通过子查询动态统计得来 。
声明额外字段 假设User 类本身没有comment_count 字段,那么想在查询时赋值,可以声明此字段:
class User extends ActiveRecord
{
/**
* $var int $commentCount 额外字段 评论数
*/
public $commentCount ;
}
在查询(index,后者view中)通过query 来赋值此字段:
$model = User::findBySql(
"SELECT user.* ,cmtSum.cmtAmount as commentCount
FROM user
LEFT JOIN (SELECT entity_id, SUM(entity_id) as cmtAmount FROM comment WHERE entity_type='User' GROUP BY entity_id) cmtSum
ON cmtSum.entity_id = id
WHERE id=:userId
",
[':userId'=>$userId]
)
->one();
看到上面的用法的select 部分多了额外字段 commentCount,如果你的实体User 中没有这个属性,那么这个值会被丢弃的!!!
关于上面的查询写法,其实还有另外版本,可以参考: filter-sort-by-summary-data-in-gridview-yii-2-0; 悲催的是在这里上面的做法行不通,生成的最终sql是有问题的
$mainModelTableName = User::tableName();
$subQuery = Comment::find()
->select(['entity_id',' SUM(entity_id) as cmtAmount '])
->groupBy('entity_id')
->onCondition([
'entity_type' => 'User',
]);
$model = User::find()
->where(['id' => $userId])
->select(["{$mainModelTableName}.*", 'cmtSum.cmtAmount as commentCount'])
// 计算从表的评论数
->leftJoin(['cmtSum' => $subQuery], 'cmtSum.entity_id = id')
->one();
上面的做法只在postGres下有问题 ,没有测过sql是否可行!
上面的做法看似已经可以了 ,但实际在postgre情况下出了个问题!这个源于额外字段的大小写! 我们声明的是 commentCount , 但pg 好像对sql是大小写不敏感的,这样导致他最终的结果集是commentcount。
AR在填充自身时使用底层数据库返回的结果集来完成的,当碰到自身对应的表(user表)外的字段时对应的代码逻辑是:
// 位于BaseActiveRecord
public static function populateRecord($record, $row)
{
$columns = array_flip($record->attributes());
foreach ($row as $name => $value) {
if (isset($columns[$name])) {
$record->_attributes[$name] = $value;
} elseif ($record->canSetProperty($name)) {
// 注意这里!!
$record->$name = $value;
}
}
$record->_oldAttributes = $record->_attributes;
}
// 继续追踪 $record->canSetProperty
// 位于 yii\base\Model
public function canSetProperty($name, $checkVars = true, $checkBehaviors = true)
{
if (method_exists($this, 'set' . $name) || $checkVars && property_exists($this, $name)) {
return true;
} elseif ($checkBehaviors) {
$this->ensureBehaviors();
foreach ($this->_behaviors as $behavior) {
if ($behavior->canSetProperty($name, $checkVars)) {
return true;
}
}
}
return false;
}
可以看到虚拟属性 setXxx 或者自身声明的变量是可以被填充的 behavior也是会被检查的
我们自己不是声明了commentCount属性么 理论上是可以被填充的,但悲催的事情发生在:property_exists property_exists 这个方法时大小写敏感的,即
if (property_exists(User::className(), 'commontcount')) {
die('1');
} else {
die('0');
}
// 注意大小写
if (property_exists(Restaurant::className(), 'commentCount')) {
die('1');
} else {
die('0');
}
上面的测试得到不同结果!
在回到查询sql语句上 SELECT user. ,cmtSum.cmtAmount as commentCount 这里的语句实际会被postgre 忽略掉大小写 变为: SELECT user. ,cmtSum.cmtAmount as commentcount 这样结果集中就没有commentCount了!所以自然也不会被填充。
这样最后只有重命名为 蛇形名称:commentCount --> commont_count .
全文完!
补充: pg数据库大小写不区分的; 但可以通过添加双引号 强制其区分大小写