如何在 GridView 中只调用一次函数?

How to invoke function only once in GridView?

我要警告:我不能使用array,只能使用Active Record

所以我有一个 GridView 和列:

$gridColumns = [
    ['class' => SerialColumn::class],
    [
        'attribute' => 'cap',
        'label' => 'Всего, Зан., Св.',
        'format' => 'raw',
        'value' => function($model) {
            $data = Yii::$app->db->createCommand("SELECT * FROM dbFunction({$model->point_id}")->queryOne();
            $html = <<< HTML
<div class="item">{$data['capacity']}</div>
<div class="item">{$data['occupied']}</div>
<div class="item">{$data['free']}</div>
HTML;       
            return $html;
        },      
    ],  
];

echo GridView::widget([
    'id' => 'my-table',
    'columns' => $gridColumns,
    'dataProvider' => $dataProvider,
    'filterModel' => $searchModel,  
    'rowOptions' => function($model) {
        $data = Yii::$app->db->createCommand("SELECT * FROM dbFunction({$model->point_id}")->queryOne();        
        
        $class = $data['free'] === 0 ? 'free-point' : '';
                
        if ($data['capacity'] === 0 && $data['occupied'] === 0 && $data['free'] === 0)
            $class = 'd-none';
        
        return [
            'data-id' => $model->id,
            'class' =>  $class,
        ];
    },
]);

它工作正常,但 dbFunction 对每一行执行两次(第一次是针对单元格,第二次是针对行选项)。这个功能很重量级

问题是:有没有办法只执行一次这个函数(对于每次迭代)并在两个地方都使用函数的结果?不知道该怎么做,因为在幕后 Yii2columns 使用循环,我猜,单独调用 rowOptions。怎么组合呢?

P.S。当然,我可以使用 js 为 rowOptions 编写条件,但这将是一个错误:它将被写为“显示 60 条记录中的 30 条”,但实际上它只会显示 5(或其他)。这就是为什么我想在后端解决问题。

我建议将加载数据的函数移动到模型中并将结果存储在模型的私有 属性 中。

例如这样的事情:

class MyModel extends ActiveRecord
{
    private ?array $capacityData = null;
    
    public function capacityData(): array
    {
        if ($this->capacityData === null) {
            $this->capacityData  = $this->getDb()->createCommand(
                "SELECT * FROM dbFunction({$model->point_id})"
            )->queryOne();
        }

        return $this->capacityData;
    }

    // ... other definitions ...
}

然后你可以像这样在多个地方使用它:

'value' => function($model) {
     $data = $model->capacityData();
     $html = <<< HTML
<div class="item">{$data['capacity']}</div>
<div class="item">{$data['occupied']}</div>
<div class="item">{$data['free']}</div>
HTML;       
     return $html;
},      

或者像这样:

'rowOptions' => function($model) {
    $data = $model->capacityData();        
        
    $class = $data['free'] === 0 ? 'free-point' : '';
                
    if ($data['capacity'] === 0 && $data['occupied'] === 0 && $data['free'] === 0)
        $class = 'd-none';
        
    return [
        'data-id' => $model->id,
        'class' =>  $class,
    ];
},

SQL 查询只会在第一次调用 capacityData() 函数时执行。在下次调用期间,数据将在 $capacityData 属性.

中可用

顺便说一句。在隐藏行的 $rowOptions 属性 中添加 class 不会解决文本中错误数字“显示 60 条记录中的 30 条”的问题。文本显示正确数字的唯一方法是数据提供者不包含不应显示的行。

我认为您可以为此尝试使用查询缓存。像这样:

Yii::$app->db->cache(function ($db, $model) {
        return $db->createCommand(
            "SELECT * FROM dbFunction({$model->point_id})"
        )->queryOne();
});

这样第二次调用将 return 缓存结果。

P.S。您还需要在数据库配置中配置缓存组件和查询缓存。