Yii2:急切地选择计算列并将值加载到模型中-属性
Yii2: Eagerly selecting calculated column and loading value into model-property
我以为我了解了 Yii2 的方方面面,但这让我很头疼。
情况
两个 table:客户和比林斯。 Client-Table 持有一个常规的客户列表。 Billing-table 每个客户都有多个条目 (1:n)。
问题
我想获取计算出的 DB-Field 连同行本身,并通过模型的虚拟 属性 访问它。
关键是它与行本身一起计算和 selected。我知道我可以通过常规虚拟 getter 计算数量来实现类似的东西...但这与 select 本身不同时。
我的计划
在客户端模型的查询对象中,我尝试添加一个额外的 select(addSelect-Method)并为该字段指定一个别名。然后我用模型的属性方法添加了这个 select 的别名。不知何故,这不起作用。
我的问题
你们中有人知道实现此目标的正确方法吗?由于这是一个非常普遍的问题,我无法想象这会太难。我就是找不到解决办法。
示例代码:
echo $client->sumOfBillings
应该在客户端模型中输出相应的 属性 的内容。此 属性 的内容应在获取客户行本身时填写,而不是在 属性 被调用时填写。
其实我自己找到了答案。以下是您的操作方法:
查询对象
所有 Yii2 模型的获取是通过它们相应的查询对象完成的。该对象通过模型 find()
-方法检索。如果覆盖此方法,则可以 return 自己的 class 查询对象。在上面的示例中,我的模型如下所示:
class Client extends \yii\db\ActiveRecord
{
//...
public static function find()
{
return new ClientQuery(get_called_class());
}
//...
}
现在在 Query-Objects init()
-Method 中我们可以添加相应的附加选择:
public class ClientQuery extends \yii\db\ActiveQuery
{
public function init()
{
parent::init();
//prepare subquery for calculation
$sub = (new Query())
->select('SUM(billing_amount)')
->from('billing')
->where('billing.client_id = client.id');
$this->addSelect(['client.*', 'sumBillings'=>$sub]);
}
}
我们现在完成了查询对象。我们现在做了什么?选择客户端时,也会计算和加载总和。但是我们如何访问它呢?这是我挣扎的困难部分。解决方案在 ActiveRecord
-class.
内
用计算数据填充模型的可能性
将此数据加载到模型中有多种可能性-class。要了解我们有哪些选项,我们可以查看 BaseActiveRecord
-class:
的 populateRecord($record, $row)
-方法
/**
* Populates an active record object using a row of data from the database/storage.
*
* This is an internal method meant to be called to create active record objects after
* fetching data from the database. It is mainly used by [[ActiveQuery]] to populate
* the query results into active records.
*
* When calling this method manually you should call [[afterFind()]] on the created
* record to trigger the [[EVENT_AFTER_FIND|afterFind Event]].
*
* @param BaseActiveRecord $record the record to be populated. In most cases this will be an instance
* created by [[instantiate()]] beforehand.
* @param array $row attribute values (name => value)
*/
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;
}
如您所见,该方法获取原始数据 ($row
) 并填充模型实例 ($record
)。如果模型具有与计算字段同名的 属性 或 setter 方法,它将填充数据。
客户端模型的最终代码
这是我的客户端模型的最终代码:
class Client extends \yii\db\ActiveRecord
{
private $_sumBillings;
//...
public static function find()
{
return new ClientQuery(get_called_class());
}
public function getSumBillings()
{
return $this->_sumBillings;
}
protected function setSumBillings($val)
{
$this->_sumBillings = $val;
}
//...
}
populateRecord()
-方法会找到setter-方法($record->canSetProperty($name)
)并调用它来填充计算值。由于它受到保护,否则它是只读的。
瞧......实际上并不难,而且绝对有用!
我以为我了解了 Yii2 的方方面面,但这让我很头疼。
情况
两个 table:客户和比林斯。 Client-Table 持有一个常规的客户列表。 Billing-table 每个客户都有多个条目 (1:n)。
问题
我想获取计算出的 DB-Field 连同行本身,并通过模型的虚拟 属性 访问它。
关键是它与行本身一起计算和 selected。我知道我可以通过常规虚拟 getter 计算数量来实现类似的东西...但这与 select 本身不同时。
我的计划
在客户端模型的查询对象中,我尝试添加一个额外的 select(addSelect-Method)并为该字段指定一个别名。然后我用模型的属性方法添加了这个 select 的别名。不知何故,这不起作用。
我的问题
你们中有人知道实现此目标的正确方法吗?由于这是一个非常普遍的问题,我无法想象这会太难。我就是找不到解决办法。
示例代码:
echo $client->sumOfBillings
应该在客户端模型中输出相应的 属性 的内容。此 属性 的内容应在获取客户行本身时填写,而不是在 属性 被调用时填写。
其实我自己找到了答案。以下是您的操作方法:
查询对象
所有 Yii2 模型的获取是通过它们相应的查询对象完成的。该对象通过模型 find()
-方法检索。如果覆盖此方法,则可以 return 自己的 class 查询对象。在上面的示例中,我的模型如下所示:
class Client extends \yii\db\ActiveRecord
{
//...
public static function find()
{
return new ClientQuery(get_called_class());
}
//...
}
现在在 Query-Objects init()
-Method 中我们可以添加相应的附加选择:
public class ClientQuery extends \yii\db\ActiveQuery
{
public function init()
{
parent::init();
//prepare subquery for calculation
$sub = (new Query())
->select('SUM(billing_amount)')
->from('billing')
->where('billing.client_id = client.id');
$this->addSelect(['client.*', 'sumBillings'=>$sub]);
}
}
我们现在完成了查询对象。我们现在做了什么?选择客户端时,也会计算和加载总和。但是我们如何访问它呢?这是我挣扎的困难部分。解决方案在 ActiveRecord
-class.
用计算数据填充模型的可能性
将此数据加载到模型中有多种可能性-class。要了解我们有哪些选项,我们可以查看 BaseActiveRecord
-class:
populateRecord($record, $row)
-方法
/**
* Populates an active record object using a row of data from the database/storage.
*
* This is an internal method meant to be called to create active record objects after
* fetching data from the database. It is mainly used by [[ActiveQuery]] to populate
* the query results into active records.
*
* When calling this method manually you should call [[afterFind()]] on the created
* record to trigger the [[EVENT_AFTER_FIND|afterFind Event]].
*
* @param BaseActiveRecord $record the record to be populated. In most cases this will be an instance
* created by [[instantiate()]] beforehand.
* @param array $row attribute values (name => value)
*/
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;
}
如您所见,该方法获取原始数据 ($row
) 并填充模型实例 ($record
)。如果模型具有与计算字段同名的 属性 或 setter 方法,它将填充数据。
客户端模型的最终代码
这是我的客户端模型的最终代码:
class Client extends \yii\db\ActiveRecord
{
private $_sumBillings;
//...
public static function find()
{
return new ClientQuery(get_called_class());
}
public function getSumBillings()
{
return $this->_sumBillings;
}
protected function setSumBillings($val)
{
$this->_sumBillings = $val;
}
//...
}
populateRecord()
-方法会找到setter-方法($record->canSetProperty($name)
)并调用它来填充计算值。由于它受到保护,否则它是只读的。
瞧......实际上并不难,而且绝对有用!