Activerecord Yii2:急切地选择计算列并将值加载到模型属性中

Activerecord Yii2:急切地选择计算列并将值加载到模型属性中,activerecord,yii2,Activerecord,Yii2,我以为我知道Yii2的方方面面,但这一点让我头疼 形势 两张表:客户和账单。客户机表包含客户机的常规列表。计费表中每个客户端都有几个条目(1:n) 问题 我想获取一个计算出的DB字段以及行本身,并通过模型的虚拟属性访问它 关键是它与行本身一起计算和选择。我知道我可以通过一个常规的虚拟getter计算数量来实现类似的效果…但这与select本身不是同时进行的 我的计划 在客户机模型的查询对象中,我尝试添加一个额外的select(addSelect方法),并给字段一个别名。然后我用模型的attrib

我以为我知道Yii2的方方面面,但这一点让我头疼

形势

两张表:客户和账单。客户机表包含客户机的常规列表。计费表中每个客户端都有几个条目(1:n)

问题

我想获取一个计算出的DB字段以及行本身,并通过模型的虚拟属性访问它

关键是它与行本身一起计算和选择。我知道我可以通过一个常规的虚拟getter计算数量来实现类似的效果…但这与select本身不是同时进行的

我的计划

在客户机模型的查询对象中,我尝试添加一个额外的select(addSelect方法),并给字段一个别名。然后我用模型的attributes方法添加了这个select的别名。不知怎的,这不管用

我的问题

你们当中有人知道实现这一目标的正确方法吗?由于这是一个非常普遍的问题,我无法想象这种情况会变得太严重。我就是找不到解决办法

示例代码:
echo$client->sumOfBillings
应输出客户端模型中相应属性的内容。此属性的内容应该在获取客户端行本身时填写,而不是在调用属性时填写。

我自己实际找到了答案。以下是您的操作方法:

查询对象

所有Yii2模型的获取都是通过它们相应的查询对象完成的。通过models
find()
-方法检索此对象。如果重写此方法,则可以为该类返回自己的查询对象。在上面的示例中,我的模型如下所示:

类客户端扩展\yii\db\ActiveRecord
{
//...
公共静态函数find()
{
返回新的ClientQuery(get_调用_class());
}
//...
}
现在,在Query Objects
init()
-方法中,我们可以添加相应的附加选择:

public类ClientQuery扩展\yii\db\ActiveQuery
{
公共函数init()
{
父::init();
//准备用于计算的子查询
$sub=(新查询())
->选择(‘金额(账单金额)’)
->从('账单')
->其中('billing.client_id=client.id');
$this->addSelect(['client.*,'sumBillings'=>$sub]);
}
}
我们现在完成了查询对象。我们现在做了什么?选择客户端时,也会计算和加载总和。但是我们如何访问它呢?这是我挣扎的最艰难的部分。解决方案位于
ActiveRecord
-类中

使用计算数据填充模型的可能性

有几种可能将此数据加载到模型类中。要了解我们有哪些选项,我们可以查看
BaseActiveRecord
-类的
populateRecord($record,$row)
-方法:

/**
*使用数据库/存储器中的一行数据填充活动记录对象。
*
*这是一个内部方法,用于在之后调用以创建活动记录对象
*正在从数据库中提取数据。它主要由[[ActiveQuery]]用于填充
*将查询结果转换为活动记录。
*
*手动调用此方法时,应在创建的
*记录以触发[[EVENT_AFTER_FIND | afterFind EVENT]]。
*
*@param BaseActiveRecord$记录要填充的记录。在大多数情况下,这将是一个实例
*事先由[[实例化()]]创建。
*@param数组$row属性值(name=>value)
*/
公共静态函数populateRecord($record,$row)
{
$columns=array_flip($record->attributes());
foreach($name=>$value的行){
if(isset($columns[$name])){
$record->_属性[$name]=$value;
}elseif($record->canSetProperty($name)){
$record->$name=$value;
}
}
$record->\u oldAttributes=$record->\u attributes;
}
如您所见,该方法获取原始数据(
$row
)并填充模型实例(
$record
)。如果模型具有与计算字段同名的属性或setter方法,则将使用数据填充该模型

客户端型号的最终代码

这是客户机模型的最终代码:

类客户端扩展\yii\db\ActiveRecord
{
私人消费美元;
//...
公共静态函数find()
{
返回新的ClientQuery(get_调用_class());
}
公共函数getSumBillings()
{
返回$this->\u sumBillings;
}
受保护功能设置缓冲($val)
{
$this->_sumBillings=$val;
}
//...
}
populateRecord()
-方法将找到setter方法(
$record->canSetProperty($name)
),并调用它来填充计算值。由于它受保护,因此它是只读的


瞧……其实没那么难,而且绝对有用

不知道我是否能帮你。然而,我不明白你的意思。你能提供一些(你所期望的)代码吗?@robsch:我在结尾处写了额外的信息,我看不出有什么好处。为什么Client->getSumBilligs()不包含常规查询?但我不是专家!也许我不明白你在这里所描述的。在我看来,你只是让事情变得比必要的更复杂了。@robsch:假设你有10万客户,需要他们的总数。如果在getter中执行查询,将发生以下情况:1。获取100000个客户端(1个查询);2.迭代它们并对每个客户机模型执行求和查询(另外10万个查询)。使用上面的方法,无论您获取的客户端块有多大,您只有一个查询。好的,明白了!这显然是一个很大的优势。值得一提的是,注释l