Yii2:急切地选择计算列和加载值到model-property中

时间:2015-03-05 09:00:18

标签: activerecord yii2

我认为在此期间我了解Yii2的每个方面,但是这个让我感到头疼。

场合

两个表:客户和比林斯。 Client-Table包含常规的客户列表。 Billing-table为每个客户端提供了几个条目(1:n)。

问题

我想获取计算出的DB-Field和行本身,并通过模型的虚拟属性访问它。

关键是它与行本身一起计算和选择。我知道我可以使用常规虚拟吸气剂计算金额来实现similliar ......但这与选择本身不同。

我的计划

在客户端模型的查询对象中,我尝试添加一个额外的select(addSelect-Method)并为该字段赋予别名。然后我用模型的attributes-method添加了这个select的别名。不知怎的,这没有用。

我的问题

您是否有人知道实现这一目标的正确方法?由于这是一个非常普遍的问题,我无法想象这太过分了。我只是以某种方式无法找到解决方案。

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

1 个答案:

答案 0 :(得分:4)

我自己实际找到了答案。这是你如何做到的:

查询对象

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

class Client extends \yii\db\ActiveRecord
{
  //...
  public static function find()
  {
    return new ClientQuery(get_called_class());
  }
  //...
}

现在在查询对象init() - 方法中,我们可以添加相应的附加选择:

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]);
  }
}

我们现在完成了query-Object。我们现在做了什么?选择客户端时,总和也会被计算和加载。但我们如何获取它?这是我奋斗的难点。解决方案位于ActiveRecord - 类。

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

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

/**
 * 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-method($record->canSetProperty($name))并调用它来填充计算值。因为它是受保护的,否则只读。

Voilà......实际上并没有那么难,绝对有用!